Mon, 15 Apr 2024 17:04:57 +0200
Better websocket broadcast messages. Added GLOBAL JSON command to the server. Better logic to trigger websocket and mqtt data updates for the fermenter units. Websocket receive added fermenter mode, stage, setpoints, switches. Added more css styles for the fermenter screen. Added the fermenter screen php and javascript.
/***************************************************************************** * Copyright (C) 2014-2024 * * Michiel Broek <mbroek at mbse dot eu> * * This file is part of the mbsePi-apps * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * mbsePi-apps is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ThermFerm; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *****************************************************************************/ #include "lock.h" #include "rdconfig.h" #include "server.h" #include "thermferm.h" #include "devices.h" #include "delay.h" #include "simulator.h" #include "lcd-pcf8574.h" #include "lcd-buffer.h" #include "slcd.h" #include "panel.h" #include "one-wire.h" #include "futil.h" #include "xutil.h" #include "pid.h" #include "mqtt.h" #include "statetbl.h" #include "websocket.h" int my_shutdown = FALSE; int my_reboot = FALSE; static pid_t pgrp, mypid; int run_pause = FALSE; int run_hold = FALSE; int row; extern int debug; extern int foreground; extern sys_config Config; extern int lcdHandle; extern int slcdHandle; extern int my_devices_state; extern int my_devices_shutdown; extern int my_panel_state; extern int my_panel_shutdown; extern int my_server_state; extern int my_server_shutdown; extern int my_ws_state; extern int my_ws_shutdown; extern int my_simulator_state; #ifdef USE_SIMULATOR extern int my_simulator_shutdown; #endif extern int my_one_wire_state; extern int my_one_wire_shutdown; int setupmenu = MENU_NONE; units_list *current_unit = NULL; /* In panel editor this points to the current unit. */ float temp_temp = 20.0; pthread_t my_one_wire_thread; pthread_t my_devices_thread; pthread_t my_panel_thread; pthread_t my_server_thread; pthread_t my_ws_thread; #ifdef USE_SIMULATOR pthread_t my_simulator_thread; #endif pthread_mutex_t mutexes[5]; extern const char UNITMODE[5][8]; extern const char PROFSTATE[4][6]; extern int sock; unsigned char degC[8] = { 0b01000, 0b10100, 0b01000, 0b00111, 0b01000, 0b01000, 0b01000, 0b00111 }; // [1] degree c sybmol unsigned char SP_Symbol[8] = { 0b11100, 0b10000, 0b11100, 0b00111, 0b11101, 0b00111, 0b00100, 0b00100 }; // [2] SP Symbol unsigned char CoolONOFF[8] = { 0b00000, 0b01110, 0b01000, 0b01000, 0b01000, 0b01000, 0b01110, 0b00000 }; // [3] Cool Symbol unsigned char RevCoolONOFF[8] = { 0b11111, 0b10001, 0b10111, 0b10111, 0b10111, 0b10111, 0b10001, 0b11111 }; // [4] Reverse Cool Symbol unsigned char HeatONOFF[8] = { 0b00000, 0b01010, 0b01010, 0b01110, 0b01110, 0b01010, 0b01010, 0b00000 }; // [5] HEAT symbol unsigned char RevHeatONOFF[8] = { 0b11111, 0b10101, 0b10101, 0b10001, 0b10001, 0b10101, 0b10101, 0b11111 }; // [6] reverse HEAT symbol int server(void); void help(void); void die(int); void stopLCD(void); #ifdef HAVE_WIRINGPI_H void sendRCswitch(char *, int); void stopRCswitch(void); #endif extern int mqtt_qos; extern int mqtt_last_mid; extern int mqtt_last_mid_sent; extern int mqtt_mid_sent; extern int mqtt_disconnect_sent; extern int mqtt_connected; extern int mqtt_status; extern int mqtt_use; extern struct mosquitto *mosq; extern char *state; void help(void) { fprintf(stdout, "mbsePi-apps thermferm v%s starting\n\n", VERSION); fprintf(stdout, "Usage: thermferm [-d] [-h]\n"); fprintf(stdout, " -d --debug Extra debug logging\n"); fprintf(stdout, " -f --foreground Run in foreground\n"); fprintf(stdout, " -h --help Display this help\n"); } void die(int onsig) { switch (onsig) { case SIGHUP: syslog(LOG_NOTICE, "Got SIGHUP, shutting down"); break; case SIGINT: syslog(LOG_NOTICE, "Keyboard interrupt, shutting down"); break; case SIGTERM: syslog(LOG_NOTICE, "Got SIGTERM, shutting down"); break; case SIGSEGV: syslog(LOG_NOTICE, "Got SIGSEGV, shutting down"); my_shutdown = TRUE; exit(SIGSEGV); break; default: syslog(LOG_NOTICE, "die() on signal %d", onsig); } my_shutdown = TRUE; } void show_mode(void) { char buf[21]; snprintf(buf, 20, "Old mode %s", UNITMODE[current_unit->mode]); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); lcdPosition(lcdHandle, 0, 1); #endif slcdPuts(slcdHandle, buf); slcdPosition(slcdHandle, 0, 1); } void go_menu(int menu) { char buf[21]; pthread_mutex_lock(&mutexes[LOCK_LCD]); pthread_mutex_lock(&mutexes[LOCK_MENU]); #ifdef HAVE_WIRINGPI_H lcdClear(lcdHandle); lcdPosition(lcdHandle, 0, 0); #endif slcdClear(slcdHandle); slcdPosition(slcdHandle, 0, 0); setupmenu = menu; switch (menu) { case MENU_NONE: lcd_buf_show(); break; case MENU_TOP_DEFAULT: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Setup mode."); lcdPosition(lcdHandle, 0, 1); lcdPuts(lcdHandle, "Up&Down = Escape"); #endif slcdPuts(slcdHandle, "Setup mode."); slcdPosition(slcdHandle, 0, 1); slcdPuts(slcdHandle, "Up&Down = Escape"); break; case MENU_TOP_UNITS: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Select units"); #endif slcdPuts(slcdHandle, "Select units"); break; case MENU_UNITS: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Choose unit:"); lcdPosition(lcdHandle, 0, 1); lcdPuts(lcdHandle, current_unit->alias); #endif slcdPuts(slcdHandle, "Choose unit:"); slcdPosition(slcdHandle, 0, 1); slcdPuts(slcdHandle, current_unit->alias); break; case MENU_MODE_OFF: show_mode(); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "New mode OFF"); #endif slcdPuts(slcdHandle, "New mode OFF"); break; case MENU_MODE_NONE: show_mode(); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "New mode NONE"); #endif slcdPuts(slcdHandle, "New mode NONE"); break; case MENU_NONE_HEAT: snprintf(buf, Config.lcd_cols, "Set heater %s", current_unit->heater_state ? "OFF":"ON"); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); #endif slcdPuts(slcdHandle, buf); break; case MENU_NONE_COOL: snprintf(buf, Config.lcd_cols, "Set cooler %s", current_unit->cooler_state ? "OFF":"ON"); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); #endif slcdPuts(slcdHandle, buf); break; case MENU_NONE_FAN: snprintf(buf, Config.lcd_cols, "Set fan %s", current_unit->fan_state ? "OFF":"ON"); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); #endif slcdPuts(slcdHandle, buf); break; case MENU_MODE_BEER: show_mode(); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "New mode BEER"); #endif slcdPuts(slcdHandle, "New mode BEER"); break; case MENU_BEER_TEMP_LO: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Set beer low"); lcdPosition(lcdHandle, 0, 1); #endif slcdPuts(slcdHandle, "Set beer low"); slcdPosition(slcdHandle, 0, 1); snprintf(buf, Config.lcd_cols, "Set %.1f", temp_temp); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); #endif slcdPuts(slcdHandle, buf); break; case MENU_BEER_TEMP_HI: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Set beer high"); lcdPosition(lcdHandle, 0, 1); #endif slcdPuts(slcdHandle, "Set beer high"); slcdPosition(slcdHandle, 0, 1); snprintf(buf, Config.lcd_cols, "Set %.1f", temp_temp); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); #endif slcdPuts(slcdHandle, buf); break; case MENU_MODE_FRIDGE: show_mode(); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "New mode FRIDGE"); #endif slcdPuts(slcdHandle, "New mode FRIDGE"); break; case MENU_FRIDGE_TEMP_LO: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Set fridge low"); lcdPosition(lcdHandle, 0, 1); #endif slcdPuts(slcdHandle, "Set fridge low"); slcdPosition(slcdHandle, 0, 1); snprintf(buf, Config.lcd_cols, "Set %.1f", temp_temp); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); #endif slcdPuts(slcdHandle, buf); break; case MENU_FRIDGE_TEMP_HI: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Set fridge high"); lcdPosition(lcdHandle, 0, 1); #endif slcdPuts(slcdHandle, "Set fridge high"); slcdPosition(slcdHandle, 0, 1); snprintf(buf, Config.lcd_cols, "Set %.1f", temp_temp); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); #endif slcdPuts(slcdHandle, buf); break; case MENU_MODE_PROFILE: show_mode(); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "New mode PROFILE"); #endif slcdPuts(slcdHandle, "New mode PROFILE"); break; case MENU_PROFILE_START: snprintf(buf, Config.lcd_cols, "%s", current_unit->profile_name); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); lcdPosition(lcdHandle, 0, 1); lcdPuts(lcdHandle, "Start profile"); #endif slcdPuts(slcdHandle, buf); slcdPosition(slcdHandle, 0, 1); slcdPuts(slcdHandle, "Start profile"); break; case MENU_PROFILE_PAUSE: snprintf(buf, Config.lcd_cols, "%s", current_unit->profile_name); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); lcdPosition(lcdHandle, 0, 1); lcdPuts(lcdHandle, "Pause profile"); #endif slcdPuts(slcdHandle, buf); slcdPosition(slcdHandle, 0, 1); slcdPuts(slcdHandle, "Pause profile"); break; case MENU_PROFILE_ABORT: snprintf(buf, Config.lcd_cols, "%s", current_unit->profile_name); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); lcdPosition(lcdHandle, 0, 1); lcdPuts(lcdHandle, "Abort profile"); #endif slcdPuts(slcdHandle, buf); slcdPosition(slcdHandle, 0, 1); slcdPuts(slcdHandle, "Abort profile"); break; case MENU_PROFILE_RESUME: snprintf(buf, Config.lcd_cols, "%s", current_unit->profile_name); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); lcdPosition(lcdHandle, 0, 1); lcdPuts(lcdHandle, "Resume profile"); #endif slcdPuts(slcdHandle, buf); slcdPosition(slcdHandle, 0, 1); slcdPuts(slcdHandle, "Resume profile"); break; case MENU_PROFILE_GOOFF: snprintf(buf, Config.lcd_cols, "%s", current_unit->profile_name); #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, buf); lcdPosition(lcdHandle, 0, 1); lcdPuts(lcdHandle, "Set profile OFF"); #endif slcdPuts(slcdHandle, buf); slcdPosition(slcdHandle, 0, 1); slcdPuts(slcdHandle, "Set profile OFF"); break; case MENU_TOP_SYS: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "System menu"); #endif slcdPuts(slcdHandle, "System menu"); break; case MENU_SYS_HALT: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Halt system"); #endif slcdPuts(slcdHandle, "Halt system"); break; case MENU_SYS_REBOOT: #ifdef HAVE_WIRINGPI_H lcdPuts(lcdHandle, "Restart app."); #endif slcdPuts(slcdHandle, "Restart app."); break; } pthread_mutex_unlock(&mutexes[LOCK_MENU]); pthread_mutex_unlock(&mutexes[LOCK_LCD]); } void stopLCD(void) { pthread_mutex_lock(&mutexes[LOCK_LCD]); #ifdef HAVE_WIRINGPI_H lcdClear(lcdHandle); #endif slcdClear(slcdHandle); setBacklight(0); pthread_mutex_unlock(&mutexes[LOCK_LCD]); } /* * Change mode of current_unit */ void change_mode(int mode) { current_unit->mqtt_flag |= MQTT_FLAG_DATA; if ((current_unit->mode == UNITMODE_OFF) && (mode != UNITMODE_OFF)) { current_unit->mqtt_flag |= MQTT_FLAG_BIRTH; } syslog(LOG_NOTICE, "Mode from %s to %s via panel interface", UNITMODE[current_unit->mode], UNITMODE[mode]); current_unit->mode = mode; /* Allways turn everything off after a mode change */ current_unit->PID_cool->OutP = current_unit->PID_heat->OutP = 0.0; current_unit->PID_cool->Mode = current_unit->PID_heat->Mode = PID_MODE_NONE; current_unit->heater_state = current_unit->cooler_state = current_unit->fan_state = current_unit->light_state = current_unit->light_timer = 0; current_unit->heater_wait = current_unit->cooler_wait = current_unit->fan_wait = current_unit->light_wait = 0; device_out(current_unit->heater_address, current_unit->heater_state); device_out(current_unit->cooler_address, current_unit->cooler_state); device_out(current_unit->fan_address, current_unit->fan_state); device_out(current_unit->light_address, current_unit->light_state); if (current_unit->mode == UNITMODE_PROFILE) { /* * Set a sane default until it will be overruled by the * main processing loop. */ current_unit->prof_target_lo = 20.0; current_unit->prof_target_hi = 20.0; current_unit->prof_fridge_mode = 0; } } /* * Handle panel key events */ void panel_key_events(int key) { units_list *unit; int rc; switch (setupmenu) { case MENU_NONE: if ((key == KEY_DOWN) || (key == KEY_UP)) lcd_buf_step(key); if ((key == KEY_CONFIRM) && (setupmenu == MENU_NONE)) go_menu(MENU_TOP_DEFAULT); break; case MENU_TOP_DEFAULT: if (key == KEY_ESCAPE) go_menu(MENU_NONE); if (key == KEY_DOWN) go_menu(MENU_TOP_UNITS); if (key == KEY_UP) go_menu(MENU_TOP_SYS); break; case MENU_TOP_UNITS: if (key == KEY_ESCAPE) go_menu(MENU_NONE); if (key == KEY_DOWN) go_menu(MENU_TOP_SYS); if (key == KEY_UP) go_menu(MENU_TOP_DEFAULT); if ((key == KEY_ENTER) && Config.units) { /* * Start with the first unit */ current_unit = Config.units; go_menu(MENU_UNITS); } break; case MENU_UNITS: if (key == KEY_ESCAPE) go_menu(MENU_TOP_UNITS); if (key == KEY_DOWN) { if (current_unit->next) { current_unit = current_unit->next; go_menu(MENU_UNITS); } } if (key == KEY_UP) { for (unit = Config.units; unit; unit = unit->next) { if (unit->next && (unit->next == current_unit)) { current_unit = unit; go_menu(MENU_UNITS); break; } } } if (key == KEY_ENTER) { /* * Drop into the current mode */ switch (current_unit->mode) { case UNITMODE_OFF: go_menu(MENU_MODE_OFF); break; case UNITMODE_NONE: go_menu(MENU_MODE_NONE); break; case UNITMODE_FRIDGE: go_menu(MENU_MODE_FRIDGE); break; case UNITMODE_BEER: go_menu(MENU_MODE_BEER); break; case UNITMODE_PROFILE: go_menu(MENU_MODE_PROFILE); break; } } break; case MENU_MODE_OFF: if (key == KEY_ESCAPE) go_menu(MENU_UNITS); if (key == KEY_DOWN) go_menu(MENU_MODE_NONE); if (key == KEY_UP) { if (current_unit->profile_uuid) go_menu(MENU_MODE_PROFILE); else go_menu(MENU_MODE_BEER); } if (key == KEY_ENTER) { change_mode(UNITMODE_OFF); go_menu(MENU_MODE_OFF); } break; case MENU_MODE_NONE: if (key == KEY_ESCAPE) go_menu(MENU_UNITS); if (key == KEY_DOWN) go_menu(MENU_MODE_FRIDGE); if (key == KEY_UP) go_menu(MENU_MODE_OFF); if (key == KEY_ENTER) { if (current_unit->mode == UNITMODE_NONE) go_menu(MENU_NONE_HEAT); else { change_mode(UNITMODE_NONE); go_menu(MENU_MODE_NONE); } } break; case MENU_NONE_HEAT: if (key == KEY_ESCAPE) go_menu(MENU_MODE_NONE); if (key == KEY_DOWN) go_menu(MENU_NONE_COOL); if (key == KEY_UP) go_menu(MENU_NONE_FAN); if (key == KEY_ENTER) { if (current_unit->heater_state) current_unit->heater_state = 0; else current_unit->heater_state = 100; go_menu(MENU_NONE_HEAT); } break; case MENU_NONE_COOL: if (key == KEY_ESCAPE) go_menu(MENU_MODE_NONE); if (key == KEY_DOWN) go_menu(MENU_NONE_FAN); if (key == KEY_UP) go_menu(MENU_NONE_HEAT); if (key == KEY_ENTER) { if (current_unit->cooler_state) current_unit->cooler_state = 0; else current_unit->cooler_state = 100; go_menu(MENU_NONE_COOL); } break; case MENU_NONE_FAN: if (key == KEY_ESCAPE) go_menu(MENU_MODE_NONE); if (key == KEY_DOWN) go_menu(MENU_NONE_HEAT); if (key == KEY_UP) go_menu(MENU_NONE_COOL); if (key == KEY_ENTER) { if (current_unit->fan_state) current_unit->fan_state = 0; else current_unit->fan_state = 100; go_menu(MENU_NONE_FAN); } break; case MENU_MODE_FRIDGE: if (key == KEY_ESCAPE) go_menu(MENU_UNITS); if (key == KEY_DOWN) go_menu(MENU_MODE_BEER); if (key == KEY_UP) go_menu(MENU_MODE_NONE); if (key == KEY_ENTER) { if (current_unit->mode == UNITMODE_FRIDGE) { temp_temp = current_unit->fridge_set_lo; go_menu(MENU_FRIDGE_TEMP_LO); } else { change_mode(UNITMODE_FRIDGE); go_menu(MENU_MODE_FRIDGE); } } break; case MENU_FRIDGE_TEMP_LO: if (key == KEY_ESCAPE) { temp_temp = current_unit->fridge_set_hi; go_menu(MENU_FRIDGE_TEMP_HI); } if (key == KEY_DOWN) { if (temp_temp > current_unit->temp_set_min) temp_temp -= 0.1; go_menu(MENU_FRIDGE_TEMP_LO); } if (key == KEY_UP) { if (temp_temp < current_unit->temp_set_max) temp_temp += 0.1; go_menu(MENU_FRIDGE_TEMP_LO); } if (key == KEY_CONFIRM) { if (temp_temp != current_unit->fridge_set_lo) { syslog(LOG_NOTICE, "Fridge temp low changed from %.1f to %.1f from the panel", current_unit->fridge_set_lo, temp_temp); current_unit->fridge_set_lo = temp_temp; current_unit->mqtt_flag |= MQTT_FLAG_DATA; } temp_temp = current_unit->fridge_set_hi; go_menu(MENU_FRIDGE_TEMP_HI); } break; case MENU_FRIDGE_TEMP_HI: if (key == KEY_ESCAPE) go_menu(MENU_MODE_FRIDGE); if (key == KEY_DOWN) { if (temp_temp > current_unit->temp_set_min) temp_temp -= 0.1; go_menu(MENU_FRIDGE_TEMP_HI); } if (key == KEY_UP) { if (temp_temp < current_unit->temp_set_max) temp_temp += 0.1; go_menu(MENU_FRIDGE_TEMP_HI); } if (key == KEY_CONFIRM) { if (temp_temp != current_unit->fridge_set_hi) { syslog(LOG_NOTICE, "Fridge temp high changed from %.1f to %.1f from the panel", current_unit->fridge_set_hi, temp_temp); current_unit->fridge_set_hi = temp_temp; current_unit->mqtt_flag |= MQTT_FLAG_DATA; } go_menu(MENU_MODE_FRIDGE); } break; case MENU_MODE_BEER: if (key == KEY_ESCAPE) go_menu(MENU_UNITS); if (key == KEY_DOWN) { if (current_unit->profile_uuid) go_menu(MENU_MODE_PROFILE); else go_menu(MENU_MODE_OFF); } if (key == KEY_UP) go_menu(MENU_MODE_FRIDGE); if (key == KEY_ENTER) { if (current_unit->mode == UNITMODE_BEER) { temp_temp = current_unit->beer_set_lo; go_menu(MENU_BEER_TEMP_LO); } else { change_mode(UNITMODE_BEER); go_menu(MENU_MODE_BEER); } } break; case MENU_BEER_TEMP_LO: if (key == KEY_ESCAPE) { temp_temp = current_unit->beer_set_hi; go_menu(MENU_BEER_TEMP_HI); } if (key == KEY_DOWN) { if (temp_temp > current_unit->temp_set_min) temp_temp -= 0.1; go_menu(MENU_BEER_TEMP_LO); } if (key == KEY_UP) { if (temp_temp < current_unit->temp_set_max) temp_temp += 0.1; go_menu(MENU_BEER_TEMP_LO); } if (key == KEY_CONFIRM) { if (temp_temp != current_unit->beer_set_lo ) { syslog(LOG_NOTICE, "Beer temp low changed from %.1f to %.1f from the panel", current_unit->beer_set_lo, temp_temp); current_unit->beer_set_lo = temp_temp; current_unit->mqtt_flag |= MQTT_FLAG_DATA; } temp_temp = current_unit->beer_set_hi; go_menu(MENU_BEER_TEMP_HI); } break; case MENU_BEER_TEMP_HI: if (key == KEY_ESCAPE) go_menu(MENU_MODE_BEER); if (key == KEY_DOWN) { if (temp_temp > current_unit->temp_set_min) temp_temp -= 0.1; go_menu(MENU_BEER_TEMP_HI); } if (key == KEY_UP) { if (temp_temp < current_unit->temp_set_max) temp_temp += 0.1; go_menu(MENU_BEER_TEMP_HI); } if (key == KEY_CONFIRM) { if (temp_temp != current_unit->beer_set_hi ) { syslog(LOG_NOTICE, "Beer temp high changed from %.1f to %.1f from the panel", current_unit->beer_set_hi, temp_temp); current_unit->beer_set_hi = temp_temp; current_unit->mqtt_flag |= MQTT_FLAG_DATA; } go_menu(MENU_MODE_BEER); } break; case MENU_MODE_PROFILE: if (key == KEY_ESCAPE) go_menu(MENU_UNITS); if (key == KEY_DOWN) go_menu(MENU_MODE_OFF); if (key == KEY_UP) go_menu(MENU_MODE_BEER); if (key == KEY_ENTER) { if (current_unit->mode == UNITMODE_PROFILE) { switch (current_unit->prof_state) { case PROFILE_OFF: go_menu(MENU_PROFILE_START); break; case PROFILE_PAUSE: go_menu(MENU_PROFILE_RESUME); break; case PROFILE_RUN: go_menu(MENU_PROFILE_PAUSE); break; case PROFILE_DONE: go_menu(MENU_PROFILE_GOOFF); break; } } else { change_mode(UNITMODE_PROFILE); go_menu(MENU_MODE_PROFILE); } } break; case MENU_PROFILE_START: if (key == KEY_ESCAPE) go_menu(MENU_MODE_PROFILE); if (key == KEY_ENTER) { current_unit->prof_state = PROFILE_RUN; current_unit->prof_started = time(NULL); current_unit->prof_paused = current_unit->prof_primary_done = 0; current_unit->prof_peak_abs = current_unit->prof_peak_rel = 0.0; syslog(LOG_NOTICE, "Profile started from the panel"); current_unit->mqtt_flag |= MQTT_FLAG_DATA; go_menu(MENU_MODE_PROFILE); } break; case MENU_PROFILE_PAUSE: if (key == KEY_ESCAPE) go_menu(MENU_MODE_PROFILE); if ((key == KEY_DOWN) || (key == KEY_UP)) go_menu(MENU_PROFILE_ABORT); if (key == KEY_ENTER) { current_unit->prof_state = PROFILE_PAUSE; current_unit->mqtt_flag |= MQTT_FLAG_DATA; syslog(LOG_NOTICE, "Profile pause from the panel"); go_menu(MENU_MODE_PROFILE); } break; case MENU_PROFILE_ABORT: if (key == KEY_ESCAPE) go_menu(MENU_MODE_PROFILE); if ((key == KEY_DOWN) || (key == KEY_UP)) { if (current_unit->prof_state == PROFILE_PAUSE) go_menu(MENU_PROFILE_RESUME); else if (current_unit->prof_state == PROFILE_RUN) go_menu(MENU_PROFILE_PAUSE); } if ((key == KEY_ENTER) && ((current_unit->prof_state == PROFILE_RUN) || (current_unit->prof_state == PROFILE_PAUSE))) { current_unit->prof_state = PROFILE_OFF; current_unit->prof_started = 0; syslog(LOG_NOTICE, "Profile aborted from the panel"); current_unit->mqtt_flag |= MQTT_FLAG_DATA; go_menu(MENU_MODE_PROFILE); } break; case MENU_PROFILE_RESUME: if (key == KEY_ESCAPE) go_menu(MENU_MODE_PROFILE); if ((key == KEY_DOWN) || (key == KEY_UP)) go_menu(MENU_PROFILE_ABORT); if (key == KEY_ENTER) { current_unit->prof_state = PROFILE_RUN; syslog(LOG_NOTICE, "Profile resume from the panel"); current_unit->mqtt_flag |= MQTT_FLAG_DATA; go_menu(MENU_MODE_PROFILE); } break; case MENU_PROFILE_GOOFF: if (key == KEY_ESCAPE) go_menu(MENU_MODE_PROFILE); if (key == KEY_ENTER) { if (current_unit->prof_state == PROFILE_DONE) { current_unit->prof_state = PROFILE_OFF; current_unit->mqtt_flag |= MQTT_FLAG_DATA; syslog(LOG_NOTICE, "Profile from done to off from the panel"); } } break; case MENU_TOP_SYS: if (key == KEY_ESCAPE) go_menu(MENU_NONE); if (key == KEY_DOWN) go_menu(MENU_TOP_DEFAULT); if (key == KEY_UP) go_menu(MENU_TOP_UNITS); if (key == KEY_ENTER) go_menu(MENU_SYS_HALT); break; case MENU_SYS_HALT: if (key == KEY_ESCAPE) go_menu(MENU_TOP_SYS); if ((key == KEY_DOWN) || (key == KEY_UP)) go_menu(MENU_SYS_REBOOT); if (key == KEY_CONFIRM) { rc = system("/sbin/halt"); syslog(LOG_NOTICE, "System halt from panel: /sbin/halt rc=%d", rc); go_menu(MENU_NONE); } break; case MENU_SYS_REBOOT: if (key == KEY_ESCAPE) go_menu(MENU_TOP_SYS); if ((key == KEY_DOWN) || (key == KEY_UP)) go_menu(MENU_SYS_HALT); if (key == KEY_CONFIRM) { /* * Restart. The server process will restart which is handled * in the main thread loop. */ my_reboot = my_shutdown = TRUE; syslog(LOG_NOTICE, "Application restart from panel"); go_menu(MENU_NONE); } break; } } int main(int argc, char *argv[]) { int rc, c, i; pid_t frk; while (1) { int option_index = 0; static struct option long_options[] = { {"debug", 0, 0, 'c'}, {"foreground", 0, 0, 'f'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; c = getopt_long(argc, argv, "dh", long_options, &option_index); if (c == -1) break; switch (c) { case 'd': debug = TRUE; break; case 'f': foreground = TRUE; break; case 'h': help(); return 1; } } openlog("thermferm", LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_USER); syslog(LOG_NOTICE, "mbsePi-apps thermferm v%s starting", VERSION); if (rdconfig()) { fprintf(stderr, "Error reading configuration\n"); syslog(LOG_NOTICE, "Error reading configuration: halted"); return 1; } /* * Catch all the signals we can, and ignore the rest. Note that SIGKILL can't be ignored * but that's live. This daemon should only be stopped by SIGTERM. * Don't catch SIGCHLD. */ for (i = 0; i < NSIG; i++) { if ((i != SIGCHLD) && (i != SIGKILL) && (i != SIGSTOP)) signal(i, (void (*))die); } #ifdef HAVE_WIRINGPI_H syslog(LOG_NOTICE, "Build with wiringPi"); if (wiringPiSetup () ) { syslog(LOG_NOTICE, "Error wiringPiSetup(): halted"); return 1; } #endif #ifdef USE_SIMULATOR syslog(LOG_NOTICE, "Build with simulator"); #endif if (foreground) { /* * Run in foreground. */ do { rc = server(); } while (my_reboot == TRUE); } else { /* * Server initialization is complete. Now we can fork the * daemon and return to the user. We need to do a setpgrp * so that the daemon will no longer be assosiated with the * users control terminal. This is done before the fork, so * that the child will not be a process group leader. Otherwise, * if the child were to open a terminal, it would become * associated with that terminal as its control terminal. */ if ((pgrp = setpgid(0, 0)) == -1) { syslog(LOG_NOTICE, "setpgpid failed"); } frk = fork(); switch (frk) { case -1: syslog(LOG_NOTICE, "Daemon fork failed: %s", strerror(errno)); stopLCD(); exit(1); case 0: /* * Run the daemon */ fclose(stdin); if (open("/dev/null", O_RDONLY) != 0) { syslog(LOG_NOTICE, "Reopen of stdin to /dev/null failed"); _exit(2); } fclose(stdout); if (open("/dev/null", O_WRONLY | O_APPEND | O_CREAT,0600) != 1) { syslog(LOG_NOTICE, "Reopen of stdout to /dev/null failed"); _exit(2); } fclose(stderr); if (open("/dev/null", O_WRONLY | O_APPEND | O_CREAT,0600) != 2) { syslog(LOG_NOTICE, "Reopen of stderr to /dev/null failed"); _exit(2); } mypid = getpid(); do { rc = server(); } while (my_reboot == TRUE); break; /* Not reached */ default: /* * Here we detach this process and let the child * run the deamon process. */ syslog(LOG_NOTICE, "Starting daemon with pid %d", frk); exit(0); } } killconfig(); syslog(LOG_NOTICE, "Finished, rc=%d", rc); return rc; } 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) syslog(LOG_NOTICE, "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) syslog(LOG_NOTICE, "prof_fridge_mode=%d run_minutes=%d steptime=%d time_until_now=%d", 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; } } SM_DECL(thermferm,(char *)"thermferm") SM_STATES CheckRun, WaitMinute, DateTime, RoomTHB, Units, ShowLCD, Minute, Keys SM_NAMES (char *)"CheckRun", (char *)"WaitMinute", (char *)"DateTime", (char *)"RoomTHB", (char *)"Units", (char *)"ShowLCD", (char *)"Minute", (char *)"Keys" SM_EDECL time_t now, last = (time_t)0, ndata = (time_t)0; int key, LCDunit, rc, temp, seconds = 0, minutes = 0; struct tm *tm; units_list *unit; SM_START(CheckRun) SM_STATE(CheckRun) if (my_shutdown) { SM_SUCCESS; } /* * Use to stop processing units. Should be used when a unit is * added or removed. */ if (run_pause) { run_hold = TRUE; syslog(LOG_NOTICE, "run_pause: entering hold state"); for (;;) { mDelay(100); if (! run_pause) break; } syslog(LOG_NOTICE, "run_pause: leaving hold state"); run_hold = FALSE; /* * In case the LCD buffers were cleared, setup the first page. */ pthread_mutex_lock(&mutexes[LOCK_LCD]); lcd_buf_write(1, (char *)" ThermFerm "); lcd_buf_write(2, (char *)"Version %s ", VERSION); pthread_mutex_unlock(&mutexes[LOCK_LCD]); } SM_PROCEED(WaitMinute); SM_STATE(WaitMinute) if (my_shutdown) { SM_SUCCESS; } now = time(NULL); if (now != last) { /* * Each second */ last = now; seconds++; SM_PROCEED(DateTime); } else { SM_PROCEED(Keys); } SM_STATE(DateTime) row = 3; tm = localtime(&now); pthread_mutex_lock(&mutexes[LOCK_LCD]); lcd_buf_write(row++, " %02d-%02d-%04d ", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900); lcd_buf_write(row++, " %02d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec); pthread_mutex_unlock(&mutexes[LOCK_LCD]); SM_PROCEED(RoomTHB); SM_STATE(RoomTHB) pthread_mutex_lock(&mutexes[LOCK_LCD]); lcd_buf_write(row, "Room temp N/A "); pthread_mutex_unlock(&mutexes[LOCK_LCD]); int updateHT = 0; if (Config.temp_address) { rc = device_in(Config.temp_address, &temp); if (rc == DEVPRESENT_YES) { if (Config.temp_value != temp) updateHT = 1; Config.temp_value = temp; Config.temp_state = 0; pthread_mutex_lock(&mutexes[LOCK_LCD]); lcd_buf_write(row, "Room temp %.1f%c ", Config.temp_value / 1000.0, 0x01); pthread_mutex_unlock(&mutexes[LOCK_LCD]); } else if (rc == DEVPRESENT_ERROR) { Config.temp_state = 1; } else { Config.temp_state = 2; } mDelay(10); } row++; pthread_mutex_lock(&mutexes[LOCK_LCD]); lcd_buf_write(row, " Humidity N/A "); pthread_mutex_unlock(&mutexes[LOCK_LCD]); if (Config.hum_address) { rc = device_in(Config.hum_address, &temp); if (rc == DEVPRESENT_YES) { if (Config.hum_value != temp) updateHT = 1; Config.hum_value = temp; Config.hum_state = 0; pthread_mutex_lock(&mutexes[LOCK_LCD]); lcd_buf_write(row, " Humidity %.1f%% ", Config.hum_value / 1000.0); pthread_mutex_unlock(&mutexes[LOCK_LCD]); } else if (rc == DEVPRESENT_ERROR) { Config.hum_state = 1; } else { Config.hum_state = 2; } mDelay(10); } row++; /* * If TH(B) changed. or if 5 minutes without * update, send the NDATA message. */ if (updateHT || (now > (ndata + 300))) { publishNData(false, 0); ndata = now; } SM_PROCEED(Units); SM_STATE(Units) LCDunit = 0; for (unit = Config.units; unit; unit = unit->next) { LCDunit++; do_unit(unit, LCDunit, seconds, minutes); } SM_PROCEED(ShowLCD); SM_STATE(ShowLCD) pthread_mutex_lock(&mutexes[LOCK_MENU]); if (setupmenu == MENU_NONE) { pthread_mutex_lock(&mutexes[LOCK_LCD]); lcd_buf_show(); pthread_mutex_unlock(&mutexes[LOCK_LCD]); } pthread_mutex_unlock(&mutexes[LOCK_MENU]); SM_PROCEED(Minute); SM_STATE(Minute) if (seconds == 60) { seconds = 0; /* * Publish data every minute if unit is active. */ for (unit = Config.units; unit; unit = unit->next) { if (unit->mode != UNITMODE_OFF) { publishDLog(unit); if (unit->event_msg) free(unit->event_msg); unit->event_msg = NULL; } } minutes++; if (minutes == 60) { minutes = 0; /* * Log usage counters every hour */ for (unit = Config.units; unit; unit = unit->next) { syslog(LOG_NOTICE, "Unit `%s' usage heater=%d cooler=%d fan=%d", unit->alias, unit->heater_usage, unit->cooler_usage, unit->fan_usage); } } /* * Save the configuration each half hour. */ if ((minutes == 15) || (minutes == 45)) wrconfig(); } SM_PROCEED(Keys); SM_STATE(Keys) slcdDummy(slcdHandle); key = keycheck(); if (key != KEY_NONE) panel_key_events(key); mDelay(25); ws_check(); mDelay(25); SM_PROCEED(CheckRun); SM_END SM_RETURN int server(void) { units_list *unit; int rc; long t = 0; syslog(LOG_NOTICE, "Server process started"); if ((rc = initLCD (Config.lcd_cols, Config.lcd_rows))) { fprintf(stderr, "Cannot initialize LCD display, rc=%d\n", rc); return 1; } #ifdef HAVE_WIRINGPI_H lcdCharDef(lcdHandle, 1, degC); lcdCharDef(lcdHandle, 2, SP_Symbol); lcdCharDef(lcdHandle, 3, CoolONOFF); lcdCharDef(lcdHandle, 4, RevCoolONOFF); lcdCharDef(lcdHandle, 5, HeatONOFF); lcdCharDef(lcdHandle, 6, RevHeatONOFF); #endif slcdCharDef(slcdHandle, 1, degC); slcdCharDef(slcdHandle, 2, SP_Symbol); slcdCharDef(slcdHandle, 3, CoolONOFF); slcdCharDef(slcdHandle, 4, RevCoolONOFF); slcdCharDef(slcdHandle, 5, HeatONOFF); slcdCharDef(slcdHandle, 6, RevHeatONOFF); my_shutdown = my_reboot = FALSE; my_devices_shutdown = my_panel_shutdown = my_server_shutdown = my_ws_shutdown = my_one_wire_shutdown = 0; my_devices_state = my_panel_state = my_server_state = my_ws_state = my_one_wire_state = 0; my_simulator_state = 0; #ifdef USE_SIMULATOR my_simulator_shutdown = 0; #endif if (lockprog((char *)"thermferm")) { syslog(LOG_NOTICE, "Can't lock"); return 1; } mqtt_connect(); /* * Start websockets first. */ rc = pthread_create(&my_ws_thread, NULL, my_ws_loop, (void *)t ); if (rc) { fprintf(stderr, "my_ws_loop thread didn't start rc=%d\n", rc); syslog(LOG_NOTICE, "my_ws_loop thread didn't start rc=%d", rc); } else { t++; } /* * Next scan the one-wire bus */ rc = pthread_create(&my_one_wire_thread, NULL, my_one_wire_loop, (void *)t ); if (rc) { fprintf(stderr, "my_one_wire_loop thread didn't start rc=%d\n", rc); syslog(LOG_NOTICE, "my_one_wire_loop thread didn't start rc=%d", rc); } else { t++; mDelay(2500); /* Wait a while to detect the devices */ } if ((rc = devices_detect())) { syslog(LOG_NOTICE, "Detected %d new devices", rc); wrconfig(); } rc = pthread_create(&my_devices_thread, NULL, my_devices_loop, (void *)t ); if (rc) { fprintf(stderr, "my_devices_loop thread didn't start rc=%d\n", rc); syslog(LOG_NOTICE, "my_devices_loop thread didn't start rc=%d", rc); } else { t++; } rc = pthread_create(&my_server_thread, NULL, my_server_loop, (void *)t ); if (rc) { fprintf(stderr, "my_server_loop thread didn't start rc=%d\n", rc); syslog(LOG_NOTICE, "my_server_loop thread didn't start rc=%d", rc); } else { t++; } rc = pthread_create(&my_panel_thread, NULL, my_panel_loop, (void *)t ); if (rc) { fprintf(stderr, "my_panel_loop thread didn't start rc=%d\n", rc); syslog(LOG_NOTICE, "my_panel_loop thread didn't start rc=%d", rc); } else { t++; } #ifdef USE_SIMULATOR rc = pthread_create(&my_simulator_thread, NULL, my_simulator_loop, (void *)t ); if (rc) { fprintf(stderr, "my_simulator_loop thread didn't start rc=%d\n", rc); syslog(LOG_NOTICE, "my_simulator_loop thread didn't start rc=%d", rc); } else { t++; } #endif /* * Initialize units for processing */ for (unit = Config.units; unit; unit = unit->next) { /* * Safety, turn everything off */ unit->mqtt_flag = unit->alarm_flag = unit->alarm_last = 0; unit->heater_state = unit->cooler_state = unit->fan_state = unit->door_state = unit->light_state = unit->light_timer = 0; unit->heater_wait = unit->cooler_wait = unit->fan_wait = unit->light_wait = 0; if (unit->mode == UNITMODE_PROFILE) { if (!unit->profile_uuid) syslog(LOG_NOTICE, "Starting unit `%s' in profile mode, no profile defined.", unit->alias); else { syslog(LOG_NOTICE, "Starting unit `%s' in profile state %s.", unit->alias, PROFSTATE[unit->prof_state]); } } else if (unit->mode == UNITMODE_BEER) { syslog(LOG_NOTICE, "Starting unit `%s' beer cooler at %.1f - %.1f degrees", unit->alias, unit->beer_set_lo, unit->beer_set_hi); } else if (unit->mode == UNITMODE_FRIDGE) { syslog(LOG_NOTICE, "Starting unit `%s' as refridgerator at %.1f - %.1f degrees", unit->alias, unit->fridge_set_lo, unit->fridge_set_hi); } else if (unit->mode == UNITMODE_NONE) { syslog(LOG_NOTICE, "Starting unit `%s' in inactive state", unit->alias); } else { syslog(LOG_NOTICE, "Starting unit `%s' in off state", unit->alias); } } publishDBirthAll(); for (unit = Config.units; unit; unit = unit->next) { if (unit->mode != UNITMODE_OFF) { unit->event_msg = xstrcpy((char *)"Startup"); publishDLog(unit); free(unit->event_msg); unit->event_msg = NULL; } } pthread_mutex_lock(&mutexes[LOCK_LCD]); lcd_buf_write(1, (char *)" ThermFerm "); lcd_buf_write(2, (char *)"Version %s ", VERSION); // 0.9.17a2 pthread_mutex_unlock(&mutexes[LOCK_LCD]); /* * Run state table */ thermferm(); /* * Stop units processing in a neat way */ for (unit = Config.units; unit; unit = unit->next) { /* * Turn everything off */ unit->heater_state = unit->cooler_state = unit->fan_state = unit->door_state = unit->light_state = unit->light_timer = 0; unit->heater_wait = unit->cooler_wait = unit->fan_wait = unit->light_wait = 0; device_out(unit->heater_address, unit->heater_state); pub_domoticz_output(unit->heater_idx, unit->heater_state); device_out(unit->cooler_address, unit->cooler_state); pub_domoticz_output(unit->cooler_idx, unit->cooler_state); device_out(unit->fan_address, unit->fan_state); pub_domoticz_output(unit->fan_idx, unit->fan_state); device_out(unit->light_address, unit->light_state); if (unit->mode != UNITMODE_OFF) { /* * If unit is active, publish we are dying. */ unit->mqtt_flag = MQTT_FLAG_DATA; publishDData(unit); publishDDeath(unit); unit->event_msg = xstrcpy((char *)"Shutdown"); publishDLog(unit); free(unit->event_msg); unit->event_msg = NULL; } syslog(LOG_NOTICE, "Unit `%s' stopped in mode %s", unit->alias, UNITMODE[unit->mode]); } syslog(LOG_NOTICE, "Out of loop, stopping threads.."); /* * Stop threads */ #ifdef USE_SIMULATOR my_simulator_shutdown = 1; while (my_simulator_state) { mDelay(50); }; #endif my_panel_shutdown = 1; while (my_panel_state) { mDelay(50); }; /* * Cancel command and shutdown via variable, one of them * will stop this thread. Includes a failsafe. */ my_server_shutdown = 1; rc = pthread_cancel(my_server_thread); rc = 0; while (my_server_state) { mDelay(50); if (rc++ > 20) { syslog(LOG_NOTICE, "Cannot terminate my_server_loop()"); break; } } my_devices_shutdown = 1; while (my_devices_state) { mDelay(50); }; my_one_wire_shutdown = 1; while (my_one_wire_state) { mDelay(50); }; my_ws_shutdown = 1; while (my_ws_state) { mDelay(50); }; mqtt_disconnect(); stopLCD(); if (sock != -1) { if (shutdown(sock, SHUT_RDWR)) { syslog(LOG_NOTICE, "Can't shutdown socket: %s", strerror(errno)); } sock = -1; } lcd_buf_reset(); wrconfig(); ulockprog((char *)"thermferm"); syslog(LOG_NOTICE, "Server process ended"); return 0; }