# HG changeset patch # User Matt Johnston # Date 1350222715 -28800 # Node ID 97aad447159360200310bc3109a17bc656c7055a # Parent 66d5e15f40d0040242bdcbfeecdeff56a79babfc# Parent de950be796dda4758ca0bcd6ac8559bbca3451a1 merge diff -r de950be796dd -r 97aad4471593 main.c --- a/main.c Thu Oct 11 20:41:52 2012 +0800 +++ b/main.c Sun Oct 14 21:51:55 2012 +0800 @@ -22,6 +22,9 @@ // - bluetooth params // - number of sensors (and range?) +#define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) + // TICK should be 8 or less (8 untested). all timers need // to be a multiple. @@ -32,6 +35,14 @@ #define VALUE_NOSENSOR 0x07D0 // 125 degrees #define VALUE_BROKEN 0x07D1 // 125.0625 +// ranges are in decicelcius +#define OVERSHOOT_DELAY 1800 // 30 mins +#define OVERSHOOT_FACTOR 1 +#define WORT_INVALID_TIME 900 // 15 mins +// fridge min/max are only used if the wort sensor is invalid +#define FRIDGE_AIR_MIN_RANGE 40 // 4º +#define FRIDGE_AIR_MAX_RANGE 40 // 4º + #define BAUD 19200 #define UBRR ((F_CPU)/8/(BAUD)-1) @@ -49,7 +60,7 @@ // total amount of 16bit values available for measurements. // adjust emperically, be sure to allow enough stack space too -#define TOTAL_MEASUREMENTS 840 +#define TOTAL_MEASUREMENTS 800 // each sensor slot uses 8 bytes #define MAX_SENSORS 6 @@ -75,10 +86,11 @@ static uint8_t wake_secs = 30; // decidegrees static int16_t fridge_setpoint = 180; // 18.0ºC -static int16_t fridge_difference = 5; // 0.5ºC +static int16_t fridge_difference = 3; // 0.3ºC static uint16_t fridge_delay = 600; // seconds // ---- Atomic guards required accessing these variables +// clock_epoch in seconds static uint32_t clock_epoch; static uint16_t comms_count; static uint16_t measure_count; @@ -117,6 +129,8 @@ static int16_t last_fridge = DS18X20_INVALID_DECICELSIUS; static int16_t last_wort = DS18X20_INVALID_DECICELSIUS; static struct epoch_ticks fridge_off_clock = {0}; +static struct epoch_ticks fridge_on_clock = {0}; +static struct epoch_ticks wort_valid_clock = {0}; int uart_putchar(char c, FILE *stream); static void long_delay(int ms); @@ -204,8 +218,8 @@ DDR_LED |= _BV(PIN_LED); DDR_SHDN |= _BV(PIN_SHDN); + PORT_FRIDGE &= ~_BV(PIN_FRIDGE); DDR_FRIDGE |= _BV(PIN_FRIDGE); - PORT_FRIDGE &= ~_BV(PIN_FRIDGE); // set pullup PORTD |= _BV(PD2); @@ -364,6 +378,10 @@ fprintf_P(crc_stdout, PSTR("fridge_diff=%.1f\n"), fridge_difference/10.0); fprintf_P(crc_stdout, PSTR("fridge_delay=%hu\n"), fridge_delay); fprintf_P(crc_stdout, PSTR("fridge_status=%hhu\n"), is_fridge_on()); + fprintf_P(crc_stdout, PSTR("fridge_last_on=%lu\n"), fridge_on_clock.ticks); + fprintf_P(crc_stdout, PSTR("fridge_last_off=%lu\n"), fridge_off_clock.ticks); + fprintf_P(crc_stdout, PSTR("last_fridge=%hu\n"), last_fridge); + fprintf_P(crc_stdout, PSTR("last_wort=%hu\n"), last_wort); fprintf_P(crc_stdout, PSTR("tick_secs=%d\n"), TICK); fprintf_P(crc_stdout, PSTR("tick_wake=%d\n"), SLEEP_COMPARE); fprintf_P(crc_stdout, PSTR("maxsens=%hhu\n"), MAX_SENSORS); @@ -574,16 +592,22 @@ return; } + int16_t old_setpoint = fridge_setpoint; + fridge_setpoint = new_f * 10; bool written = set_initial_eeprom(); if (!written) { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + if (old_setpoint != fridge_setpoint) { - eeprom_write(fridge_setpoint, fridge_setpoint); + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + eeprom_write(fridge_setpoint, fridge_setpoint); + } } } - printf_P(PSTR("new fridge %.1fº\n"), fridge_setpoint / 10.0f); + printf_P(PSTR("old fridge %.1fº new fridge %.1fº\n"), + old_setpoint / 10.0f, fridge_setpoint / 10.0f); } static void @@ -814,29 +838,86 @@ { struct epoch_ticks now; get_epoch_ticks(&now); - uint16_t delay_delta = now.ticks - fridge_off_clock.ticks; - if (delay_delta < fridge_delay) + uint32_t off_time = now.ticks - fridge_off_clock.ticks; + bool wort_valid = last_wort != DS18X20_INVALID_DECICELSIUS; + bool fridge_valid = last_fridge != DS18X20_INVALID_DECICELSIUS; + + int16_t wort_max = fridge_setpoint + fridge_difference; + int16_t wort_min = fridge_setpoint; + + // the fridge min/max only apply if the wort sensor is broken + int16_t fridge_min = fridge_setpoint - FRIDGE_AIR_MIN_RANGE; + int16_t fridge_max = fridge_setpoint + FRIDGE_AIR_MAX_RANGE; + + uint8_t fridge_on = PORT_FRIDGE & _BV(PIN_FRIDGE); + printf_P(PSTR("last_wort %hd (%hd, %hd), last_fridge %hd (%hd, %hd), setpoint %hd, diff %hd, fridge_on %hhu\n"), + last_wort, wort_min, wort_max, + last_fridge, fridge_min, fridge_max, + fridge_setpoint, fridge_difference, fridge_on); + + if (off_time < fridge_delay) { printf_P(PSTR("waiting for fridge delay current %hu, wait %hu\n"), - delay_delta, fridge_delay); + off_time, fridge_delay); return; } - if (last_wort == DS18X20_INVALID_DECICELSIUS) + // handle failure of the wort sensor. if it is a short (intermittent?) + // failure we wait until it has been broken for a period of time + // (WORT_INVALID_TIME) before doing anything. + if (wort_valid) + { + wort_valid_clock = now; + } + else { - // can't really do much sensible.... alert perhaps? - printf_P(PSTR("Bad last wort!\n")); - need_comms = 1; - return; + printf_P(PSTR("wort sensor is invalid\n")); + uint32_t invalid_time = now.ticks - wort_valid_clock.ticks; + if (invalid_time < WORT_INVALID_TIME) + { + printf("only been invalid for %ld, waiting\n", invalid_time); + return; + } + } + + if (!fridge_valid) + { + printf_P(PSTR("fridge sensor is invalid\n")); } - int16_t wort_delta = last_wort - fridge_setpoint; - uint8_t fridge_on = PORT_FRIDGE & _BV(PIN_FRIDGE); - printf_P(PSTR("last_wort %hd, setpoint %hd, delta %hd, fridge_on %d\n"), - last_wort, fridge_setpoint, wort_delta, fridge_on); if (fridge_on) { - if (last_wort <= fridge_setpoint) + bool turn_off = false; + uint16_t on_time = now.ticks - fridge_on_clock.ticks; + + uint16_t overshoot = 0; + if (on_time > 1800) + { + // *10.0f for decicelcius + overshoot = OVERSHOOT_FACTOR * 10.0f * MIN(3600, on_time) / 3600.0; + } + + printf_P(PSTR("on_time %hu, overshoot %hu\n"), on_time, overshoot); + + // wort has cooled enough. will probably cool a bit more by itself + if (wort_valid) + { + if ((last_wort - overshoot) < fridge_setpoint) + { + printf_P(PSTR("wort has cooled enough, overshoot %hu on_time %hu\n"), overshoot, on_time); + turn_off = true; + } + } + else + { + if (fridge_valid && last_fridge < fridge_min) + { + printf_P(PSTR("fridge off fallback\n")); + turn_off = true; + } + } + + if (turn_off) { // too cold, turn off printf_P(PSTR("Turning fridge off\n")); @@ -846,11 +927,31 @@ } else { - if (wort_delta > fridge_difference) + bool turn_on = false; + + if (wort_valid) + { + if (last_wort >= wort_max) + { + printf_P(PSTR("wort is too hot\n")); + turn_on = true; + } + } + else + { + if (fridge_valid && last_fridge >= fridge_max) + { + printf_P(PSTR("fridge on fallback\n")); + turn_on = true; + } + } + + if (turn_on) { // too hot, turn on printf_P(PSTR("Turning fridge on\n")); PORT_FRIDGE |= _BV(PIN_FRIDGE); + fridge_on_clock = now; } } } @@ -889,11 +990,11 @@ if (memcmp(sensor_id[s], fridge_id, sizeof(fridge_id)) == 0) { - last_fridge = ds18b20_raw16_to_decicelsius(reading);; + last_fridge = ds18b20_raw16_to_decicelsius(reading); } if (memcmp(sensor_id[s], wort_id, sizeof(wort_id)) == 0) { - last_wort = ds18b20_raw16_to_decicelsius(reading);; + last_wort = ds18b20_raw16_to_decicelsius(reading); } } diff -r de950be796dd -r 97aad4471593 simple_ds18b20.c --- a/simple_ds18b20.c Thu Oct 11 20:41:52 2012 +0800 +++ b/simple_ds18b20.c Sun Oct 14 21:51:55 2012 +0800 @@ -80,7 +80,7 @@ decicelsius = -decicelsius; } - if ( /* decicelsius == 850 || */ decicelsius < -550 || decicelsius > 1250 ) { + if ( decicelsius == 850 || decicelsius < -550 || decicelsius > 1250 ) { return DS18X20_INVALID_DECICELSIUS; } else { return decicelsius; diff -r de950be796dd -r 97aad4471593 web/config.py --- a/web/config.py Thu Oct 11 20:41:52 2012 +0800 +++ b/web/config.py Sun Oct 14 21:51:55 2012 +0800 @@ -14,16 +14,20 @@ LINE_WIDTH = 2 -SENSOR_NAMES = {'sensor_28 CE B2 1A 03 00 00 99': "Wort", +SENSOR_NAMES = {'sensor_28 CE B2 1A 03 00 00 99': "Fridge", 'sensor_28 CC C1 1A 03 00 00 D4': "Ambient", - 'sensor_28 49 BC 1A 03 00 00 54': "Other", + 'sensor_28 49 BC 1A 03 00 00 54': "Wort", 'sensor_voltage': 'Voltage', + 'sensor_fridge_setpoint': 'Setpoint', + 'sensor_fridge_on': 'On', } SENSOR_COLOURS = {'Wort': 'e49222', 'Ambient': '028b3d', 'Voltage': '7db5d3aa', - 'Other': '78000c', + 'Fridge': '4c40c8', + 'Setpoint': '39c662', + 'On': 'd7cedd', } diff -r de950be796dd -r 97aad4471593 web/log.py --- a/web/log.py Thu Oct 11 20:41:52 2012 +0800 +++ b/web/log.py Sun Oct 14 21:51:55 2012 +0800 @@ -34,6 +34,11 @@ '--step', '3600', 'DS:temp:GAUGE:7200:1:10', 'RRA:AVERAGE:0.5:1:87600'] + elif 'fridge_on' in sensor_id: + args = [ + '--step', '300', + 'DS:temp:GAUGE:600:-100:500', + 'RRA:LAST:0.5:1:1051200'] else: args = [ '--step', '300', @@ -62,6 +67,7 @@ graph_args = [] have_volts = False for n, (rrdfile, sensor) in enumerate(rrds): + unit = None if 'avrtemp' in sensor: continue if 'voltage' in sensor: @@ -69,6 +75,10 @@ vname = 'scalevolts' graph_args.append('DEF:%(vname)s=%(rrdfile)s:temp:AVERAGE:step=3600' % locals()) unit = 'V' + elif 'fridge_on' in sensor: + vname = 'fridge_on' + graph_args.append('DEF:raw%(vname)s=%(rrdfile)s:temp:LAST' % locals()) + graph_args.append('CDEF:%(vname)s=raw%(vname)s,3,+' % locals()) else: vname = 'temp%d' % n graph_args.append('DEF:raw%(vname)s=%(rrdfile)s:temp:AVERAGE' % locals()) @@ -76,12 +86,20 @@ graph_args.append('CDEF:%(vname)s=raw%(vname)s,35,GT,UNKN,raw%(vname)s,0.1,*,2,+,IF' % locals()) unit = 'ºC' - last_value = float(rrdtool.info(rrdfile)['ds[temp].last_ds']) + format_last_value = None + if unit: + try: + last_value = float(rrdtool.info(rrdfile)['ds[temp].last_ds']) + format_last_value = ('%f' % last_value).rstrip('0.') + unit + except ValueError: + pass width = config.LINE_WIDTH legend = config.SENSOR_NAMES.get(sensor, sensor) colour = config.SENSOR_COLOURS.get(legend, colour_from_string(sensor)) - format_last_value = ('%f' % last_value).rstrip('0') + unit - print_legend = '%s (%s)' % (legend, format_last_value) + if format_last_value: + print_legend = '%s (%s)' % (legend, format_last_value) + else: + print_legend = legend graph_args.append('LINE%(width)f:%(vname)s#%(colour)s:%(print_legend)s' % locals()) end = int(start+length) @@ -128,6 +146,13 @@ #return tempf return tempf.read() +def validate_values(measurements): + for m in measurements: + if m == 85: + yield 'U' + else: + yield '%f' % m + def sensor_update(sensor_id, measurements, first_real_time, time_step): try: open(sensor_rrd_path(sensor_id)) @@ -135,9 +160,9 @@ create_rrd(sensor_id) if measurements: - values = ['%d:%f' % p for p in + values = ['%d:%s' % p for p in zip((first_real_time + time_step*t for t in xrange(len(measurements))), - measurements)] + validate_values(measurements))] rrdfile = sensor_rrd_path(sensor_id) # XXX what to do here when it fails... @@ -224,6 +249,13 @@ voltage = 0.001 * int(entries['voltage']) sensor_update('voltage', [voltage], time.time(), 1) + if 'fridge_status' in entries: + fridge_on = int(entries['fridge_status']) + sensor_update('fridge_on', [fridge_on], time.time(), 1) + + if 'fridge' in entries: + fridge_setpoint = float(entries['fridge']) + sensor_update('fridge_setpoint', [fridge_setpoint], time.time(), 1) #sqlite # - time # - voltage