comparison water.c @ 3:e75ecd162da3

Add watering logic ressurected from 2013. Handles basic "water all regions for X minutes in X minutes time" Tolerates power failures.
author Daniel O'Connor <darius@dons.net.au>
date Tue, 27 Jan 2015 23:40:01 +1030
parents
children 951277329ee6
comparison
equal deleted inserted replaced
2:ae8fb85a4949 3:e75ecd162da3
1 /*
2 * Watering logic
3 *
4 * Copyright (c) 2015
5 * Daniel O'Connor <darius@dons.net.au>. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <avr/io.h>
33 #include <avr/interrupt.h>
34 #include <avr/pgmspace.h>
35 #include <avr/eeprom.h>
36 #include <util/crc16.h>
37
38 #include "ds1307.h"
39 #include "water.h"
40
41 struct tm {
42 int32_t sec;
43 int32_t usec;
44 };
45
46 volatile struct tm nowtm;
47
48 #define RELAY_MASK 0xf0
49
50 typedef struct {
51 char *name;
52 uint8_t relay;
53 } region_t;
54
55 static region_t regions[] = {
56 { "front", 4 },
57 { "beds", 5 },
58 // { "side", 6 },
59 };
60
61 #define NJOBS 6
62
63 typedef struct {
64 uint8_t valid;
65 uint32_t start;
66 uint16_t length;
67 uint8_t region;
68 } job_t;
69
70 typedef struct {
71 job_t jobs[NJOBS];
72 } settings_t;
73
74 /* Current settings in RAM */
75 static settings_t settings;
76 /* EEPROM copy of settings */
77 struct {
78 settings_t settings;
79 uint16_t crc;
80 } ee_area __attribute__((section(".eeprom")));
81
82 /* Local function prototypes */
83 static void water_default_settings(void);
84 static void water_write_settings(void);
85 static void water_load_or_init_settings(void);
86 static void water_addjob(int region, uint16_t delay, uint16_t time);
87 static void water_printjobs(void);
88
89 void
90 water_init(void) {
91 puts_P(PSTR("Initing water stuff\r\n"));
92
93 water_load_or_init_settings();
94
95 /* Setup timer */
96 /* 8Mhz / 256 = 31250 Hz / 250 = 125 Hz = IRQ every 8 ms */
97
98 /* CTC mode, no output on pin, Divide clock by 256 */
99 TCCR2A = _BV(WGM21);
100 TCCR2B = _BV(CS22) | _BV(CS21);
101
102 /* Compare with ... */
103 OCR2A = 250;
104
105 /* Enable interrupt for match on A */
106 TIMSK2 = _BV(OCIE2A);
107
108 nowtm.sec = 0;
109 nowtm.usec = 0;
110 }
111
112 /*
113 * Timer 2 Compare IRQ
114 *
115 * Update time counter
116 */
117
118 ISR(TIMER2_COMPA_vect) {
119 nowtm.usec += 8000;
120 while (nowtm.usec > 1000000) {
121 nowtm.usec -= 1000000;
122 nowtm.sec++;
123 }
124 }
125
126 #define MAXARGS 10
127
128 /* Parse water related command */
129 void
130 water_cmd(char *buf) {
131 char *e, **btmp;
132 uint16_t delay;
133 uint16_t time;
134 long tmp;
135 int i, argc, skip;
136 char *argv[MAXARGS];
137
138 /* Split command string on space/tab boundaries into argv/argc */
139 argc = 0;
140 btmp = &buf;
141 skip = 1;
142 while (1) {
143 argv[argc] = strsep(btmp, " \t");
144 if (argv[argc] == '\0')
145 break;
146 /* Skip first arg (ie 'wa') */
147 if (skip) {
148 skip = 0;
149 continue;
150 }
151 argc++;
152 if (argc == MAXARGS)
153 break;
154 }
155
156 if (!strcmp(argv[0], "all")) {
157 if (argc != 3) {
158 puts_P(PSTR("Wrong number of arguments, usage: wa all delay time\r\n"));
159 return;
160 }
161
162 tmp = strtol(argv[1], &e, 10);
163 if (e == argv[1]) {
164 puts_P(PSTR("Unable to parse delay\r\n"));
165 return;
166 }
167 if (tmp < 0 || tmp > 65535) {
168 puts_P(PSTR("delay out of range, must be 0 - 65535\r\n"));
169 return;
170 }
171 delay = tmp;
172
173 tmp = strtol(argv[2], &e, 10);
174 if (e == argv[2]) {
175 puts_P(PSTR("Unable to parse time\r\n"));
176 return;
177 }
178 if (tmp < 1 || tmp > 65535) {
179 puts_P(PSTR("time out of range, must be 1 - 65535\r\n"));
180 return;
181 }
182 time = tmp;
183
184 water_printjobs();
185
186 /* add for each region one after the other */
187 for (i = 0; i < sizeof(regions) / sizeof(regions[0]); i++) {
188 water_addjob(i, delay + time * i, time);
189 }
190
191 water_printjobs();
192 } else {
193 puts_P(PSTR("Unknown 'wa' sub-command\r\n"));
194 return;
195 }
196 }
197
198 static void
199 water_addjob(int region, uint16_t delay, uint16_t time) {
200 int i;
201 time_t now;
202
203 printf_P(PSTR("adding region %d, delay %d, time %d\r\n"), region, delay, time);
204
205 now = ds1307_time();
206 if (now == -1) {
207 puts_P(PSTR("Unable to read time\r\n"));
208 return;
209 }
210
211 /* Find an open job slot */
212 for (i = 0; i < NJOBS; i++) {
213 if (settings.jobs[i].valid == 0)
214 break;
215 }
216
217 if (i == NJOBS) {
218 puts_P(PSTR("Couldn't find free slot\r\n"));
219 return;
220 }
221
222 settings.jobs[i].start = now + delay * 60;
223 settings.jobs[i].length = time * 60;
224 settings.jobs[i].region = region;
225 settings.jobs[i].valid = 1;
226
227 water_write_settings();
228 }
229
230 static void
231 water_printjobs(void) {
232 int i;
233
234 for (i = 0; i < NJOBS; i++) {
235 printf_P(PSTR("%d: "), i);
236 if (settings.jobs[i].valid) {
237 printf_P(PSTR("region %d (%s) start %ld length %d\r\n"), settings.jobs[i].region, regions[settings.jobs[i].region].name,
238 settings.jobs[i].start, settings.jobs[i].length);
239 } else {
240 printf_P(PSTR("invalid\r\n"));
241 }
242 }
243 }
244
245 /* Update water related state machine */
246 void
247 water_update(void) {
248 static time_t lastcheck = 0;
249 int i;
250 uint8_t relays;
251 static int16_t lastrelays = -1;
252 time_t now;
253 static uint8_t logged[NJOBS] = {0};
254
255 if (lastcheck + 5 > nowtm.sec)
256 return;
257
258 lastcheck = nowtm.sec;
259
260 now = ds1307_time();
261 if (now == -1) {
262 puts_P(PSTR("Unable to read time\r\n"));
263 return;
264 }
265
266 relays = 0;
267
268 for (i = 0; i < NJOBS; i++) {
269 if (settings.jobs[i].valid) {
270 if (now > settings.jobs[i].start + settings.jobs[i].length) {
271 printf_P(PSTR("Marking job %d (%s) done\r\n"), i, regions[settings.jobs[i].region].name);
272 settings.jobs[i].valid = 0;
273 logged[i] = 0;
274 continue;
275 }
276
277 if (now > settings.jobs[i].start) {
278 if (logged[i] == 0) {
279 printf_P(PSTR("Job %d (%s) running\r\n"), i, regions[settings.jobs[i].region].name);
280 logged[i] = 1;
281 }
282 relays |= 1 << regions[settings.jobs[i].region].relay;
283 continue;
284 }
285 }
286 }
287
288 /* Update EEPROM with jobs */
289 water_write_settings();
290
291 if (lastrelays == -1 || lastrelays != relays) {
292 printf_P(PSTR("[%ld] Setting relays to 0x%02x\r\n"), now, relays);
293 lastrelays = relays;
294 }
295
296 PORTB = (PORTB & ~RELAY_MASK) | relays;
297 }
298
299 /* Read the settings from EEPROM
300 * If the CRC fails then reload from flash
301 */
302 static void
303 water_load_or_init_settings(void) {
304 uint8_t *dptr;
305 uint16_t crc, strcrc;
306 int i;
307
308 puts_P(PSTR("Loading settings\r\n"));
309
310 crc = 0;
311 eeprom_busy_wait();
312 eeprom_read_block(&settings, &ee_area.settings, sizeof(settings_t));
313 strcrc = eeprom_read_word(&ee_area.crc);
314
315 dptr = (uint8_t *)&settings;
316
317 for (i = 0; i < sizeof(settings_t); i++)
318 crc = _crc16_update(crc, dptr[i]);
319
320 /* All OK? */
321 if (crc == strcrc)
322 return;
323
324 printf_P(PSTR("CRC mismatch got 0x%04x vs 0x%04x, setting defaults\r\n"), crc, strcrc);
325 water_default_settings();
326 water_write_settings();
327 }
328
329 /* Set defaults */
330 static void
331 water_default_settings(void) {
332 int i;
333
334 puts_P(PSTR("Defaulting settings\r\n"));
335
336 for (i = 0; i < NJOBS; i++)
337 settings.jobs[i].valid = 0;
338 }
339
340 /* Write the current settings out to EEPROM */
341 static void
342 water_write_settings(void) {
343 uint16_t crc;
344 uint8_t *dptr;
345 int i;
346
347 //puts_P(PSTR("Saving settings\r\n"));
348
349 eeprom_busy_wait();
350 /* Use update variant to save EEPROM cycles */
351 eeprom_update_block(&settings, &ee_area.settings, sizeof(settings_t));
352
353 dptr = (uint8_t *)&settings;
354 crc = 0;
355 for (i = 0; i < sizeof(settings_t); i++)
356 crc = _crc16_update(crc, dptr[i]);
357
358 eeprom_update_word(&ee_area.crc, crc);
359 }