# HG changeset patch # User Daniel O'Connor # Date 1422364201 -37800 # Node ID e75ecd162da344ccc69a4f1aaa84e3185a5100b5 # Parent ae8fb85a494933980515d87fb730ee6d7d73c7a2 Add watering logic ressurected from 2013. Handles basic "water all regions for X minutes in X minutes time" Tolerates power failures. diff -r ae8fb85a4949 -r e75ecd162da3 water.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/water.c Tue Jan 27 23:40:01 2015 +1030 @@ -0,0 +1,359 @@ +/* + * Watering logic + * + * Copyright (c) 2015 + * Daniel O'Connor . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ds1307.h" +#include "water.h" + +struct tm { + int32_t sec; + int32_t usec; +}; + +volatile struct tm nowtm; + +#define RELAY_MASK 0xf0 + +typedef struct { + char *name; + uint8_t relay; +} region_t; + +static region_t regions[] = { + { "front", 4 }, + { "beds", 5 }, +// { "side", 6 }, +}; + +#define NJOBS 6 + +typedef struct { + uint8_t valid; + uint32_t start; + uint16_t length; + uint8_t region; +} job_t; + +typedef struct { + job_t jobs[NJOBS]; +} settings_t; + +/* Current settings in RAM */ +static settings_t settings; +/* EEPROM copy of settings */ +struct { + settings_t settings; + uint16_t crc; +} ee_area __attribute__((section(".eeprom"))); + +/* Local function prototypes */ +static void water_default_settings(void); +static void water_write_settings(void); +static void water_load_or_init_settings(void); +static void water_addjob(int region, uint16_t delay, uint16_t time); +static void water_printjobs(void); + +void +water_init(void) { + puts_P(PSTR("Initing water stuff\r\n")); + + water_load_or_init_settings(); + + /* Setup timer */ + /* 8Mhz / 256 = 31250 Hz / 250 = 125 Hz = IRQ every 8 ms */ + + /* CTC mode, no output on pin, Divide clock by 256 */ + TCCR2A = _BV(WGM21); + TCCR2B = _BV(CS22) | _BV(CS21); + + /* Compare with ... */ + OCR2A = 250; + + /* Enable interrupt for match on A */ + TIMSK2 = _BV(OCIE2A); + + nowtm.sec = 0; + nowtm.usec = 0; +} + +/* + * Timer 2 Compare IRQ + * + * Update time counter + */ + +ISR(TIMER2_COMPA_vect) { + nowtm.usec += 8000; + while (nowtm.usec > 1000000) { + nowtm.usec -= 1000000; + nowtm.sec++; + } +} + +#define MAXARGS 10 + +/* Parse water related command */ +void +water_cmd(char *buf) { + char *e, **btmp; + uint16_t delay; + uint16_t time; + long tmp; + int i, argc, skip; + char *argv[MAXARGS]; + + /* Split command string on space/tab boundaries into argv/argc */ + argc = 0; + btmp = &buf; + skip = 1; + while (1) { + argv[argc] = strsep(btmp, " \t"); + if (argv[argc] == '\0') + break; + /* Skip first arg (ie 'wa') */ + if (skip) { + skip = 0; + continue; + } + argc++; + if (argc == MAXARGS) + break; + } + + if (!strcmp(argv[0], "all")) { + if (argc != 3) { + puts_P(PSTR("Wrong number of arguments, usage: wa all delay time\r\n")); + return; + } + + tmp = strtol(argv[1], &e, 10); + if (e == argv[1]) { + puts_P(PSTR("Unable to parse delay\r\n")); + return; + } + if (tmp < 0 || tmp > 65535) { + puts_P(PSTR("delay out of range, must be 0 - 65535\r\n")); + return; + } + delay = tmp; + + tmp = strtol(argv[2], &e, 10); + if (e == argv[2]) { + puts_P(PSTR("Unable to parse time\r\n")); + return; + } + if (tmp < 1 || tmp > 65535) { + puts_P(PSTR("time out of range, must be 1 - 65535\r\n")); + return; + } + time = tmp; + + water_printjobs(); + + /* add for each region one after the other */ + for (i = 0; i < sizeof(regions) / sizeof(regions[0]); i++) { + water_addjob(i, delay + time * i, time); + } + + water_printjobs(); + } else { + puts_P(PSTR("Unknown 'wa' sub-command\r\n")); + return; + } +} + +static void +water_addjob(int region, uint16_t delay, uint16_t time) { + int i; + time_t now; + + printf_P(PSTR("adding region %d, delay %d, time %d\r\n"), region, delay, time); + + now = ds1307_time(); + if (now == -1) { + puts_P(PSTR("Unable to read time\r\n")); + return; + } + + /* Find an open job slot */ + for (i = 0; i < NJOBS; i++) { + if (settings.jobs[i].valid == 0) + break; + } + + if (i == NJOBS) { + puts_P(PSTR("Couldn't find free slot\r\n")); + return; + } + + settings.jobs[i].start = now + delay * 60; + settings.jobs[i].length = time * 60; + settings.jobs[i].region = region; + settings.jobs[i].valid = 1; + + water_write_settings(); +} + +static void +water_printjobs(void) { + int i; + + for (i = 0; i < NJOBS; i++) { + printf_P(PSTR("%d: "), i); + if (settings.jobs[i].valid) { + printf_P(PSTR("region %d (%s) start %ld length %d\r\n"), settings.jobs[i].region, regions[settings.jobs[i].region].name, + settings.jobs[i].start, settings.jobs[i].length); + } else { + printf_P(PSTR("invalid\r\n")); + } + } +} + +/* Update water related state machine */ +void +water_update(void) { + static time_t lastcheck = 0; + int i; + uint8_t relays; + static int16_t lastrelays = -1; + time_t now; + static uint8_t logged[NJOBS] = {0}; + + if (lastcheck + 5 > nowtm.sec) + return; + + lastcheck = nowtm.sec; + + now = ds1307_time(); + if (now == -1) { + puts_P(PSTR("Unable to read time\r\n")); + return; + } + + relays = 0; + + for (i = 0; i < NJOBS; i++) { + if (settings.jobs[i].valid) { + if (now > settings.jobs[i].start + settings.jobs[i].length) { + printf_P(PSTR("Marking job %d (%s) done\r\n"), i, regions[settings.jobs[i].region].name); + settings.jobs[i].valid = 0; + logged[i] = 0; + continue; + } + + if (now > settings.jobs[i].start) { + if (logged[i] == 0) { + printf_P(PSTR("Job %d (%s) running\r\n"), i, regions[settings.jobs[i].region].name); + logged[i] = 1; + } + relays |= 1 << regions[settings.jobs[i].region].relay; + continue; + } + } + } + + /* Update EEPROM with jobs */ + water_write_settings(); + + if (lastrelays == -1 || lastrelays != relays) { + printf_P(PSTR("[%ld] Setting relays to 0x%02x\r\n"), now, relays); + lastrelays = relays; + } + + PORTB = (PORTB & ~RELAY_MASK) | relays; +} + +/* Read the settings from EEPROM + * If the CRC fails then reload from flash + */ +static void +water_load_or_init_settings(void) { + uint8_t *dptr; + uint16_t crc, strcrc; + int i; + + puts_P(PSTR("Loading settings\r\n")); + + crc = 0; + eeprom_busy_wait(); + eeprom_read_block(&settings, &ee_area.settings, sizeof(settings_t)); + strcrc = eeprom_read_word(&ee_area.crc); + + dptr = (uint8_t *)&settings; + + for (i = 0; i < sizeof(settings_t); i++) + crc = _crc16_update(crc, dptr[i]); + + /* All OK? */ + if (crc == strcrc) + return; + + printf_P(PSTR("CRC mismatch got 0x%04x vs 0x%04x, setting defaults\r\n"), crc, strcrc); + water_default_settings(); + water_write_settings(); +} + +/* Set defaults */ +static void +water_default_settings(void) { + int i; + + puts_P(PSTR("Defaulting settings\r\n")); + + for (i = 0; i < NJOBS; i++) + settings.jobs[i].valid = 0; +} + +/* Write the current settings out to EEPROM */ +static void +water_write_settings(void) { + uint16_t crc; + uint8_t *dptr; + int i; + + //puts_P(PSTR("Saving settings\r\n")); + + eeprom_busy_wait(); + /* Use update variant to save EEPROM cycles */ + eeprom_update_block(&settings, &ee_area.settings, sizeof(settings_t)); + + dptr = (uint8_t *)&settings; + crc = 0; + for (i = 0; i < sizeof(settings_t); i++) + crc = _crc16_update(crc, dptr[i]); + + eeprom_update_word(&ee_area.crc, crc); +} diff -r ae8fb85a4949 -r e75ecd162da3 water.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/water.h Tue Jan 27 23:40:01 2015 +1030 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2012 + * Daniel O'Connor . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +void water_init(void); +void water_cmd(char *buf); +void water_update(void); +