view water.c @ 5:951277329ee6

Use splitargv instead of rolling our own. Fix WDT tripping continuously on self reset (disable WDT on startup). Pretty up startup message.
author Daniel O'Connor <darius@dons.net.au>
date Sun, 15 Feb 2015 16:16:54 +1030
parents e75ecd162da3
children c32b5792683a
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 "splitargv.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++;
    }
}

/* Parse water related command */
void
water_cmd(int argc, char **argv) {
    char	*e;
    uint16_t	delay;
    uint16_t	time;
    long	tmp;
    int		i;

    if (!strcmp_P(argv[0], PSTR("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);
}