view water.c @ 8:c32b5792683a

Rework code to allow multiple relays to be set per job.
author Daniel O'Connor <darius@dons.net.au>
date Sun, 15 Feb 2015 17:47:55 +1030
parents 951277329ee6
children 13903d8343a6
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	relays;
} 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(uint8_t relays, uint16_t delay, uint16_t time);
static void		water_deljob(int job);
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, time;
    uint8_t	relays;
    long	tmp;
    int		region;

    if (!strcasecmp_P(argv[0], PSTR("add"))) {
	if (argc != 4) {
	    puts_P(PSTR("Wrong number of arguments, usage: wa add region delay time\r\n"));
	    return;
	}

	relays = 0;
	if (!strcasecmp_P(argv[1], PSTR("all"))) {
	    /* all => Or everything together */
	    for (region = 0; region < sizeof(regions) / sizeof(regions[0]); region++)
		relays |= 1 << regions[region].relay;
	} else {
	    /* find if it's a valid region */
	    for (region = 0; region < sizeof(regions) / sizeof(regions[0]); region++)
		if (!strcasecmp(argv[1], regions[region].name))
		    relays |= 1 << regions[region].relay;
	    if (relays == 0) {
		puts_P(PSTR("Unknown region\r\n"));
		return;
	    }
	}
	tmp = strtol(argv[2], &e, 10);
	if (e == argv[2]) {
	    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[3], &e, 10);
	if (e == argv[3]) {
	    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_addjob(relays, delay, time);

	water_printjobs();
    } else if (!strcasecmp_P(argv[0], PSTR("del"))) {
	if (argc != 2) {
	    puts_P(PSTR("Wrong number of arguments, usage: wa del job\r\n"));
	    return;
	}

	tmp = strtol(argv[1], &e, 10);
	if (e == argv[1]) {
	    puts_P(PSTR("Unable to parse job\r\n"));
	    return;
	}
	water_deljob(tmp);
    } else if (!strcasecmp_P(argv[0], PSTR("pr"))) {
	if (argc != 1) {
	    puts_P(PSTR("Wrong number of arguments, usage: wa pr\r\n"));
	    return;
	}
	water_printjobs();
    } else if (!strcasecmp_P(argv[0], PSTR("regions"))) {
	int i;
	if (argc != 1) {
	    puts_P(PSTR("Wrong number of arguments, usage: wa regions\r\n"));
	    return;
	}
	printf_P(PSTR("%-20S %S\r\n"), PSTR("Region"), PSTR("Relay"));
	for (i = 0; i < sizeof(regions) / sizeof(regions[0]); i++)
	    printf_P(PSTR("%-20s %d\r\n"), regions[i].name, regions[i].relay);
    } else if (!strcasecmp_P(argv[0], PSTR("help"))) {
        puts_P(PSTR(
	      "wa help                   This help\r\n"
	      "wa add region dly time    Water region (or all) for time minutes after dly minutes\r\n"
	      "wa del job                Delete job\r\n"
	      "wa pr                     Print list of jobs\r\n"
	      "wa regions                Print list of regions\r\n"
	  ));
    } else {
	    puts_P(PSTR("Unknown 'wa' sub-command. 'wa help' for help\r\n"));
	    return;
    }
}

static void
water_addjob(uint8_t relays, uint16_t delay, uint16_t time) {
    int		i;
    time_t	now;

    printf_P(PSTR("adding delays 0x%02x, delay %d, time %d\r\n"), relays, 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].relays = relays;
    settings.jobs[i].valid = 1;

    water_write_settings();
}

static void
water_deljob(int job) {
    if (job < 0 || job >= NJOBS) {
	    puts_P(PSTR("Invalid job number\r\n"));
	    return;
    }

    if (settings.jobs[job].valid == 0) {
	    puts_P(PSTR("Job already deleted\r\n"));
	    return;
    }

    settings.jobs[job].valid = 0;
    printf_P(PSTR("Removed job %d\r\n"), job);

    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("relays 0x%02x start %ld length %d\r\n"), settings.jobs[i].relays,
		     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 (relays 0x%02x) done\r\n"), i, settings.jobs[i].relays);
		settings.jobs[i].valid = 0;
		logged[i] = 0;
		continue;
	    }

	    if (now > settings.jobs[i].start) {
		if (logged[i] == 0) {
		    printf_P(PSTR("Job %d (relays 0x%02x) running\r\n"), i, settings.jobs[i].relays);
		    logged[i] = 1;
		}
		relays |= 1 << settings.jobs[i].relays;
		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);
}