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.
thermferm/mqtt.c | file | annotate | diff | comparison | revisions | |
thermferm/server.c | file | annotate | diff | comparison | revisions | |
thermferm/thermferm.c | file | annotate | diff | comparison | revisions | |
thermferm/websocket.c | file | annotate | diff | comparison | revisions | |
www/css/style.css | file | annotate | diff | comparison | revisions | |
www/favicon.ico | file | annotate | diff | comparison | revisions | |
www/fermenter.php | file | annotate | diff | comparison | revisions | |
www/getfermenter.php | file | annotate | diff | comparison | revisions | |
www/images/computer.png | file | annotate | diff | comparison | revisions | |
www/includes/global.inc.php | file | annotate | diff | comparison | revisions | |
www/js/fermenter.js | file | annotate | diff | comparison | revisions |
--- a/thermferm/mqtt.c Sun Apr 14 14:41:38 2024 +0200 +++ b/thermferm/mqtt.c Mon Apr 15 17:04:57 2024 +0200 @@ -1220,8 +1220,10 @@ /* * Build and send websocket message. */ - payload = xstrcpy((char *)"{\"fermenter\":"); - payloadu = unit_data(unit, true); + payload = xstrcpy((char *)"{\"type\":\"fermenter\",\"unit\":\""); + payload = xstrcat(payload, unit->alias); + payload = xstrcat(payload, (char *)"\",\"metric\":"); + payloadu = unit_data(unit, false); payload = xstrcat(payload, payloadu); payload = xstrcat(payload, (char *)"}"); ws_broadcast(payload); @@ -1229,6 +1231,8 @@ payload = NULL; free(payloadu); payloadu = NULL; + + unit->mqtt_flag &= ~MQTT_FLAG_DATA; } @@ -1410,20 +1414,19 @@ char *payload = NULL, buf[64]; struct utsname ubuf; - payload = xstrcpy((char *)"{\"thermferm\":{"); - + payload = xstrcpy((char *)"{\"type\":\"global\",\"name\":\""); + payload = xstrcat(payload, Config.name); + payload = xstrcat(payload, (char *)"\",\"node\":\""); if (uname(&ubuf) == 0) { - payload = xstrcat(payload, (char *)"\"node\":\""); payload = xstrcat(payload, ubuf.nodename); payload = xstrcat(payload, (char *)"\",\"os\":\""); payload = xstrcat(payload, ubuf.sysname); payload = xstrcat(payload, (char *)"\",\"os_version\":\""); payload = xstrcat(payload, ubuf.release); - payload = xstrcat(payload, (char *)"\""); } else { - payload = xstrcat(payload, (char *)"\"node\":\"Unknown\",\"os\":\"Unknown\",\"os_version\":\"Unknown\""); + payload = xstrcat(payload, (char *)"Unknown\",\"os\":\"Unknown\",\"os_version\":\"Unknown"); } - payload = xstrcat(payload, (char *)",\"FW\":\""); + payload = xstrcat(payload, (char *)"\",\"FW\":\""); payload = xstrcat(payload, (char *)VERSION); payload = xstrcat(payload, (char *)"\""); @@ -1444,7 +1447,7 @@ payload = xstrcat(payload, (char *)"}"); } - payload = xstrcat(payload, (char *)"}}"); + payload = xstrcat(payload, (char *)"}"); ws_broadcast(payload); free(payload); payload = NULL;
--- a/thermferm/server.c Sun Apr 14 14:41:38 2024 +0200 +++ b/thermferm/server.c Mon Apr 15 17:04:57 2024 +0200 @@ -569,6 +569,7 @@ /* * GLOBAL GET * GLOBAL PUT + * GLOBAL JSON */ int cmd_global(int s, char *buf) { @@ -588,6 +589,7 @@ srv_send(s, (char *)"Recognized commands:"); srv_send(s, (char *)"GLOBAL GET Get global settings"); srv_send(s, (char *)"GLOBAL PUT Put global settings"); + srv_send(s, (char *)"GLOBAL JSON Get global json settings"); srv_send(s, (char *)"."); return 0; } @@ -617,6 +619,52 @@ return 0; } + if (strcmp(opt, (char *)"JSON") == 0) { + char *payload = NULL, tbuf[64]; + struct utsname ubuf; + + payload = xstrcpy((char *)"{\"type\":\"global\",\"name\":\""); + payload = xstrcat(payload, Config.name); + payload = xstrcat(payload, (char *)"\",\"node\":\""); + if (uname(&ubuf) == 0) { + payload = xstrcat(payload, ubuf.nodename); + payload = xstrcat(payload, (char *)"\",\"os\":\""); + payload = xstrcat(payload, ubuf.sysname); + payload = xstrcat(payload, (char *)"\",\"os_version\":\""); + payload = xstrcat(payload, ubuf.release); + } else { + payload = xstrcat(payload, (char *)"Unknown\",\"os\":\"Unknown\",\"os_version\":\"Unknown"); + } + payload = xstrcat(payload, (char *)"\",\"FW\":\""); + payload = xstrcat(payload, (char *)VERSION); + payload = xstrcat(payload, (char *)"\""); + + if (Config.temp_address || Config.hum_address) { + payload = xstrcat(payload, (char *)",\"THB\":{"); + if (Config.temp_address) { + payload = xstrcat(payload, (char *)"\"temperature\":"); + sprintf(tbuf, "%.1f", Config.temp_value / 1000.0); + payload = xstrcat(payload, tbuf); + } + if (Config.temp_address && Config.hum_address) + payload = xstrcat(payload, (char *)","); + if (Config.hum_address) { + payload = xstrcat(payload, (char *)"\"humidity\":"); + sprintf(tbuf, "%.1f", Config.hum_value / 1000.0); + payload = xstrcat(payload, tbuf); + } + payload = xstrcat(payload, (char *)"}"); + } + + payload = xstrcat(payload, (char *)"}"); + srv_send(s, (char *)"213 Global json data follows:"); + srv_send(s, payload); + srv_send(s, (char *)"."); + free(payload); + payload = NULL; + return 0; + } + if (strcmp(opt, (char *)"PUT") == 0) { int mqtt_reconnect = 0; while (1) { @@ -1535,13 +1583,17 @@ } if (strcmp(opt, (char *)"JSON") == 0) { + syslog(LOG_NOTICE, "UNIT JSON %s", param); for (unit = Config.units; unit; unit = unit->next) { if (strcmp(param, unit->uuid) == 0) { char *payload, *payloadu; srv_send(s, (char *)"213 Unit json data follows:"); - payload = xstrcpy((char *)"{\"fermenter\":"); - payloadu = unit_data(unit, true); + + payload = xstrcpy((char *)"{\"type\":\"fermenter\",\"unit\":\""); + payload = xstrcat(payload, unit->alias); + payload = xstrcat(payload, (char *)"\",\"metric\":"); + payloadu = unit_data(unit, false); payload = xstrcat(payload, payloadu); payload = xstrcat(payload, (char *)"}"); srv_send(s, payload);
--- a/thermferm/thermferm.c Sun Apr 14 14:41:38 2024 +0200 +++ b/thermferm/thermferm.c Mon Apr 15 17:04:57 2024 +0200 @@ -1029,7 +1029,7 @@ float LCDair, LCDbeer, LCDspL, LCDspH; unsigned char LCDstatC, LCDstatH; - unit->mqtt_flag &= ~MQTT_FLAG_DATA; +// unit->mqtt_flag &= ~MQTT_FLAG_DATA; unit->alarm_flag = 0; if (unit->air_address) { @@ -1623,7 +1623,7 @@ unit->mqtt_flag &= ~MQTT_FLAG_BIRTH; } else { publishDData(unit); - unit->mqtt_flag &= ~MQTT_FLAG_DATA; +// unit->mqtt_flag &= ~MQTT_FLAG_DATA; } if (unit->mqtt_flag & MQTT_FLAG_DEATH) { publishDDeath(unit);
--- a/thermferm/websocket.c Sun Apr 14 14:41:38 2024 +0200 +++ b/thermferm/websocket.c Mon Apr 15 17:04:57 2024 +0200 @@ -26,12 +26,17 @@ #include "thermferm.h" #include "xutil.h" +#include "devices.h" #include "websocket.h" #include <libwebsockets.h> extern sys_config Config; extern int debug; +extern const char UNITMODE[5][8]; +extern const char UNITSTAGE[4][12]; +//extern const char PROFSTATE[5][6]; +//extern const char TEMPSTATE[3][8]; int my_ws_shutdown = 0; int my_ws_state = 0; @@ -63,6 +68,157 @@ +void fermenter_ws_receive(char *buf) +{ + struct json_object *val, *val2, *jobj; + units_list *unit; + bool changed; + + jobj = json_tokener_parse(buf); + json_object_object_get_ex(jobj, "unit", &val); + + for (unit = Config.units ; unit; unit = unit->next) { + if (strcmp((char *)json_object_get_string(val), unit->alias) == 0) { + /* + * Setpoints + * {"type":"fermenter","unit":"unit0","setpoint_low":20.3,"setpoint_high":20.7} + */ + if ((unit->mode == UNITMODE_FRIDGE) || (unit->mode == UNITMODE_BEER)) { + changed = false; + if (json_object_object_get_ex(jobj, "setpoint_low", &val)) { + if (unit->PID_heat->SetP != json_object_get_double(val)) { + changed = true; + syslog(LOG_NOTICE, "ws: unit %s setpoint low from %.1f to %.1f", unit->alias, unit->PID_heat->SetP, json_object_get_double(val)); + unit->PID_heat->SetP = json_object_get_double(val); + } + } + if (json_object_object_get_ex(jobj, "setpoint_high", &val)) { + if (unit->PID_cool->SetP != json_object_get_double(val)) { + changed = true; + syslog(LOG_NOTICE, "ws: unit %s setpoint high from %.1f to %.1f", unit->alias, unit->PID_cool->SetP, json_object_get_double(val)); + unit->PID_cool->SetP = json_object_get_double(val); + } + } + if (changed) { + if (unit->mode == UNITMODE_FRIDGE) { + unit->fridge_set_lo = unit->PID_heat->SetP; + unit->fridge_set_hi = unit->PID_cool->SetP; + } else { + unit->beer_set_lo = unit->PID_heat->SetP; + unit->beer_set_hi = unit->PID_cool->SetP; + } + unit->mqtt_flag |= MQTT_FLAG_DATA; + break; + } + } + + /* + * Unit mode + * {"type":"fermenter","unit":"unit0","mode":"NONE"} + */ + if (json_object_object_get_ex(jobj, "mode", &val)) { + for (int i = 0; i < 5; i++) { + if (strcmp((char *)json_object_get_string(val), UNITMODE[i]) == 0) { + if (unit->mode != i) { + unit->mqtt_flag |= MQTT_FLAG_DATA; + /* Initialize log if the unit is turned on */ + if ((unit->mode == UNITMODE_OFF) && (i != UNITMODE_OFF)) { + unit->mqtt_flag |= MQTT_FLAG_BIRTH; + } + if (i == UNITMODE_PROFILE) { + /* Do some checks and refuse profile mode cannot be set */ + if (unit->profile_uuid == NULL) { + syslog(LOG_NOTICE, "ws: unit %s refuse mode profile, not loaded", unit->alias); + break; + } + } + syslog(LOG_NOTICE, "ws: unit %s mode to %s", unit->alias, UNITMODE[i]); + unit->mode = i; + if ((unit->mode != UNITMODE_OFF) && ! unit->event_msg) + unit->event_msg = xstrcpy((char *)UNITMODE[i]); + /* Allways turn everything off after a mode change */ + unit->PID_cool->OutP = unit->PID_heat->OutP = 0.0; + unit->PID_cool->Mode = unit->PID_heat->Mode = PID_MODE_NONE; + unit->heater_state = unit->cooler_state = unit->fan_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); + device_out(unit->cooler_address, unit->cooler_state); + device_out(unit->fan_address, unit->fan_state); + device_out(unit->light_address, unit->light_state); + if (unit->mode == UNITMODE_PROFILE) { + /* + * Set a sane default until it will be overruled by the + * main processing loop. + */ + unit->prof_target_lo = unit->profile_inittemp_lo; + unit->prof_target_hi = unit->profile_inittemp_hi;; + unit->prof_fridge_mode = 0; + unit->prof_state = PROFILE_OFF; + unit->prof_started = unit->prof_paused = unit->prof_primary_done = 0; + unit->prof_peak_abs = unit->prof_peak_rel = 0.0; + } + } + break; + } + } + } + + /* + * Unit stage + * {"type":"fermenter","unit":"unit0","stage":"SECONDARY"} + */ + if (json_object_object_get_ex(jobj, "stage", &val)) { + for (int i = 0; i < 4; i++) { + if (strcmp((char *)json_object_get_string(val), UNITSTAGE[i]) == 0) { + if (unit->stage != i) { + syslog(LOG_NOTICE, "DCMD change fermenter %s: stage to %s", unit->alias, UNITSTAGE[i]); + unit->mqtt_flag |= MQTT_FLAG_DATA; + unit->stage = i; + if ((unit->mode != UNITMODE_OFF) && ! unit->event_msg) + unit->event_msg = xstrcpy((char *)UNITSTAGE[i]); + } + break; + } + } + } + + /* + * Unit heater and cooler switch + * {"type":"fermenter","unit":"unit0","heater_state":100,"cooler_state":0} + */ + if ((json_object_object_get_ex(jobj, "heater_state", &val)) && + (json_object_object_get_ex(jobj, "cooler_state", &val2)) && + (unit->mode == UNITMODE_NONE)) { + if (json_object_get_int(val) != unit->heater_state) + unit->heater_state = json_object_get_int(val); + if (json_object_get_int(val2) != unit->cooler_state) + unit->cooler_state = json_object_get_int(val2); + if (unit->heater_state || unit->cooler_state) + unit->heater_state = unit->cooler_state = 0; // Safety + unit->mqtt_flag |= MQTT_FLAG_DATA; + syslog(LOG_NOTICE, "ws: unit %s heater_state to %d, cooler_state to %d", unit->alias, unit->heater_state, unit->cooler_state); + break; + } + + /* + * Unit fan switch + * {"type":"fermenter","unit":"unit0","fan_state":0} + */ + if ((json_object_object_get_ex(jobj, "fan_state", &val)) && (unit->mode == UNITMODE_NONE)) { + if (json_object_get_int(val) != unit->fan_state) + unit->fan_state = json_object_get_int(val); + unit->mqtt_flag |= MQTT_FLAG_DATA; + syslog(LOG_NOTICE, "ws: unit %s fan_state to %d", unit->alias, unit->fan_state); + break; + } + + return; + } + } + syslog(LOG_NOTICE, "fermenter_ws_receive(%s)", buf); +} + + static int callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__lws_mirror *pss = (struct per_session_data__lws_mirror *)user; @@ -123,15 +279,15 @@ * {"node":"rpi01","group_id":"fermenters","control":"reboot"} * {"node":"rpi01","group_id":"fermenters","control":"rebirth"} */ -// if (strncmp(buf, (char *)"{\"device\":\"fermenters\",", 23) == 0) { -// fermenter_ws_receive(buf); + if (strncmp(buf, (char *)"{\"type\":\"fermenter\",", 20) == 0) { + fermenter_ws_receive(buf); // } else if (strncmp(buf, (char *)"{\"device\":\"co2meters\",", 22) == 0) { // co2meter_ws_receive(buf); // } else if (strncmp(buf, (char *)"{\"device\":\"ispindels\",", 22) == 0) { // ispindel_ws_receive(buf); // } else if (strncmp(buf, (char *)"{\"node\":\"", 9) == 0) { // node_ws_receive(buf); -// } + } break;
--- a/www/css/style.css Sun Apr 14 14:41:38 2024 +0200 +++ b/www/css/style.css Mon Apr 15 17:04:57 2024 +0200 @@ -29,3 +29,227 @@ } +#fermenter_table { + width: 960px; + height: 210px; + background: #252526; + margin: 5px; + border: 2px solid; + border-color: #59b4d4; + border-radius: 5px 5px 5px 5px; +} + + +/* + * +----------------------------------------------------+ + * |+-------------------++--------++-------------------+| + * || || || || + * || || chiller|| || + * || || || || + * || Air temp |+--------+| Beer temp || + * || Temperature | | Pressure || + * || | | || + * || | | || + * || | | || + * |+-------------------+ +-------------------+| + * +----------------------------------------------------+ + */ + +#fermenter_thermometers { + width: 960px; + height: 390px; + float: left; + background-color: #252526; + margin: 5px; + margin-top: 3px; + border: 2px solid; + border-color: #59b4d4; + border-radius: 5px 5px 5px 5px; +} + + +/* + * +----------panel_top------------+ + * | door light alarm power LEDs | + * | | + * +-------------------------------+ + * +---------panel_display---------+ + * |+-------------++--------------+| + * || display 1 || display 2 || + * |+-------------++--------------+| + * +-------------------------------+ + * +---------panel_control---------+ + * |+--------++---------++--------+| + * || led 1 || led 2 || led 3 || + * |+--------++---------++--------+| + * |+--------++---------++--------+| + * || sw 1 || sw 2 || sw 3 || + * |+--------++---------++--------+| + * +-------------------------------+ + * +--------panel_buttons----------+ + * | | + * +-------------------------------+ + */ +#fermenter_panel_top { + width: 290px; + height: 100px; + float: right; + margin: 5px; + background-color: #252526; + border: 2px solid; + border-color: #59b4d4; + border-radius: 5px 5px 5px 5px; +} + +#fermenter_doorled, +#fermenter_lightled, +#fermenter_alarmled, +#fermenter_powerled { + width: 50px; + height: 30px; + float: left; + text-align: center; + margin-top: 15px; + margin-left: 20px; +} + +#fermenter_panel_display { + width: 290px; + height: 98px; + float: right; + margin: 5px; + margin-top: 3px; + background-color: #252526; + border: 2px solid; + border-color: #59b4d4; + border-radius: 5px 5px 5px 5px; +} + +#fermenter_display { + width: 145px; + height: 98px; + float: left; + text-align: center; +} + + +#fermenter_panel_control { + width: 290px; + height: 150px; + float: right; + margin: 5px; + margin-top: 3px; + background-color: #252526; + border: 2px solid; + border-color: #59b4d4; + border-radius: 5px 5px 5px 5px; +} + +#fermenter_led1, +#fermenter_led2, +#fermenter_led3 { + width: 96px; + height: 30px; + float: left; + text-align: center; + margin-top: 13px; +} + +#fermenter_toggle1 { + float: left; + margin-left: 29px; + margin-top: 20px; +} + +#fermenter_toggle2, +#fermenter_toggle3 { + float: left; + margin-left: 60px; + margin-top: 20px; +} + +#fermenter_panel_buttons { + width: 290px; + height: 227px; + float: right; + margin: 5px; + margin-top: 3px; + background-color: #252526; + border: 2px solid; + border-color: #59b4d4; + border-radius: 5px 5px 5px 5px; +} + +.LEDred_on { + margin: 5px auto; + width: 18px; + height: 18px; + background-color: #F40; + border-radius: 50%; + box-shadow: #000 0 0px 4px 1px, inset #C33 0 -1px 5px, #f44 0 2px 12px; +} + +.LEDred_off { + margin: 5px auto; + width: 18px; + height: 18px; + background-color: #820; + border-radius: 50%; + box-shadow: #400 0 0px 1px 1px; +} + +.LEDyellow_on { + margin: 5px auto; + width: 18px; + height: 18px; + background-color: #FF0; + border-radius: 50%; + box-shadow: #000 0 0px 4px 1px, inset #860 0 -1px 5px, #DD0 0 2px 12px; +} + +.LEDyellow_off { + margin: 5px auto; + width: 18px; + height: 18px; + background-color: #A90; + border-radius: 50%; + box-shadow: #440 0 0px 1px 1px; +} + +.LEDgreen_on { + margin: 5px auto; + width: 18px; + height: 18px; + background-color: #5E0; + border-radius: 50%; + box-shadow: #000 0 0px 4px 1px, inset #270 0 -1px 5px, #5D0 0 2px 12px; +} + +.LEDgreen_off { + margin: 5px auto; + width: 18px; + height: 18px; + background-color: #270; + border-radius: 50%; + box-shadow: #250 0 0px 1px 1px; +} + +.LEDblue_on { + margin: 5px auto; + width: 18px; + height: 18px; + background-color: #4AF; + border-radius: 50%; + box-shadow: #000 0 0px 4px 1px, inset #247 0 -1px 5px, #48F 0 2px 12px; +} + +.LEDblue_off { + margin: 5px auto; + width: 18px; + height: 18px; + background-color: #137; + border-radius: 50%; + box-shadow: #024 0 0px 1px 1px; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/www/fermenter.php Mon Apr 15 17:04:57 2024 +0200 @@ -0,0 +1,109 @@ +<?php +require_once($_SERVER['DOCUMENT_ROOT'].'/includes/global.inc.php'); +page_header('Monitor Fermentation', 'fermenter'); +?> + + <div id="fermenter"> + + <div style="float: left; width: 970px;"> + <div id="fermenter_table"> + <div id="fermenter_info"> + <table style='width: 100%; padding: 10px;'> + <col width="20%"> + <col width="40%"> + <col width="40%"> + <tr><th colspan=3>Fermenter overview</th></tr> + <tr style="height: 25px;"> + <td>Room TH</td> + <td colspan="2"><div id="room_thb"></div></td> + </tr> + <tr style="height: 25px;"> + <td>Fermenter unit</td> + <td colspan="2"><div id="info_system"></div></td> + </tr> + <tr style="height: 25px;"> + <td>Code and beer</td> + <td colspan="2"><div id="info_beer"></div></td> + </tr> + <tr style="height: 25px;"> + <td>Working mode</td> + <td><div id="info_mode"></div></td> + <td><input type="button" id="Profile1" value="Profile 1" /><input style="margin-left: 10px;" type="button" id="Profile2" value="Profile 2" /></td> + </tr> + <tr style="height: 25px;"> + <td>Fermentation fase</td> + <td colspan="2"><div id="info_stage"></div></td> + </tr> + <tr style="height: 25px;"> + <td>Fermentation profile</td> + <td><div id="info_profile"></div></td> + <td><div id="status_profile"></div></td> + </tr> + </table> + </div> + </div> + <div id='fermenter_thermometers'> + <div id="gaugeContainer_air" style='float: left; margin-top: 10px; margin-left: 10px;'></div> + <div id="gaugeContainer_beer" style="float: right; margin-top: 10px; margin-right: 10px;"></div> + <div id="gaugeContainer_chiller" style="float: left; margin-top: 15px;"></div> + </div> + </div> <!-- End left side --> + + <div style="float: right; width: 295px;"> + <div id="fermenter_panel_top"> + <div id="fermenter_doorled"></div> + <div id="fermenter_lightled"></div> + <div id="fermenter_alarmled"></div> + <div id="fermenter_powerled"></div> + </div> <!-- fermenter_panel_top --> + + <div id="fermenter_panel_display"> + <div id="fermenter_display" class="f_display"> + <div id="target_lo" style="margin-left: 40px; margin-top: 15px;"></div> + <div style="margin-top: 5px;">°C low</div> + </div> + <div id="fermenter_display" class="f_display"> + <div id="target_hi" style="margin-left: 40px; margin-top: 15px;"></div> + <div style="margin-top: 5px;">°C high</div> + </div> + </div> <!-- fermenter_panel_display --> + + <div id="fermenter_panel_control"> + <div class="f_control_leds"> + <div id="fermenter_led1"></div> + <div id="fermenter_led2"></div> + <div id="fermenter_led3"></div> + </div> + <div class="f_control_switches"> + <div id="fermenter_toggle1"></div> + <div id="fermenter_toggle2"></div> + <div id="fermenter_toggle3"></div> + </div> + </div> <!-- fermenter_panel_control --> + + <div id="fermenter_panel_buttons"> + </div> <!-- fermenter_panel_buttons --> + </div> <!-- End right side --> + </div> + + <div id="eventWindow"> + <div> + Confirm abort profile + </div> + <div> + <div> + Press "OK" to abort this profile. When started it will start from the beginning.<br> + Press "Cancel" to close without aborting the profile. + </div> + <div> + <div style="float: right; margin-top: 40px; margin-bottom: 10px;"> + <input type="button" id="delOk" value="OK" style="margin-right: 90px" /> + <input type="button" id="delCancel" value="Cancel" style="margin-right: 80px" /> + </div> + </div> + </div> + </div> + +<?php +page_footer(); +?>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/www/getfermenter.php Mon Apr 15 17:04:57 2024 +0200 @@ -0,0 +1,65 @@ +<?php + + +function open_socket() +{ + $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + + if (!($sock === false)) { + if (socket_connect($sock, "localhost", 6554)) { + socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, array('sec' => 15, 'usec' => 0)); + } else { + socket_close($sock); + } + } + return $sock; +} + + +/** + * @param string $command to send to the server. + * @return string with the complete reply from the + * server. This can be a multiline reply. + */ +function send_cmd($command) +{ + $sock = open_socket(); + if ($sock == false) { + return ""; + } + socket_write($sock, $command . "\r\n", 4096); + + $answer = ""; + while (1) { + $line = socket_read($sock, 4096); + if ($line === '') + break; + $answer .= $line; + } + socket_close($sock); + + return $answer; +} + + +function startsWith($haystack, $needle) +{ + return !strncmp($haystack, $needle, strlen($needle)); +} + + +if (isset($_GET["uuid"])) + $uuid = $_GET["uuid"]; +else + $uuid = "e53858c3-a97e-4b07-85e9-524257909f45"; + +$answer = send_cmd("UNIT JSON ".$uuid); +header("Content-type: application/json"); + +$arr = explode("\r\n", $answer); +if (startsWith($arr[0], "213")) { + echo $arr[1]; +} else { + echo '{}'; +} +
--- a/www/includes/global.inc.php Sun Apr 14 14:41:38 2024 +0200 +++ b/www/includes/global.inc.php Mon Apr 15 17:04:57 2024 +0200 @@ -27,6 +27,11 @@ //$my_style = 'ui-redmond'; $my_style = 'ui-mbse'; +if (isset($_GET['uuid'])) + $my_uuid = $_GET['uuid']; +else + $my_uuid = ''; + require_once($_SERVER['DOCUMENT_ROOT'].'/version.php'); @@ -80,6 +85,7 @@ function page_header($title, $loadjs) { global $my_style; global $my_version; + global $my_uuid; ?> <!DOCTYPE html> <html> @@ -91,7 +97,7 @@ <link type="text/css" href="jqwidgets/styles/jqx.<?php echo $my_style; ?>.css" rel="stylesheet" /> <meta name="viewport" content="width=device-width, initial-scale=1"> <script> - var theme = "<?php echo $my_style; ?>"; + var theme = "<?php echo $my_style; ?>", my_uuid = "<?php echo $my_uuid; ?>"; </script> <script src="js/jquery-1.11.1.js"></script> <script src="js/reconnecting-websocket.min.js"></script> @@ -99,8 +105,25 @@ <script src="jqwidgets/jqxwindow.js"></script> <script src="jqwidgets/jqxmenu.js"></script> <script src="jqwidgets/jqxtabs.js"></script> + <script src="jqwidgets/jqxdata.js"></script> + <script src="jqwidgets/jqxdata.export.js"></script> <script src="jqwidgets/jqxloader.js"></script> <script src="jqwidgets/jqxbuttons.js"></script> + <script src="jqwidgets/jqxscrollbar.js"></script> + <script src="jqwidgets/jqxnumberinput.js"></script> + <script src="jqwidgets/jqxlistbox.js"></script> + <script src="jqwidgets/jqxdropdownlist.js"></script> + <script src="jqwidgets/jqxdropdownbutton.js"></script> + <script src="jqwidgets/jqxinput.js"></script> + <script src="jqwidgets/jqxeditor.js"></script> + <script src="jqwidgets/jqxtooltip.js"></script> + <script src="jqwidgets/jqxswitchbutton.js"></script> + <script src="jqwidgets/jqxradiobutton.js"></script> + <script src="jqwidgets/jqxcheckbox.js"></script> + + <script src="jqwidgets/jqxgauge.js"></script> + + <script src="jqwidgets/jqxdraw.js"></script> <script src="jqwidgets/globalization/globalize.js"></script> <script src="js/global.js"></script> @@ -132,8 +155,8 @@ if (strcmp($arr[$i], ".") == 0) break; $parts = explode(",", $arr[$i]); - echo ' <li><img style="float: left; margin-right: 5px;" src="images/fridge.png" />'; - echo '<a href="fermenter.php?uuid='.$parts[0].'">'.$parts[1].'</a></li>'.PHP_EOL; + echo " <li><img style='float: left; margin-right: 5px;' src='images/fridge.png' />"; + echo "<a href='fermenter.php?uuid=".$parts[0]."'>".$parts[1]."</a></li>".PHP_EOL; $i++; } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/www/js/fermenter.js Mon Apr 15 17:04:57 2024 +0200 @@ -0,0 +1,494 @@ +/***************************************************************************** + * Copyright (C) 2024 + * + * Michiel Broek <mbroek at mbse dot eu> + * + * This file is part of mbsePi-apps thermferm + * + * 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. + * + * Brewery Management System istributed 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. + *****************************************************************************/ + +function createAbortElements() { + $('#eventWindow').jqxWindow({ + theme: theme, + position: { x: 440, y: 210 }, + width: 400, + height: 200, + resizable: false, + isModal: true, + modalOpacity: 0.4, + okButton: $('#delOk'), + cancelButton: $('#delCancel'), + initContent: function() { + $('#delOk').jqxButton({ template: 'danger', width: '65px', theme: theme }); + $('#delCancel').jqxButton({ template: 'success', width: '65px', theme: theme }); + $('#delCancel').focus(); + } + }); + $('#eventWindow').jqxWindow('hide'); +} + + +$(document).ready(function() { + + var record = {}, + global = {}, + blank = {}, + ppayload = '', + yl = 12, // Normal yeast temp range + yh = 24, + + gaugeoptions = { + min: 0, max: 45, width: 375, height: 375, + ranges: [{ startValue: 0, endValue: yl, style: { fill: '#3399FF', stroke: '#3399FF' }, endWidth: 10, startWidth: 10 }, + { startValue: yl, endValue: yh, style: { fill: '#00CC33', stroke: '#00CC33' }, endWidth: 10, startWidth: 10 }, + { startValue: yh, endValue: 45, style: { fill: '#FC6A6A', stroke: '#FC6A6A' }, endWidth: 10, startWidth: 10 }], + ticksMinor: { interval: 1, size: '5%' }, + ticksMajor: { interval: 5, size: '9%' }, + labels: { interval: 5 }, + style: { fill: '#eeeeee', stroke: '#666666' }, + value: 0, + colorScheme: 'scheme05' + }, + gaugeSmalloptions = { + min: -15, max: 25, width: 190, height: 190, + ranges: [{ startValue: -15, endValue: 0, startWidth: 5, endWidth: 5, style: { fill: '#3399FF', stroke: '#3399FF' }}, + { startValue: 0, endValue: 10, startWidth: 5, endWidth: 5, style: { fill: '#00CC33', stroke: '#00CC33' }}, + { startValue: 10, endValue: 25, startWidth: 5, endWidth: 5, style: { fill: '#FC6A6A', stroke: '#FC6A6A' }}], + ticksMinor: { interval: 1, size: '5%' }, + ticksMajor: { interval: 5, size: '9%' }, + labels: { interval: 5 }, + style: { fill: '#eeeeee', stroke: '#666666' }, + value: 0, + colorScheme: 'scheme05', + caption: { value: 'Chiller', position: 'bottom', offset: [0, 10] } + }, + switchoptions = { + height: 68, + width: 35, + onLabel: 'ON', + offLabel: 'OFF', + theme: theme, + thumbSize: '50%', + orientation: 'vertical' + }, + targetoptions = { inputMode: 'simple', theme: theme, width: 70, min: 0, max: 45, decimalDigits: 1, spinButtons: true }, + + globalSource = { + datatype: 'json', + cache: false, + datafields: [ + { name: 'type' }, + { name: 'name' }, + { name: 'node' }, + { name: 'os' }, + { name: 'os_version' }, + { name: 'FW' }, + { name: 'room_temp', map: 'THB>temperature', type: 'float' }, + { name: 'room_hum', map: 'THB>humidity', type: 'float' } + ], + id: 'name', + url: 'getglobal.php' + }, + globalData = new $.jqx.dataAdapter(globalSource, { + loadComplete: function(records) { + global = globalData.records[0]; + updateScreen(); + } + }), + url = 'getfermenter.php?uuid=' + my_uuid, + source = { + datatype: 'json', + datafields: [ + { name: 'type' }, + { name: 'unit' }, + { name: 'beercode', map: 'metric>product>code' }, + { name: 'beername', map: 'metric>product>name' }, + { name: 'yeast_lo', map: 'metric>product>yeast_lo' }, + { name: 'yeast_hi', map: 'metric>product>yeast_hi' }, + { name: 'air_state', map: 'metric>air>state' }, + { name: 'air_temperature', map: 'metric>air>temperature' }, + { name: 'beer_state', map: 'metric>beer>state' }, + { name: 'beer_temperature', map: 'metric>beer>temperature' }, + { name: 'chiller_state', map: 'metric>chiller>state' }, + { name: 'chiller_temperature', map: 'metric>chiller>temperature' }, + { name: 'heater_state', map: 'metric>heater>state' }, + { name: 'heater_usage', map: 'metric>heater>usage' }, + { name: 'cooler_state', map: 'metric>cooler>state' }, + { name: 'cooler_usage', map: 'metric>cooler>usage' }, + { name: 'fan_state', map: 'metric>fan>state' }, + { name: 'fan_usage', map: 'metric>fan>usage' }, + { name: 'light_address', map: 'metric>light>address' }, + { name: 'light_state', map: 'metric>light>state' }, + { name: 'light_usage', map: 'metric>light>usage' }, + { name: 'door_address', map: 'metric>door>address' }, + { name: 'door_state', map: 'metric>door>state' }, + { name: 'psu_address', map: 'metric>psu>address' }, + { name: 'psu_state', map: 'metric>psu>state' }, + { name: 'mode', map: 'metric>mode' }, + { name: 'alarm', map: 'metric>alarm', type: 'int' }, + { name: 'setpoint_high', map: 'metric>setpoint>high' }, + { name: 'setpoint_low', map: 'metric>setpoint>low' }, + { name: 'profile_uuid', type: 'string' }, + { name: 'profile_name', type: 'string' }, + { name: 'profile_state', type: 'string' }, + { name: 'profile_percent', type: 'int' }, + { name: 'profile_inittemp_high', type: 'float' }, + { name: 'profile_inittemp_low', type: 'float' }, + { name: 'profile_steps', type: 'string' }, + { name: 'stage', map: 'metric>stage', type: 'string' }, + { name: 'beeruuid', map: 'metric>product>uuid' } + ], + id: 'alias', + url: url + }, + dataAdapter = new $.jqx.dataAdapter(source, { + loadComplete: function(records) { + record = dataAdapter.records[0]; + updateScreen(); + } + }); + + function updateScreen() { + $('#room_thb').html(global.room_temp + '°C ' + global.room_hum + '% humidity'); + $('#info_system').html(record.unit); + $('#info_beer').html(record.beercode + ' - ' + record.beername); + $('#info_mode').jqxDropDownList('selectItem', record.mode); + $('#info_stage').jqxDropDownList('selectItem', record.stage); + if (record.door_address) { + if (record.door_state != '0') { + $('#fermenter_doorled').html('<div class="LEDyellow_on"></div>Door'); + } else { + $('#fermenter_doorled').html('<div class="LEDyellow_off"></div>Door'); + } + } + if (record.light_address) { + if (record.light_state != '0') { + $('#fermenter_lightled').html('<div class="LEDyellow_on"></div>Light'); + } else { + $('#fermenter_lightled').html('<div class="LEDyellow_off"></div>Light'); + } + } + if (record.mode != 'OFF') { + $('#fermenter_powerled').html('<div class="LEDblue_on"></div>Power'); + } else { + $('#fermenter_powerled').html('<div class="LEDblue_off"></div>Power'); + } + if (record.alarm != '0') { + $('#fermenter_alarmled').html('<div class="LEDred_on"></div>Alarm'); + } else { + $('#fermenter_alarmled').html('<div class="LEDred_off"></div>Alarm'); + } + + $('#target_lo').val(record.setpoint_low); + $('#target_hi').val(record.setpoint_high); + if ((record.mode == 'FRIDGE') || (record.mode == 'BEER')) { + $('#target_lo').jqxNumberInput({ readOnly: false, Width: 70, spinButtons: true }); + $('#target_hi').jqxNumberInput({ readOnly: false, Width: 70, spinButtons: true }); + } else { + $('#target_lo').jqxNumberInput({ readOnly: true, Width: 50, spinButtons: false }); + $('#target_hi').jqxNumberInput({ readOnly: true, Width: 50, spinButtons: false }); + } + + $('.f_control_leds').show(); + if (record.heater_state != '0') { + $('#fermenter_led1').html('<div class="LEDgreen_on"></div>Heat'); + } else { + $('#fermenter_led1').html('<div class="LEDgreen_off"></div>Heat'); + } + if (record.cooler_state != '0') { + $('#fermenter_led2').html('<div class="LEDgreen_on"></div>Cool'); + } else { + $('#fermenter_led2').html('<div class="LEDgreen_off"></div>Cool'); + } + if (record.fan_state != '0') { + $('#fermenter_led3').html('<div class="LEDgreen_on"></div>Fan'); + } else { + $('#fermenter_led3').html('<div class="LEDgreen_off"></div>Fan'); + } + + if (record.mode == 'NONE') { + $('.f_control_switches').show(); + } else { + $('.f_control_switches').hide(); + } + if ((record.heater_state != '0') != $('#fermenter_toggle1').jqxSwitchButton('val')) + $('#fermenter_toggle1').val(record.heater_state != '0'); + if ((record.cooler_state != '0') != $('#fermenter_toggle2').jqxSwitchButton('val')) + $('#fermenter_toggle2').val(record.cooler_state != '0'); + if ((record.fan_state != '0') != $('#fermenter_toggle3').jqxSwitchButton('val')) + $('#fermenter_toggle3').val(record.fan_state != '0'); + + $('#info_profile').html(record.profile_name); + if (record.profile_name == '') + $('#info_mode').jqxDropDownList('disableItem', 'PROFILE'); + else + $('#info_mode').jqxDropDownList('enableItem', 'PROFILE'); + + if (record.mode == 'PROFILE') { + if (record.profile_state == 'OFF') { + $('#info_mode').jqxDropDownList({ disabled: false }); + $('#Profile1').jqxButton({ template: 'success', value: 'Starten' }); + $('#Profile1').show(); + $('#Profile2').hide(); + $('#status_profile').html(''); + } else if (record.profile_state == 'RUN') { + $('#info_mode').jqxDropDownList({ disabled: true }); + $('#Profile1').jqxButton({ template: 'danger', value: 'Afbreken' }); + $('#Profile2').jqxButton({ template: 'primary', value: 'Pauze' }); + $('#Profile1').show(); + $('#Profile2').show(); + $('#status_profile').html('Profiel actief, ' + record.profile_percent + '% gereed'); + } else if (record.profile_state == 'PAUSE') { + $('#info_mode').jqxDropDownList({ disabled: true }); + $('#Profile1').jqxButton({ template: 'danger', value: 'Afbreken' }); + $('#Profile2').jqxButton({ template: 'success', value: 'Doorgaan' }); + $('#Profile1').show(); + $('#Profile2').show(); + $('#status_profile').html('Profiel pauze, ' + record.profile_percent + '% gereed'); + } else if (record.profile_state == 'DONE') { + $('#info_mode').jqxDropDownList({ disabled: true }); + $('#Profile1').jqxButton({ template: 'primary', value: 'Profiel Ok' }); + $('#Profile1').show(); + $('#Profile2').hide(); + $('#status_profile').html('Profiel is gereed'); + } + } else { + $('#info_mode').jqxDropDownList({ disabled: false }); + $('#Profile1').hide(); + $('#Profile2').hide(); + $('#status_profile').html(''); + } + + var yl = record.yeast_lo; + var yh = record.yeast_hi; + var range = { ranges: [{ startValue: 0, endValue: yl, style: { fill: '#3399FF', stroke: '#3399FF' }, endWidth: 10, startWidth: 10 }, + { startValue: yl, endValue: yh, style: { fill: '#00CC33', stroke: '#00CC33' }, endWidth: 10, startWidth: 10 }, + { startValue: yh, endValue: 45, style: { fill: '#FC6A6A', stroke: '#FC6A6A' }, endWidth: 10, startWidth: 10 }]}; + $('#gaugeContainer_air').jqxGauge(range); + $('#gaugeContainer_beer').jqxGauge(range); + + if (record.air_temperature !== undefined) { + $('#gaugeContainer_air').jqxGauge({ caption: { value: 'Air: ' + record.air_temperature.toFixed(3) }}); + $('#gaugeContainer_air').jqxGauge({ value: record.air_temperature }); + } + if (record.air_state == 'OK') { + $('#gaugeContainer_air').jqxGauge({ disabled: false }); + } else { + $('#gaugeContainer_air').jqxGauge({ disabled: true }); + } + if (record.beer_temperature !== undefined) { + $('#gaugeContainer_beer').jqxGauge({ caption: { value: 'Beer: ' + record.beer_temperature.toFixed(3) }}); + $('#gaugeContainer_beer').jqxGauge({ value: record.beer_temperature }); + } + if (record.beer_state == 'OK') { + $('#gaugeContainer_beer').jqxGauge({ disabled: false }); + } else { + $('#gaugeContainer_beer').jqxGauge({ disabled: true }); + } + if (record.chiller_temperature !== undefined) { + $('#gaugeContainer_chiller').jqxGauge({ value: record.chiller_temperature }); + } + if (record.chiller_state == 'OK') { + $('#gaugeContainer_chiller').jqxGauge({ disabled: false }); + } else { + $('#gaugeContainer_chiller').jqxGauge({ disabled: true }); + } + } + + $('#gaugeContainer_air').jqxGauge(gaugeoptions); + $('#gaugeContainer_air').jqxGauge({ caption: { value: 'Air: 00.000' }}); + $('#gaugeContainer_beer').jqxGauge(gaugeoptions); + $('#gaugeContainer_beer').jqxGauge({ caption: { value: 'Beer: 00.000' }}); + $('#gaugeContainer_chiller').jqxGauge(gaugeSmalloptions); + + $('#fermenter_toggle1').jqxSwitchButton(switchoptions); + $('#fermenter_toggle2').jqxSwitchButton(switchoptions); + $('#fermenter_toggle3').jqxSwitchButton(switchoptions); + + srcMode = ['OFF', 'NONE', 'FRIDGE', 'BEER', 'PROFILE']; + srcStage = ['PRIMARY', 'SECONDARY', 'TERTIARY', 'CARBONATION']; + $('#info_mode').jqxDropDownList({ theme: theme, source: srcMode, width: 100, height: 24, dropDownHeight: 156 }); + $('#info_stage').jqxDropDownList({ theme: theme, source: srcStage, width: 150, height: 24, dropDownHeight: 125 }); + + $('#target_lo').jqxNumberInput(targetoptions); + $('#target_hi').jqxNumberInput(targetoptions); + + $('#Profile1').jqxButton({ template: 'info', width: '150px', height: 24, theme: theme }); + $('#Profile2').jqxButton({ template: 'info', width: '150px', height: 24, theme: theme }); + $('#Profile1').hide(); // Hide these until they are needed. + $('#Profile2').hide(); + + // Get the data immediatly and then at regular intervals to refresh. + dataAdapter.dataBind(); + globalData.dataBind(); + + $('#info_mode').on('select', function(event) { + if (event.args && event.args.item.value != record.mode) { + record.mode = event.args.item.value; + console.log('set mode ' + record.mode); + var msg = '{"type":"fermenter","unit":"' + record.unit + '","mode":"' + record.mode + '"}'; + websocket.send(msg); + } + }); + $('#info_stage').on('select', function(event) { + if (event.args && event.args.item.value != record.stage) { + record.stage = event.args.item.value; + console.log('set stage ' + record.stage); + var msg = '{"type":"fermenter","unit":"' + record.unit + '","stage":"' + record.stage + '"}'; + websocket.send(msg); + } + }); + + $('#target_lo').on('change', function(event) { + record.setpoint_low = parseFloat(event.args.value); + // Keep the high target above the low. + if (record.setpoint_low > record.setpoint_high) { + record.setpoint_high = record.setpoint_low; + $('#target_hi').val(record.setpoint_high); + } + console.log('set setpoints ' + record.setpoint_low + ' ' + record.setpoint_high); + websocket.send('{"type":"fermenter","unit":"' + record.unit + + '","setpoint_low":' + record.setpoint_low + ',"setpoint_high":' + record.setpoint_high + '}'); + }); + $('#target_hi').on('change', function(event) { + record.setpoint_high = parseFloat(event.args.value); + // Keep the low target below the high. + if (record.setpoint_high < record.setpoint_low) { + record.setpoint_low = record.setpoint_high; + $('#target_lo').val(record.setpoint_low); + } + console.log('set setpoints ' + record.setpoint_low + ' ' + record.setpoint_high); + websocket.send('{"type":"fermenter","unit":"' + record.unit + + '","setpoint_low":' + record.setpoint_low + ',"setpoint_high":' + record.setpoint_high + '}'); + }); + + $('#fermenter_toggle1').on('checked', function(event) { + if (record.mode == 'NONE' && record.heater_state != 0) { + console.log('set heater ' + $("#fermenter_toggle1").jqxSwitchButton('val')); + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","heater_state":0}'); + } + }); + $('#fermenter_toggle1').on('unchecked', function(event) { + if (record.mode == 'NONE' && record.heater_state == 0) { + console.log('set heater ' + $("#fermenter_toggle1").jqxSwitchButton('val')); + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","heater_state":100,"cooler_state":0}'); + } + }); + $('#fermenter_toggle2').on('checked', function(event) { + if (record.mode == 'NONE' && record.cooler_state != 0) { + console.log('set cooler ' + $("#fermenter_toggle2").jqxSwitchButton('val')); + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","cooler_state":0}'); + } + }); + $('#fermenter_toggle2').on('unchecked', function(event) { + if (record.mode == 'NONE' & record.cooler_state == 0) { + console.log('set cooler ' + $("#fermenter_toggle2").jqxSwitchButton('val')); + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","cooler_state":100,"heater_state":0}'); + } + }); + $('#fermenter_toggle3').on('checked', function(event) { + if (record.mode == 'NONE' && record.fan_state != 0) { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","fan_state":0}'); + } + }); + $('#fermenter_toggle3').on('unchecked', function(event) { + if (record.mode == 'NONE' && record.fan_state == 0) { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","fan_state":100}'); + } + }); + $('#Profile1').click(function() { + if (record.mode == 'PROFILE') { + if (record.profile_state == 'OFF') { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","profile":{"command":"start"}}'); + } else if ((record.profile_state == 'RUN') || (record.profile_state == 'PAUSE')) { + // Open a popup to confirm this action. + $('#eventWindow').jqxWindow('open'); + $('#delOk').click(function() { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","profile":{"command":"abort"}}'); + }); + } else if (record.profile_state == 'DONE') { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","profile":{"command":"done"}}'); + } + } + }); + $('#Profile2').click(function() { + if (record.mode == 'PROFILE') { + if ((record.profile_state == 'RUN') || (record.profile_state == 'PAUSE')) { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","profile":{"command":"pause"}}'); + } + } + }); + + createAbortElements(); + + websocket.onmessage = function(evt) { + var msg = evt.data; + var obj = JSON.parse(msg); + + console.log('ws got ' + msg); + + if (obj.ping == 1) { + console.log('ws got ping'); + websocket.send('{"pong":1}'); + } + + if (obj.type == 'fermenter' && obj.unit == record.unit) { + console.log('ws got this device ' + msg); + record.beeruuid = obj.metric.product.uuid; + record.beercode = obj.metric.product.code; + record.beername = obj.metric.product.name; + record.yeast_lo = obj.metric.product.yeast_lo; + record.yeast_hi = obj.metric.product.yeast_hi; + record.air_state = obj.metric.air.state; + record.air_temperature = obj.metric.air.temperature; + record.beer_state = obj.metric.beer.state; + record.beer_temperature = obj.metric.beer.temperature; + record.chiller_state = obj.metric.chiller.state; + record.chiller_temperature = obj.metric.chiller.temperature; + if (obj.metric.heater.state !== undefined) + record.heater_state = obj.metric.heater.state; + if (obj.metric.cooler.state !== undefined) + record.cooler_state = obj.metric.cooler.state; + if (obj.metric.fan.state !== undefined) + record.fan_state = obj.metric.fan.state; + if (obj.metric.door) + record.door_state = obj.metric.door.state; + if (obj.metric.light) + record.light_state = obj.metric.light.state; + if (obj.metric.psu) + record.psu_state = obj.metric.psu.state; + record.mode = obj.metric.mode; + record.stage = obj.metric.stage; + record.alarm = obj.metric.alarm; + record.setpoint_low = obj.metric.setpoint.low; + record.setpoint_high = obj.metric.setpoint.high; + if (obj.profile) { + record.profile_uuid = obj.profile_uuid; + record.profile_name = obj.profile_name; + record.profile_state = obj.profile_state; + record.profile_percent = obj.profile_percent; + record.profile_inittemp_high = obj.profile_inittemp_high; + record.profile_inittemp_low = obj.profile_inittemp_low; + } else { + record.profile_uuid = ''; + record.profile_name = ''; + record.profile_state = ''; + record.profile_percent = 0; + } + updateScreen(); + } + } +});