# HG changeset patch # User Michiel Broek # Date 1712498706 -7200 # Node ID 66fae54fa7baa2a521e1ecfae9c2b19371e7aa34 # Parent 8adbc76fd122287fe4f565b084734028888cfa3b Fermentation processing in it's own function. diff -r 8adbc76fd122 -r 66fae54fa7ba thermferm/server.c --- a/thermferm/server.c Sun Apr 07 11:25:00 2024 +0200 +++ b/thermferm/server.c Sun Apr 07 16:05:06 2024 +0200 @@ -2226,7 +2226,7 @@ * descriptor, s, for that connection. */ s = accept(ls, (struct sockaddr *)&peeraddr_in, &addrlen); - syslog(LOG_NOTICE, "my_server_loop accept socket %d", s); +// syslog(LOG_NOTICE, "my_server_loop accept socket %d", s); if (s == -1) { syslog(LOG_NOTICE, "my_server_loop accept failed %s", strerror(errno)); break; diff -r 8adbc76fd122 -r 66fae54fa7ba thermferm/thermferm.c --- a/thermferm/thermferm.c Sun Apr 07 11:25:00 2024 +0200 +++ b/thermferm/thermferm.c Sun Apr 07 16:05:06 2024 +0200 @@ -36,6 +36,7 @@ #include "xutil.h" #include "pid.h" #include "mqtt.h" +#include "statetbl.h" int my_shutdown = FALSE; @@ -43,6 +44,7 @@ static pid_t pgrp, mypid; int run_pause = FALSE; int run_hold = FALSE; +int row; extern int debug; extern sys_config Config; @@ -1027,20 +1029,638 @@ } +void do_unit(units_list *unit, int LCDunit, int *seconds, int *minutes) +{ + time_t now; + prof_step *step; + int rc, temp; + int run_seconds, run_minutes, run_hours, tot_minutes; + int current_step, valid_step, time_until_now, previous_fridge_mode; + float previous_target_lo, previous_target_hi; + float LCDair, LCDbeer, LCDspL, LCDspH; + unsigned char LCDstatC, LCDstatH; + + unit->mqtt_flag &= ~MQTT_FLAG_DATA; + unit->alarm_flag = 0; + + if (unit->air_address) { + rc = device_in(unit->air_address, &temp); + if (rc == DEVPRESENT_YES) { + if (unit->air_temperature != temp) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + unit->air_temperature = temp; + unit->air_state = 0; + } else if (rc == DEVPRESENT_ERROR) { + unit->air_state = 1; + } else { + unit->air_state = 2; + } + } + + if (unit->beer_address) { + rc = device_in(unit->beer_address, &temp); + if ((rc == DEVPRESENT_NO) && unit->beer_address2) { + /* Read alternative sensor */ + rc = device_in(unit->beer_address2, &temp); + } + if (rc == DEVPRESENT_YES) { + if (unit->beer_temperature != temp) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + unit->beer_temperature = temp; + unit->beer_state = 0; + } else if (rc == DEVPRESENT_ERROR) { + unit->beer_state = 1; + } else { + unit->beer_state = 2; + } + } + + if (unit->chiller_address) { + rc = device_in(unit->chiller_address, &temp); + if (rc == DEVPRESENT_YES) { + if (unit->chiller_temperature != temp) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + unit->chiller_temperature = temp; + unit->chiller_state = 0; + } else if (rc == DEVPRESENT_ERROR) { + unit->chiller_state = 1; + } else { + unit->chiller_state = 2; + } + } + + /* + * Unit door state, default is closed. + */ + if (unit->door_address) { + rc = device_in(unit->door_address, &temp); + if (rc == DEVPRESENT_YES) { + if (temp) { + if (unit->door_state == 0) { + syslog(LOG_NOTICE, "Unit `%s' door closed", unit->alias); + unit->door_state = 1; + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } else { + if (unit->door_state) { + syslog(LOG_NOTICE, "Unit `%s' door opened", unit->alias); + unit->door_state = 0; + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + /* + * If unit is active and the door is open + */ + if (unit->mode != UNITMODE_NONE) { + unit->alarm_flag |= ALARM_FLAG_DOOR; + } + } + } else { + unit->door_state = 1; + } + } else { + unit->door_state = 1; + } + + /* + * Unit PSU state + */ + if (unit->psu_address) { + rc = device_in(unit->psu_address, &temp); + if (rc == DEVPRESENT_YES) { + if (temp) { + if (unit->psu_state == 0) { + syslog(LOG_NOTICE, "Unit `%s' PSU (12 volt) is on", unit->alias); + unit->psu_state = 1; + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } else { + if (unit->psu_state) { + syslog(LOG_NOTICE, "Unit `%s' PSU (12 volt) is off", unit->alias); + unit->psu_state = 0; + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + unit->alarm_flag |= ALARM_FLAG_PSU; + } + } else { + unit->psu_state = 1; + } + } else { + /* + * No state available, assume Ok. + */ + unit->psu_state = 1; + } + + /* + * Handle profile + */ + if ((unit->mode == UNITMODE_PROFILE) && (unit->profile_uuid)) { + /* + * unit->prof_started - start time or 0 if not yet running. + * unit->prof_state - PROFILE_OFF|PROFILE_PAUSE|PROFILE_RUN|PROFILE_DONE + * unit->prof_target - Calculated target temperature. + * unit->prof_paused - Internal pause counter. + * unit->prof_peak_abs - Peak temperature of the beer. + * unit->prof_peak_rel - Peak temperature between beer and fridge. + * unit->prof_primary_done - time when primary fermentation was over the peak. + */ + + /* + * Safe defaults + */ + unit->prof_target_lo = unit->profile_inittemp_lo; + unit->prof_target_hi = unit->profile_inittemp_hi; + unit->prof_fridge_mode = 0; + + switch (unit->prof_state) { + case PROFILE_OFF: + unit->prof_percent = 0; + break; + case PROFILE_PAUSE: + /* + * Keep current temperature, measure pause time. For + * temperature fall thru. + */ + unit->prof_paused++; + case PROFILE_RUN: + /* + * Calculate current profile step and desired temperature. + * When all steps are done, set state to PROFILE_DONE. + */ + previous_target_lo = unit->profile_inittemp_lo; + previous_target_hi = unit->profile_inittemp_hi; + previous_fridge_mode = unit->profile_fridge_mode; + time_until_now = current_step = 0; + now = time(NULL); + run_seconds = (int)(now - unit->prof_started - unit->prof_paused); + run_minutes = run_seconds / 60; + run_hours = run_minutes / 60; + if (debug) + fprintf(stdout, "run_HMS=%d,%d,%d ", run_hours, run_minutes, run_seconds); + + /* + * Primary fermentation tests + */ + if ((unit->beer_temperature / 1000.0) > unit->prof_peak_abs) + unit->prof_peak_abs = unit->beer_temperature / 1000.0; + if (((unit->beer_temperature - unit->air_temperature) / 1000.0) > unit->prof_peak_rel) + unit->prof_peak_rel = (unit->beer_temperature - unit->air_temperature) / 1000.0; + if (unit->prof_primary_done == 0) { + if (unit->cooler_address) { + /* + * There is a cooler. If the difference between the beer and air temperature + * drops we assume the primary fermentation is done. + */ + if (((unit->beer_temperature - unit->air_temperature) / 1000.0) < (unit->prof_peak_rel - 0.5)) { + unit->prof_primary_done = time(NULL); + syslog(LOG_NOTICE, "Profile `%s' primary fermentation is ready (cooler mode)", unit->profile_name); + if (! unit->event_msg) + unit->event_msg = xstrcpy((char *)"Primary peak"); + } + } else { + /* + * This method works if the unit has no cooling or if the profile allowed the + * beer temperature to rise freely. + */ + if ((unit->beer_temperature / 1000.0) < (unit->prof_peak_abs - 0.5)) { + unit->prof_primary_done = time(NULL); + syslog(LOG_NOTICE, "Profile `%s' primary fermentation is ready (free rise mode)", unit->profile_name); + if (! unit->event_msg) + unit->event_msg = xstrcpy((char *)"Primary peak"); + } + } + } + + /* + * See how long this profile will take + */ + tot_minutes = 0; + for (step = unit->profile_steps; step; step = step->next) { + tot_minutes += ((step->steptime + step->resttime) * 60); + } + if ((tot_minutes == 0) && unit->profile_totalsteps) { + syslog(LOG_NOTICE, "Profile `%s' steps disappeared", unit->profile_name); + unit->prof_state = PROFILE_OFF; + break; + } + + valid_step = FALSE; + for (step = unit->profile_steps; step; step = step->next) { + /* + * step->steptime + * step->resttime + * step->target + */ + current_step++; + if ((run_hours >= time_until_now) && (run_hours < (time_until_now + step->steptime + step->resttime))) { + /* + * This is our current step + */ + valid_step = TRUE; + if ((run_hours - time_until_now) < step->steptime) { + unit->prof_target_lo = previous_target_lo + (((run_minutes - (time_until_now * 60.0)) / (step->steptime * 60.0)) * (step->target_lo - previous_target_lo)); + unit->prof_target_hi = previous_target_hi + (((run_minutes - (time_until_now * 60.0)) / (step->steptime * 60.0)) * (step->target_hi - previous_target_hi)); + if (step->fridge_mode > previous_fridge_mode) { + unit->prof_fridge_mode = (((run_minutes - (time_until_now * 60)) * 100) / (step->steptime * 60)); + } else if (step->fridge_mode < previous_fridge_mode) { + unit->prof_fridge_mode = 100 - (((run_minutes - (time_until_now * 60)) * 100) / (step->steptime * 60)); + } else { + unit->prof_fridge_mode = step->fridge_mode; + } + if (debug) + fprintf(stdout, "prof_fridge_mode=%d run_minutes=%d steptime=%d time_until_now=%d\n", + unit->prof_fridge_mode, run_minutes, step->steptime, time_until_now); + } else { + unit->prof_target_lo = step->target_lo; + unit->prof_target_hi = step->target_hi; + unit->prof_fridge_mode = step->fridge_mode; + } + break; + } + time_until_now += step->steptime + step->resttime; + previous_target_lo = step->target_lo; + previous_target_hi = step->target_hi; + previous_fridge_mode = step->fridge_mode; + } + + if (valid_step == TRUE) { + unit->prof_percent = (100 * run_minutes) / tot_minutes; + if (((*minutes == 10) || (*minutes == 40)) && (*seconds == 1)) { + syslog(LOG_NOTICE, "Profile `%s' running %dd %02d:%02d in step %d, %d%% done, fridge/beer %d%% %.3f..%.3f degrees", + unit->profile_name, run_hours / 24, run_hours % 24, run_minutes % 60, current_step, + unit->prof_percent, unit->prof_fridge_mode, unit->prof_target_lo, unit->prof_target_hi); + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } else { + /* + * No more steps to do + */ + unit->prof_state = PROFILE_DONE; + unit->prof_percent = 100; + syslog(LOG_NOTICE, "Profile `%s' is done", unit->profile_name); + unit->mqtt_flag |= MQTT_FLAG_DATA; + if (! unit->event_msg) + unit->event_msg = xstrcpy((char *)"Profile finished"); + } + break; + + case PROFILE_DONE: + /* + * Keep this state, set target temperature to the last step. + */ + previous_target_lo = unit->profile_inittemp_lo; + previous_target_hi = unit->profile_inittemp_hi; + previous_fridge_mode = unit->profile_fridge_mode; + for (step = unit->profile_steps; step; step = step->next) { + if ((step->steptime + step->resttime) == 0) + break; + previous_target_lo = step->target_lo; + previous_target_hi = step->target_hi; + previous_fridge_mode = step->fridge_mode; + } + unit->prof_target_lo = previous_target_lo; + unit->prof_target_hi = previous_target_hi; + unit->prof_fridge_mode = previous_fridge_mode; + unit->prof_percent = 100; + break; + } /* switch */ + } else { + /* + * Set some sane values + */ + unit->prof_target_lo = 19.8; + unit->prof_target_hi = 20.2; + unit->prof_fridge_mode = 0; + } + + /* + * Manual switching + */ + if (unit->mode == UNITMODE_NONE) { + device_out(unit->heater_address, unit->heater_state); + device_out(unit->cooler_address, unit->cooler_state); + device_out(unit->fan_address, unit->fan_state); + } + + /* + * Usage counters + */ + if (unit->heater_address && unit->heater_state) + unit->heater_usage++; + if (unit->cooler_address && unit->cooler_state) + unit->cooler_usage++; + if (unit->fan_address && unit->fan_state) + unit->fan_usage++; + if (unit->light_address && unit->light_state) + unit->light_usage++; + + /* + * Interior lights + */ + if (unit->light_address) { + if (unit->light_timer) { + unit->light_timer--; + } + if (unit->door_state && !unit->light_timer && unit->light_state) { + if (unit->light_wait > 0) { + unit->light_wait--; + } else { + unit->light_state = 0; + syslog(LOG_NOTICE, "Unit `%s' lights On => Off", unit->alias); + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } + if ((!unit->door_state || unit->light_timer) && !unit->light_state) { + unit->light_wait = unit->light_delay; /* No delay to turn lights on */ + unit->light_state = 1; + unit->mqtt_flag |= MQTT_FLAG_DATA; + syslog(LOG_NOTICE, "Unit `%s' lights Off => On", unit->alias); + } + device_out(unit->light_address, unit->light_state); + } + + /* + * Temperature control in this unit + */ + if ((unit->mode == UNITMODE_FRIDGE) || (unit->mode == UNITMODE_BEER) || (unit->mode == UNITMODE_PROFILE)) { + + /* + * Set both PID's to their input values. + */ + unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; + if (unit->mode == UNITMODE_FRIDGE) { + unit->PID_cool->SetP = unit->fridge_set_hi; + unit->PID_heat->SetP = unit->fridge_set_lo; + unit->PID_cool->Input = unit->PID_heat->Input = unit->air_temperature / 1000.0; + unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_BOO; + } else if (unit->mode == UNITMODE_BEER) { + unit->PID_cool->SetP = unit->beer_set_hi; + unit->PID_heat->SetP = unit->beer_set_lo; + unit->PID_cool->Input = unit->PID_heat->Input = unit->beer_temperature / 1000.0; + unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_AUTO; + } else if (unit->mode == UNITMODE_PROFILE) { + double usetemp; + unit->PID_cool->SetP = unit->prof_target_hi; + unit->PID_heat->SetP = unit->prof_target_lo; + /* + * Get percentage to use from each thermometer. unit->prof_fridge_mode = 0..100 + */ + usetemp = ((unit->prof_fridge_mode * (unit->air_temperature / 1000.0)) + + ((100 - unit->prof_fridge_mode) * (unit->beer_temperature / 1000.0))) / 100.0; + unit->PID_cool->Input = unit->PID_heat->Input = usetemp; + unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_AUTO; + } + + /* + * PID controller compute, simulate 100 mSec loops by running 10 times. + */ + for (int i = 0; i < 10; i++) { + UpdatePID(unit->PID_heat); + UpdatePID(unit->PID_cool); + } + /* + * Logging + */ + if (unit->heater_address) { + /* + * Prevent extreme heating + */ + if ((unit->mode == UNITMODE_BEER) && ((unit->air_temperature / 1000.0) > (unit->PID_heat->Input + 8.0))) { + unit->PID_heat->OutP = 0.0; + } + if (*seconds == 60) { + syslog(LOG_NOTICE, "Heat: sp=%.3f Input=%.3f iState=%.3f Err=%.3f Out=%.1f", + unit->PID_heat->SetP, unit->PID_heat->Input, unit->PID_heat->iState, unit->PID_heat->Err, unit->PID_heat->OutP); + } + } else { + unit->PID_heat->OutP = 0.0; + } + if (unit->cooler_address) { + /* + * Prevent extreme cooling + */ + if ((unit->mode == UNITMODE_BEER) && ((unit->air_temperature / 1000.0) < (unit->PID_cool->Input - 8.0))) { + unit->PID_cool->OutP = 0.0; + } + /* + * Prevent cooling if we use a chiller and the chiller temperature is not low enough. + */ + if (unit->chiller_address && (unit->chiller_state == 0)) { + if ((unit->chiller_temperature / 1000.0) > ((unit->air_temperature / 1000.0) - 1)) { + unit->PID_cool->OutP = 0.0; + unit->alarm_flag |= ALARM_FLAG_CHILLER; + if (*seconds == 60) { + syslog(LOG_NOTICE, "Cool: Air=%.2f Chiller=%.2f alarm", unit->air_temperature / 1000.0, unit->chiller_temperature / 1000.0); + } + } + } + if (*seconds == 60) { + syslog(LOG_NOTICE, "Cool: sp=%.3f Input=%.3f iState=%.3f Err=%.3f Out=%.1f", + unit->PID_cool->SetP, unit->PID_cool->Input, unit->PID_cool->iState, unit->PID_cool->Err, unit->PID_cool->OutP); + } + } else { + unit->PID_cool->OutP = 0.0; + } + + /* + * Deadlock, kill lowest value. + */ + if (unit->PID_cool->OutP && unit->PID_heat->OutP) { + if (unit->PID_cool->OutP > unit->PID_heat->OutP) + unit->PID_heat->OutP = 0.0; + else + unit->PID_cool->OutP = 0.0; + } + + if (unit->heater_address && ! unit->cooler_state) { + if (unit->PID_heat->OutP >= 50) { + if (unit->heater_wait < unit->heater_delay) { + unit->heater_wait++; + } else { + int power = round(unit->PID_heat->OutP); + if (unit->heater_state != power) { + syslog(LOG_NOTICE, "Unit `%s' heater %d%% => %d%%", unit->alias, unit->heater_state, power); + unit->heater_state = power; + if (unit->heater_address) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } + } + } else { + if (unit->heater_wait > 0) { + unit->heater_wait--; + } else { + if (unit->heater_state) { + syslog(LOG_NOTICE, "Unit `%s' heater On => Off", unit->alias); + unit->heater_state = 0; + if (unit->heater_address) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } + } + } + if (unit->door_state) { + device_out(unit->heater_address, unit->heater_state); + } else { + device_out(unit->heater_address, 0); + } + } + + if (unit->cooler_address && ! unit->heater_state) { + if (unit->PID_cool->OutP >= 50) { + if (unit->cooler_wait < unit->cooler_delay) { + unit->cooler_wait++; + } else { + int power = round(unit->PID_cool->OutP); + if (unit->cooler_state != power) { + syslog(LOG_NOTICE, "Unit `%s' cooler %d%% => %d%%", unit->alias, unit->cooler_state, power); + unit->cooler_state = power; + if (unit->cooler_address) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } + } + } else { + if (unit->cooler_wait > 0) { + unit->cooler_wait--; + } else { + if (unit->cooler_state) { + syslog(LOG_NOTICE, "Unit `%s' cooler On => Off", unit->alias); + unit->cooler_state = 0; + if (unit->cooler_address) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } + } + } + if (unit->door_state) { + device_out(unit->cooler_address, unit->cooler_state); + } else { + device_out(unit->cooler_address, 0); + } + } + + /* + * If there is a fan, and the unit door is closed, and the unit should be doing + * something, then turn on the global fan. + * But if there is a chiller, do not turn it on if cooling. + */ + if (unit->fan_address) { + if ((unit->door_state) && (unit->cooler_state == 0)) { + if (unit->fan_wait < unit->fan_delay) { + unit->fan_wait++; + } else { + if (! unit->fan_state) { + syslog(LOG_NOTICE, "Unit `%s' Fan Off => On", unit->alias); + unit->fan_state = 100; + if (unit->fan_address) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } + } + } else { + if (unit->fan_wait > 0) { + unit->fan_wait--; + } else { + if (unit->fan_state) { + syslog(LOG_NOTICE, "Unit `%s' Fan On => Off", unit->alias); + unit->fan_state = 0; + if (unit->fan_address) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + } + } + } + device_out(unit->fan_address, unit->fan_state); + } + + } else { + unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; + } /* fridge beer or profile mode */ + + /* + * Now everything is set and done, update the LCD display + */ + LCDair = unit->air_temperature / 1000.0; + LCDbeer = unit->beer_temperature / 1000.0; + LCDstatC = LCDstatH = ' '; + if (unit->heater_address) { + if (unit->heater_state) + LCDstatH = '\6'; + else + LCDstatH = '\5'; + } + if (unit->cooler_address) { + if (unit->cooler_state) + LCDstatC = '\4'; + else + LCDstatC = '\3'; + } + LCDspH = LCDspL = 0.0; + if (unit->mode == UNITMODE_BEER) { + LCDspH = unit->beer_set_hi; + LCDspL = unit->beer_set_lo; + } else if (unit->mode == UNITMODE_FRIDGE) { + LCDspH = unit->fridge_set_hi; + LCDspL = unit->fridge_set_lo; + } else if (unit->mode == UNITMODE_PROFILE) { + if (unit->prof_state != PROFILE_OFF) { + LCDspL = unit->prof_target_lo; + LCDspH = unit->prof_target_hi; + } + } + if (*seconds == 60) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + } + pthread_mutex_lock(&mutexes[LOCK_LCD]); + /* + * Write 4 rows to the LCD to display the unit state + */ + lcd_buf_write(row++, "Unit %d: %s ", LCDunit, UNITMODE[unit->mode]); + lcd_buf_write(row++, "%s ", unit->product_name); + lcd_buf_write(row++, "%c%5.1f\2 A%6.2f\1 ", LCDstatC, LCDspH, LCDair); + lcd_buf_write(row++, "%c%5.1f\2 B%6.2f\1 ", LCDstatH, LCDspL, LCDbeer); + pthread_mutex_unlock(&mutexes[LOCK_LCD]); + + /* + * Publish MQTT messages set in flag + */ + if (unit->mqtt_flag) { + if (unit->mqtt_flag & MQTT_FLAG_BIRTH) { + publishDBirth(unit); + unit->mqtt_flag &= ~MQTT_FLAG_BIRTH; + } else { + publishDData(unit); + unit->mqtt_flag &= ~MQTT_FLAG_DATA; + } + if (unit->mqtt_flag & MQTT_FLAG_DEATH) { + publishDDeath(unit); + unit->mqtt_flag &= ~MQTT_FLAG_DEATH; + } + } + + /* + * Handle changed alarms + */ + if (unit->alarm_flag != unit->alarm_last) { + syslog(LOG_NOTICE, "Unit `%s' Alarm %d => %d", unit->alias, unit->alarm_last, unit->alarm_flag); + unit->alarm_last = unit->alarm_flag; + } +} + + int server(void) { time_t now, last = (time_t)0, ndata = (time_t)0;; units_list *unit; - prof_step *step; - int row, rc, run = 1, seconds = 0, minutes = 0, temp; - int run_seconds, run_minutes, run_hours, tot_minutes, key; + int rc, run = 1, seconds = 0, minutes = 0, temp; + int key; struct tm *tm; long t = 0; - int current_step, valid_step, time_until_now, previous_fridge_mode; - float previous_target_lo, previous_target_hi; - float LCDair, LCDbeer, LCDspL, LCDspH; - unsigned char LCDstatC, LCDstatH; int LCDunit; syslog(LOG_NOTICE, "Server process started"); @@ -1245,618 +1865,8 @@ LCDunit = 0; for (unit = Config.units; unit; unit = unit->next) { LCDunit++; - unit->mqtt_flag &= ~MQTT_FLAG_DATA; - unit->alarm_flag = 0; - - if (unit->air_address) { - rc = device_in(unit->air_address, &temp); - if (rc == DEVPRESENT_YES) { - if (unit->air_temperature != temp) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - unit->air_temperature = temp; - unit->air_state = 0; - } else if (rc == DEVPRESENT_ERROR) { - unit->air_state = 1; - } else { - unit->air_state = 2; - } - } - - if (unit->beer_address) { - rc = device_in(unit->beer_address, &temp); - if ((rc == DEVPRESENT_NO) && unit->beer_address2) { - /* Read alternative sensor */ - rc = device_in(unit->beer_address2, &temp); - } - if (rc == DEVPRESENT_YES) { - if (unit->beer_temperature != temp) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - unit->beer_temperature = temp; - unit->beer_state = 0; - } else if (rc == DEVPRESENT_ERROR) { - unit->beer_state = 1; - } else { - unit->beer_state = 2; - } - } - - if (unit->chiller_address) { - rc = device_in(unit->chiller_address, &temp); - if (rc == DEVPRESENT_YES) { - if (unit->chiller_temperature != temp) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - unit->chiller_temperature = temp; - unit->chiller_state = 0; - } else if (rc == DEVPRESENT_ERROR) { - unit->chiller_state = 1; - } else { - unit->chiller_state = 2; - } - } - - /* - * Unit door state, default is closed. - */ - if (unit->door_address) { - rc = device_in(unit->door_address, &temp); - if (rc == DEVPRESENT_YES) { - if (temp) { - if (unit->door_state == 0) { - syslog(LOG_NOTICE, "Unit `%s' door closed", unit->alias); - unit->door_state = 1; - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } else { - if (unit->door_state) { - syslog(LOG_NOTICE, "Unit `%s' door opened", unit->alias); - unit->door_state = 0; - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - /* - * If unit is active and the door is open - */ - if (unit->mode != UNITMODE_NONE) { - unit->alarm_flag |= ALARM_FLAG_DOOR; - } - } - } else { - unit->door_state = 1; - } - } else { - unit->door_state = 1; - } - - /* - * Unit PSU state - */ - if (unit->psu_address) { - rc = device_in(unit->psu_address, &temp); - if (rc == DEVPRESENT_YES) { - if (temp) { - if (unit->psu_state == 0) { - syslog(LOG_NOTICE, "Unit `%s' PSU (12 volt) is on", unit->alias); - unit->psu_state = 1; - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } else { - if (unit->psu_state) { - syslog(LOG_NOTICE, "Unit `%s' PSU (12 volt) is off", unit->alias); - unit->psu_state = 0; - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - unit->alarm_flag |= ALARM_FLAG_PSU; - } - } else { - unit->psu_state = 1; - } - } else { - /* - * No state available, assume Ok. - */ - unit->psu_state = 1; - } - - /* - * Handle profile - */ - if ((unit->mode == UNITMODE_PROFILE) && (unit->profile_uuid)) { - /* - * unit->prof_started - start time or 0 if not yet running. - * unit->prof_state - PROFILE_OFF|PROFILE_PAUSE|PROFILE_RUN|PROFILE_DONE - * unit->prof_target - Calculated target temperature. - * unit->prof_paused - Internal pause counter. - * unit->prof_peak_abs - Peak temperature of the beer. - * unit->prof_peak_rel - Peak temperature between beer and fridge. - * unit->prof_primary_done - time when primary fermentation was over the peak. - */ - - /* - * Safe defaults - */ - unit->prof_target_lo = unit->profile_inittemp_lo; - unit->prof_target_hi = unit->profile_inittemp_hi; - unit->prof_fridge_mode = 0; - - switch (unit->prof_state) { - case PROFILE_OFF: - unit->prof_percent = 0; - break; - case PROFILE_PAUSE: - /* - * Keep current temperature, measure pause time. For - * temperature fall thru. - */ - unit->prof_paused++; - case PROFILE_RUN: - /* - * Calculate current profile step and desired temperature. - * When all steps are done, set state to PROFILE_DONE. - */ - previous_target_lo = unit->profile_inittemp_lo; - previous_target_hi = unit->profile_inittemp_hi; - previous_fridge_mode = unit->profile_fridge_mode; - time_until_now = current_step = 0; - run_seconds = (int)(now - unit->prof_started - unit->prof_paused); - run_minutes = run_seconds / 60; - run_hours = run_minutes / 60; - if (debug) - fprintf(stdout, "run_HMS=%d,%d,%d ", run_hours, run_minutes, run_seconds); - - /* - * Primary fermentation tests - */ - if ((unit->beer_temperature / 1000.0) > unit->prof_peak_abs) - unit->prof_peak_abs = unit->beer_temperature / 1000.0; - if (((unit->beer_temperature - unit->air_temperature) / 1000.0) > unit->prof_peak_rel) - unit->prof_peak_rel = (unit->beer_temperature - unit->air_temperature) / 1000.0; - if (unit->prof_primary_done == 0) { - if (unit->cooler_address) { - /* - * There is a cooler. If the difference between the beer and air temperature - * drops we assume the primary fermentation is done. - */ - if (((unit->beer_temperature - unit->air_temperature) / 1000.0) < (unit->prof_peak_rel - 0.5)) { - unit->prof_primary_done = time(NULL); - syslog(LOG_NOTICE, "Profile `%s' primary fermentation is ready (cooler mode)", unit->profile_name); - if (! unit->event_msg) - unit->event_msg = xstrcpy((char *)"Primary peak"); - } - } else { - /* - * This method works if the unit has no cooling or if the profile allowed the - * beer temperature to rise freely. - */ - if ((unit->beer_temperature / 1000.0) < (unit->prof_peak_abs - 0.5)) { - unit->prof_primary_done = time(NULL); - syslog(LOG_NOTICE, "Profile `%s' primary fermentation is ready (free rise mode)", unit->profile_name); - if (! unit->event_msg) - unit->event_msg = xstrcpy((char *)"Primary peak"); - } - } - } - - /* - * See how long this profile will take - */ - tot_minutes = 0; - for (step = unit->profile_steps; step; step = step->next) { - tot_minutes += ((step->steptime + step->resttime) * 60); - } - if ((tot_minutes == 0) && unit->profile_totalsteps) { - syslog(LOG_NOTICE, "Profile `%s' steps disappeared", unit->profile_name); - unit->prof_state = PROFILE_OFF; - break; - } - - valid_step = FALSE; - for (step = unit->profile_steps; step; step = step->next) { - /* - * step->steptime - * step->resttime - * step->target - */ - current_step++; - if ((run_hours >= time_until_now) && (run_hours < (time_until_now + step->steptime + step->resttime))) { - /* - * This is our current step - */ - valid_step = TRUE; - if ((run_hours - time_until_now) < step->steptime) { - unit->prof_target_lo = previous_target_lo + (((run_minutes - (time_until_now * 60.0)) / (step->steptime * 60.0)) * (step->target_lo - previous_target_lo)); - unit->prof_target_hi = previous_target_hi + (((run_minutes - (time_until_now * 60.0)) / (step->steptime * 60.0)) * (step->target_hi - previous_target_hi)); - if (step->fridge_mode > previous_fridge_mode) { - unit->prof_fridge_mode = (((run_minutes - (time_until_now * 60)) * 100) / (step->steptime * 60)); - } else if (step->fridge_mode < previous_fridge_mode) { - unit->prof_fridge_mode = 100 - (((run_minutes - (time_until_now * 60)) * 100) / (step->steptime * 60)); - } else { - unit->prof_fridge_mode = step->fridge_mode; - } - if (debug) - fprintf(stdout, "prof_fridge_mode=%d run_minutes=%d steptime=%d time_until_now=%d\n", - unit->prof_fridge_mode, run_minutes, step->steptime, time_until_now); - } else { - unit->prof_target_lo = step->target_lo; - unit->prof_target_hi = step->target_hi; - unit->prof_fridge_mode = step->fridge_mode; - } - break; - } - time_until_now += step->steptime + step->resttime; - previous_target_lo = step->target_lo; - previous_target_hi = step->target_hi; - previous_fridge_mode = step->fridge_mode; - } - - if (valid_step == TRUE) { - unit->prof_percent = (100 * run_minutes) / tot_minutes; - if (((minutes == 10) || (minutes == 40)) && (seconds == 1)) { - syslog(LOG_NOTICE, "Profile `%s' running %dd %02d:%02d in step %d, %d%% done, fridge/beer %d%% %.3f..%.3f degrees", - unit->profile_name, run_hours / 24, run_hours % 24, run_minutes % 60, current_step, - unit->prof_percent, unit->prof_fridge_mode, unit->prof_target_lo, unit->prof_target_hi); - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } else { - /* - * No more steps to do - */ - unit->prof_state = PROFILE_DONE; - unit->prof_percent = 100; - syslog(LOG_NOTICE, "Profile `%s' is done", unit->profile_name); - unit->mqtt_flag |= MQTT_FLAG_DATA; - if (! unit->event_msg) - unit->event_msg = xstrcpy((char *)"Profile finished"); - } - break; - - case PROFILE_DONE: - /* - * Keep this state, set target temperature to the last step. - */ - previous_target_lo = unit->profile_inittemp_lo; - previous_target_hi = unit->profile_inittemp_hi; - previous_fridge_mode = unit->profile_fridge_mode; - for (step = unit->profile_steps; step; step = step->next) { - if ((step->steptime + step->resttime) == 0) - break; - previous_target_lo = step->target_lo; - previous_target_hi = step->target_hi; - previous_fridge_mode = step->fridge_mode; - } - unit->prof_target_lo = previous_target_lo; - unit->prof_target_hi = previous_target_hi; - unit->prof_fridge_mode = previous_fridge_mode; - unit->prof_percent = 100; - break; - } /* switch */ - } else { - /* - * Set some sane values - */ - unit->prof_target_lo = 19.8; - unit->prof_target_hi = 20.2; - unit->prof_fridge_mode = 0; - } - - /* - * Manual switching - */ - if (unit->mode == UNITMODE_NONE) { - device_out(unit->heater_address, unit->heater_state); - device_out(unit->cooler_address, unit->cooler_state); - device_out(unit->fan_address, unit->fan_state); - } + do_unit(unit, LCDunit, &seconds, &minutes); - /* - * Usage counters - */ - if (unit->heater_address && unit->heater_state) - unit->heater_usage++; - if (unit->cooler_address && unit->cooler_state) - unit->cooler_usage++; - if (unit->fan_address && unit->fan_state) - unit->fan_usage++; - if (unit->light_address && unit->light_state) - unit->light_usage++; - - /* - * Interior lights - */ - if (unit->light_address) { - if (unit->light_timer) { - unit->light_timer--; - } - if (unit->door_state && !unit->light_timer && unit->light_state) { - if (unit->light_wait > 0) { - unit->light_wait--; - } else { - unit->light_state = 0; - syslog(LOG_NOTICE, "Unit `%s' lights On => Off", unit->alias); - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } - if ((!unit->door_state || unit->light_timer) && !unit->light_state) { - unit->light_wait = unit->light_delay; /* No delay to turn lights on */ - unit->light_state = 1; - unit->mqtt_flag |= MQTT_FLAG_DATA; - syslog(LOG_NOTICE, "Unit `%s' lights Off => On", unit->alias); - } - device_out(unit->light_address, unit->light_state); - } - - /* - * Temperature control in this unit - */ - if ((unit->mode == UNITMODE_FRIDGE) || (unit->mode == UNITMODE_BEER) || (unit->mode == UNITMODE_PROFILE)) { - - /* - * Set both PID's to their input values. - */ - unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; - if (unit->mode == UNITMODE_FRIDGE) { - unit->PID_cool->SetP = unit->fridge_set_hi; - unit->PID_heat->SetP = unit->fridge_set_lo; - unit->PID_cool->Input = unit->PID_heat->Input = unit->air_temperature / 1000.0; - unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_BOO; - } else if (unit->mode == UNITMODE_BEER) { - unit->PID_cool->SetP = unit->beer_set_hi; - unit->PID_heat->SetP = unit->beer_set_lo; - unit->PID_cool->Input = unit->PID_heat->Input = unit->beer_temperature / 1000.0; - unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_AUTO; - } else if (unit->mode == UNITMODE_PROFILE) { - double usetemp; - unit->PID_cool->SetP = unit->prof_target_hi; - unit->PID_heat->SetP = unit->prof_target_lo; - /* - * Get percentage to use from each thermometer. unit->prof_fridge_mode = 0..100 - */ - usetemp = ((unit->prof_fridge_mode * (unit->air_temperature / 1000.0)) + - ((100 - unit->prof_fridge_mode) * (unit->beer_temperature / 1000.0))) / 100.0; - unit->PID_cool->Input = unit->PID_heat->Input = usetemp; - unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_AUTO; - } - - /* - * PID controller compute, simulate 100 mSec loops. - */ - for (int i = 0; i < 10; i++) { - UpdatePID(unit->PID_heat); - UpdatePID(unit->PID_cool); - } - - /* - * Logging - */ - if (unit->heater_address) { - /* - * Prevent extreme heating - */ - if ((unit->mode == UNITMODE_BEER) && ((unit->air_temperature / 1000.0) > (unit->PID_heat->Input + 8.0))) { - unit->PID_heat->OutP = 0.0; - } - if (seconds == 60) { - syslog(LOG_NOTICE, "Heat: sp=%.3f Input=%.3f iState=%.3f Err=%.3f Out=%.1f", - unit->PID_heat->SetP, unit->PID_heat->Input, unit->PID_heat->iState, unit->PID_heat->Err, unit->PID_heat->OutP); - } - } else { - unit->PID_heat->OutP = 0.0; - } - if (unit->cooler_address) { - /* - * Prevent extreme cooling - */ - if ((unit->mode == UNITMODE_BEER) && ((unit->air_temperature / 1000.0) < (unit->PID_cool->Input - 8.0))) { - unit->PID_cool->OutP = 0.0; - } - /* - * Prevent cooling if we use a chiller and the chiller temperature is not low enough. - */ - if (unit->chiller_address && (unit->chiller_state == 0)) { - if ((unit->chiller_temperature / 1000.0) > ((unit->air_temperature / 1000.0) - 1)) { - unit->PID_cool->OutP = 0.0; - unit->alarm_flag |= ALARM_FLAG_CHILLER; - if (seconds == 60) { - syslog(LOG_NOTICE, "Cool: Air=%.2f Chiller=%.2f alarm", unit->air_temperature / 1000.0, unit->chiller_temperature / 1000.0); - } - } - } - if (seconds == 60) { - syslog(LOG_NOTICE, "Cool: sp=%.3f Input=%.3f iState=%.3f Err=%.3f Out=%.1f", - unit->PID_cool->SetP, unit->PID_cool->Input, unit->PID_cool->iState, unit->PID_cool->Err, unit->PID_cool->OutP); - } - } else { - unit->PID_cool->OutP = 0.0; - } - - /* - * Deadlock, kill lowest value. - */ - if (unit->PID_cool->OutP && unit->PID_heat->OutP) { - if (unit->PID_cool->OutP > unit->PID_heat->OutP) - unit->PID_heat->OutP = 0.0; - else - unit->PID_cool->OutP = 0.0; - } - - if (unit->heater_address && ! unit->cooler_state) { - if (unit->PID_heat->OutP >= 50) { - if (unit->heater_wait < unit->heater_delay) { - unit->heater_wait++; - } else { - int power = round(unit->PID_heat->OutP); - if (unit->heater_state != power) { - syslog(LOG_NOTICE, "Unit `%s' heater %d%% => %d%%", unit->alias, unit->heater_state, power); - unit->heater_state = power; - if (unit->heater_address) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } - } - } else { - if (unit->heater_wait > 0) { - unit->heater_wait--; - } else { - if (unit->heater_state) { - syslog(LOG_NOTICE, "Unit `%s' heater On => Off", unit->alias); - unit->heater_state = 0; - if (unit->heater_address) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } - } - } - if (unit->door_state) { - device_out(unit->heater_address, unit->heater_state); - } else { - device_out(unit->heater_address, 0); - } - } - - if (unit->cooler_address && ! unit->heater_state) { - if (unit->PID_cool->OutP >= 50) { - if (unit->cooler_wait < unit->cooler_delay) { - unit->cooler_wait++; - } else { - int power = round(unit->PID_cool->OutP); - if (unit->cooler_state != power) { - syslog(LOG_NOTICE, "Unit `%s' cooler %d%% => %d%%", unit->alias, unit->cooler_state, power); - unit->cooler_state = power; - if (unit->cooler_address) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } - } - } else { - if (unit->cooler_wait > 0) { - unit->cooler_wait--; - } else { - if (unit->cooler_state) { - syslog(LOG_NOTICE, "Unit `%s' cooler On => Off", unit->alias); - unit->cooler_state = 0; - if (unit->cooler_address) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } - } - } - if (unit->door_state) { - device_out(unit->cooler_address, unit->cooler_state); - } else { - device_out(unit->cooler_address, 0); - } - } - - /* - * If there is a fan, and the unit door is closed, and the unit should be doing - * something, then turn on the global fan. - * But if there is a chiller, do not turn it on if cooling. - */ - if (unit->fan_address) { - if ((unit->door_state) && (unit->cooler_state == 0)) { - if (unit->fan_wait < unit->fan_delay) { - unit->fan_wait++; - } else { - if (! unit->fan_state) { - syslog(LOG_NOTICE, "Unit `%s' Fan Off => On", unit->alias); - unit->fan_state = 100; - if (unit->fan_address) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } - } - } else { - if (unit->fan_wait > 0) { - unit->fan_wait--; - } else { - if (unit->fan_state) { - syslog(LOG_NOTICE, "Unit `%s' Fan On => Off", unit->alias); - unit->fan_state = 0; - if (unit->fan_address) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - } - } - } - device_out(unit->fan_address, unit->fan_state); - } - - } else { - unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; - } /* fridge beer or profile mode */ - - /* - * Now everything is set and done, update the LCD display - */ - LCDair = unit->air_temperature / 1000.0; - LCDbeer = unit->beer_temperature / 1000.0; - LCDstatC = LCDstatH = ' '; - if (unit->heater_address) { - if (unit->heater_state) - LCDstatH = '\6'; - else - LCDstatH = '\5'; - } - if (unit->cooler_address) { - if (unit->cooler_state) - LCDstatC = '\4'; - else - LCDstatC = '\3'; - } - LCDspH = LCDspL = 0.0; - if (unit->mode == UNITMODE_BEER) { - LCDspH = unit->beer_set_hi; - LCDspL = unit->beer_set_lo; - } else if (unit->mode == UNITMODE_FRIDGE) { - LCDspH = unit->fridge_set_hi; - LCDspL = unit->fridge_set_lo; - } else if (unit->mode == UNITMODE_PROFILE) { - if (unit->prof_state != PROFILE_OFF) { - LCDspL = unit->prof_target_lo; - LCDspH = unit->prof_target_hi; - } - } -// if ((seconds == 60) && ((unit->mode == UNITMODE_FRIDGE) || (unit->mode == UNITMODE_BEER) || (unit->mode == UNITMODE_PROFILE))) { -// unit->mqtt_flag |= MQTT_FLAG_DATA; -// } - if (seconds == 60) { - unit->mqtt_flag |= MQTT_FLAG_DATA; - } - pthread_mutex_lock(&mutexes[LOCK_LCD]); - /* - * Write 4 rows to the LCD to display the unit state - */ - lcd_buf_write(row++, "Unit %d: %s ", LCDunit, UNITMODE[unit->mode]); - lcd_buf_write(row++, "%s ", unit->product_name); - lcd_buf_write(row++, "%c%5.1f\2 A%6.2f\1 ", LCDstatC, LCDspH, LCDair); - lcd_buf_write(row++, "%c%5.1f\2 B%6.2f\1 ", LCDstatH, LCDspL, LCDbeer); - pthread_mutex_unlock(&mutexes[LOCK_LCD]); - - /* - * Publish MQTT messages set in flag - */ - if (unit->mqtt_flag) { - if (unit->mqtt_flag & MQTT_FLAG_BIRTH) { - publishDBirth(unit); - unit->mqtt_flag &= ~MQTT_FLAG_BIRTH; - } else { - publishDData(unit); - unit->mqtt_flag &= ~MQTT_FLAG_DATA; - } - if (unit->mqtt_flag & MQTT_FLAG_DEATH) { - publishDDeath(unit); - unit->mqtt_flag &= ~MQTT_FLAG_DEATH; - } - } - - /* - * Handle changed alarms - */ - if (unit->alarm_flag != unit->alarm_last) { - syslog(LOG_NOTICE, "Unit `%s' Alarm %d => %d", unit->alias, unit->alarm_last, unit->alarm_flag); - unit->alarm_last = unit->alarm_flag; - } } /* for units */ pthread_mutex_lock(&mutexes[LOCK_MENU]);