Mercurial > ~darius > hgwebdir.cgi > sprink
view 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 |
line wrap: on
line source
/* * Watering logic * * Copyright (c) 2015 * Daniel O'Connor <darius@dons.net.au>. 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 <stdio.h> #include <stdlib.h> #include <string.h> #include <avr/io.h> #include <avr/interrupt.h> #include <avr/pgmspace.h> #include <avr/eeprom.h> #include <util/crc16.h> #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); }