bmsd/fermenters.c

changeset 679
48f8f3fce7c0
parent 677
6e82fece1f8f
child 684
ccb9f24d0fe9
--- a/bmsd/fermenters.c	Thu May 14 14:38:20 2020 +0200
+++ b/bmsd/fermenters.c	Mon May 18 11:00:59 2020 +0200
@@ -27,6 +27,7 @@
 #include "xutil.h"
 #include "fermenters.h"
 #include "mysql.h"
+#include "mqtt.h"
 #include "websocket.h"
 
 
@@ -36,15 +37,401 @@
 extern sys_config       Config;
 
 
+void fermenter_ws_send(sys_fermenter_list *fermenter)
+{
+    char	*msg = NULL, buf[65];
+
+    msg = xstrcpy((char *)"{\"device\":\"fermenters\",\"node\":\"");
+    msg = xstrcat(msg, fermenter->node);
+    msg = xstrcat(msg, (char *)"\",\"unit\":\"");
+    msg = xstrcat(msg, fermenter->alias);
+    msg = xstrcat(msg, (char *)"\",\"online\":");
+    msg = xstrcat(msg, fermenter->online ? (char *)"1":(char *)"0");
+    msg = xstrcat(msg, (char *)",\"mode\":\"");
+    msg = xstrcat(msg, fermenter->mode);
+    msg = xstrcat(msg, (char *)"\",\"beeruuid\":\"");
+    msg = xstrcat(msg, fermenter->beeruuid);
+    msg = xstrcat(msg, (char *)"\",\"beercode\":\"");
+    msg = xstrcat(msg, fermenter->beercode);
+    msg = xstrcat(msg, (char *)"\",\"beername\":\"");
+    msg = xstrcat(msg, fermenter->beername);
+    msg = xstrcat(msg, (char *)"\",\"yeast_lo\":");
+    snprintf(buf, 64, "%.3f", fermenter->yeast_lo);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"yeast_hi\":");
+    snprintf(buf, 64, "%.3f", fermenter->yeast_hi);
+    msg = xstrcat(msg, buf);
+    if (fermenter->air_address) {
+	msg = xstrcat(msg, (char *)",\"air_state\":\"");
+	msg = xstrcat(msg, fermenter->air_state);
+        msg = xstrcat(msg, (char *)"\",\"air_temperature\":");
+        snprintf(buf, 64, "%.3f", fermenter->air_temperature);
+        msg = xstrcat(msg, buf);
+    }
+    if (fermenter->beer_address) {
+	msg = xstrcat(msg, (char *)",\"beer_state\":\"");
+	msg = xstrcat(msg, fermenter->beer_state);
+        msg = xstrcat(msg, (char *)"\",\"beer_temperature\":");
+        snprintf(buf, 64, "%.3f", fermenter->beer_temperature);
+        msg = xstrcat(msg, buf);
+    }
+    if (fermenter->chiller_address) {
+	msg = xstrcat(msg, (char *)",\"chiller_state\":\"");
+	msg = xstrcat(msg, fermenter->chiller_state);
+        msg = xstrcat(msg, (char *)"\",\"chiller_temperature\":");
+        snprintf(buf, 64, "%.3f", fermenter->chiller_temperature);
+        msg = xstrcat(msg, buf);
+    }
+    if (fermenter->heater_address) {
+        msg = xstrcat(msg, (char *)",\"heater_state\":");
+        snprintf(buf, 64, "%d", fermenter->heater_state);
+        msg = xstrcat(msg, buf);
+    }
+    if (fermenter->cooler_address) {
+        msg = xstrcat(msg, (char *)",\"cooler_state\":");
+        snprintf(buf, 64, "%d", fermenter->cooler_state);
+        msg = xstrcat(msg, buf);
+    }
+    if (fermenter->fan_address) {
+        msg = xstrcat(msg, (char *)",\"fan_state\":");
+        snprintf(buf, 64, "%d", fermenter->fan_state);
+        msg = xstrcat(msg, buf);
+    }
+    if (fermenter->light_address) {
+	msg = xstrcat(msg, (char *)",\"light_address\":\"");
+	msg = xstrcat(msg, fermenter->light_address);
+        msg = xstrcat(msg, (char *)"\",\"light_state\":");
+        snprintf(buf, 64, "%d", fermenter->light_state);
+        msg = xstrcat(msg, buf);
+    }
+    if (fermenter->door_address) {
+	msg = xstrcat(msg, (char *)",\"door_address\":\"");
+        msg = xstrcat(msg, fermenter->door_address);
+        msg = xstrcat(msg, (char *)"\",\"door_state\":");
+        snprintf(buf, 64, "%d", fermenter->door_state);
+        msg = xstrcat(msg, buf);
+    }
+    if (fermenter->psu_address) {
+        msg = xstrcat(msg, (char *)",\"psu_address\":\"");
+        msg = xstrcat(msg, fermenter->psu_address);
+        msg = xstrcat(msg, (char *)"\",\"psu_state\":");
+        snprintf(buf, 64, "%d", fermenter->psu_state);
+        msg = xstrcat(msg, buf);
+    }
+    msg = xstrcat(msg, (char *)",\"setpoint_low\":");
+    snprintf(buf, 64, "%.3f", fermenter->setpoint_low);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"setpoint_high\":");
+    snprintf(buf, 64, "%.3f", fermenter->setpoint_high);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"alarm\":");
+    snprintf(buf, 64, "%d", fermenter->alarm);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"stage\":\"");
+    msg = xstrcat(msg, fermenter->stage);
+    msg = xstrcat(msg, (char *)"\"");
+
+    if (fermenter->profile_uuid) {
+	msg = xstrcat(msg, (char *)",\"profile_uuid\":\"");
+	msg = xstrcat(msg, fermenter->profile_uuid);
+	msg = xstrcat(msg, (char *)"\",\"profile_name\":\"");
+	msg = xstrcat(msg, fermenter->profile_name);
+	msg = xstrcat(msg, (char *)"\",\"profile_state\":\"");
+	msg = xstrcat(msg, fermenter->profile_state);
+	msg = xstrcat(msg, (char *)"\",\"profile_percent\":");
+	snprintf(buf, 64, "%d", fermenter->profile_percent);
+	msg = xstrcat(msg, buf);
+	msg = xstrcat(msg, (char *)",\"profile_inittemp_high\":");
+	snprintf(buf, 64, "%.3f", fermenter->profile_inittemp_high);
+	msg = xstrcat(msg, buf);
+	msg = xstrcat(msg, (char *)",\"profile_inittemp_low\":");
+	snprintf(buf, 64, "%.3f", fermenter->profile_inittemp_low);
+	msg = xstrcat(msg, buf);
+	msg = xstrcat(msg, (char *)",\"profile_steps\":");
+	msg = xstrcat(msg, fermenter->profile_steps);
+    }
+
+    msg = xstrcat(msg, (char *)",\"webcam_url\":\"");
+    msg = xstrcat(msg, fermenter->webcam_url);
+    msg = xstrcat(msg, (char *)"\",\"webcam_light\":");
+    snprintf(buf, 64, "%d", fermenter->webcam_light);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)"}");
+    ws_broadcast(msg);
+    free(msg);
+    msg = NULL;
+}
+
+
+
+char *fermenter_paybase(void)
+{
+    static char *tmp;
+    char	buf[33];
+
+    tmp = xstrcpy((char *)"{\"timestamp\":");
+    snprintf(buf, 32, "%ld", time(NULL));
+    tmp = xstrcat(tmp, buf);
+    tmp = xstrcat(tmp, (char *)",\"metric\":");
+    return tmp;
+}
+
+
+void fermenter_ws_receive(char *payload)
+{
+    struct json_object  *jobj, *pobj, *iobj, *val;
+    char                *node = NULL, *alias = NULL, *beeruuid = NULL, *beercode = NULL, *beername = NULL;
+    char		*mode = NULL, *stage = NULL, *profile = NULL, *profile_uuid = NULL, *profile_name = NULL, *profile_steps = NULL;
+    char		*topic = NULL, *pay = NULL, buf[75], *profile_command = NULL;
+    float		setpoint_low = 0, setpoint_high = 0, yeast_lo = 0, yeast_hi = 0, inittemp_lo = 0, inittemp_hi = 0;
+    int			heater_state = -1, cooler_state = -1, fan_state = -1, profile_fridgemode = -1;
+
+    syslog(LOG_NOTICE, "fermenter_ws_receive(%s)", payload);
+
+    /*
+     * Process the JSON formatted payload.
+     */
+    jobj = json_tokener_parse(payload);
+    if (json_object_object_get_ex(jobj, "node", &val))
+        node = xstrcpy((char *)json_object_get_string(val));
+    if (json_object_object_get_ex(jobj, "unit", &val))
+        alias = xstrcpy((char *)json_object_get_string(val));
+    if (json_object_object_get_ex(jobj, "beeruuid", &val))
+        beeruuid = xstrcpy((char *)json_object_get_string(val));
+    if (json_object_object_get_ex(jobj, "beercode", &val))
+        beercode = xstrcpy((char *)json_object_get_string(val));
+    if (json_object_object_get_ex(jobj, "beername", &val))
+        beername = xstrcpy((char *)json_object_get_string(val));
+    if (json_object_object_get_ex(jobj, "mode", &val))
+	mode = xstrcpy((char *)json_object_get_string(val));
+    if (json_object_object_get_ex(jobj, "stage", &val))
+	stage = xstrcpy((char *)json_object_get_string(val));
+    if (json_object_object_get_ex(jobj, "setpoint_low", &val))
+        setpoint_low = json_object_get_double(val);
+    if (json_object_object_get_ex(jobj, "setpoint_high", &val))
+        setpoint_high = json_object_get_double(val);
+    if (json_object_object_get_ex(jobj, "yeast_lo", &val))
+        yeast_lo = json_object_get_double(val);
+    if (json_object_object_get_ex(jobj, "yeast_hi", &val))
+        yeast_hi = json_object_get_double(val);
+    if (json_object_object_get_ex(jobj, "heater_state", &val))
+	heater_state = json_object_get_int(val);
+    if (json_object_object_get_ex(jobj, "cooler_state", &val))
+        cooler_state = json_object_get_int(val);
+    if (json_object_object_get_ex(jobj, "fan_state", &val))
+        fan_state = json_object_get_int(val);
+    if (json_object_object_get_ex(jobj, "profile", &pobj)) {
+	profile = xstrcpy((char *)json_object_get_string(pobj));
+	if (profile == NULL) { // clear profile request
+	    profile = xstrcpy((char *)"null");
+	}
+	if (json_object_object_get_ex(pobj, "uuid", &val)) {
+	    profile_uuid = xstrcpy((char *)json_object_get_string(val));
+	    syslog(LOG_NOTICE, "profile uuid");
+	}
+	if (json_object_object_get_ex(pobj, "name", &val))
+	    profile_name = xstrcpy((char *)json_object_get_string(val));
+	if (json_object_object_get_ex(pobj, "inittemp", &iobj)) {
+
+	    if (json_object_object_get_ex(iobj, "low", &val))
+		inittemp_lo = json_object_get_double(val);
+	    if (json_object_object_get_ex(iobj, "high", &val))
+		inittemp_hi = json_object_get_double(val);
+	}
+	if (json_object_object_get_ex(pobj, "fridgemode", &val))
+	    profile_fridgemode = json_object_get_int(val);
+	if (json_object_object_get_ex(pobj, "steps", &val))
+	    profile_steps = xstrcpy((char *)json_object_get_string(val));
+	if (json_object_object_get_ex(pobj, "command", &val)) {
+	    profile_command = xstrcpy((char *)json_object_get_string(val));
+	    syslog(LOG_NOTICE, "profile command %s", profile_command);
+	}
+    }
+    json_object_put(jobj);
+
+    /*
+     * Prepare MQTT topic
+     */
+    topic = xstrcpy((char *)"mbv1.0/fermenters/DCMD/");
+    topic = xstrcat(topic, node);
+    topic = xstrcat(topic, (char *)"/");
+    topic = xstrcat(topic, alias);
+
+    if (node && alias) {
+    	if (mode) {
+	    syslog(LOG_NOTICE, "Set fermenter %s/%s mode %s", node, alias, mode);
+	    pay = fermenter_paybase();
+	    pay = xstrcat(pay, (char *)"{\"mode\":\"");
+	    pay = xstrcat(pay, mode);
+	    pay = xstrcat(pay, (char *)"\"}}");
+	    mqtt_publish(topic, pay);
+	    free(pay);
+	    pay = NULL;
+    	}
+
+    	if (stage) {
+	    syslog(LOG_NOTICE, "Set fermenter %s/%s stage %s", node, alias, stage);
+	    pay = fermenter_paybase();
+            pay = xstrcat(pay, (char *)"{\"stage\":\"");
+            pay = xstrcat(pay, stage);
+            pay = xstrcat(pay, (char *)"\"}}");
+            mqtt_publish(topic, pay);
+            free(pay);
+            pay = NULL;
+    	}
+
+    	if (setpoint_low > 0 && setpoint_high > 0 && setpoint_high >= setpoint_low) {
+	    syslog(LOG_NOTICE, "Set fermenter %s/%s setpoint %.1f %.1f", node, alias, setpoint_low, setpoint_high);
+	    pay = fermenter_paybase();
+	    pay = xstrcat(pay, (char *)"{\"setpoint\":{\"low\":");
+	    snprintf(buf, 64, "%.1f", setpoint_low);
+	    pay = xstrcat(pay, buf);
+	    pay = xstrcat(pay, (char *)",\"high\":");
+	    snprintf(buf, 64, "%.1f", setpoint_high);
+	    pay = xstrcat(pay, buf);
+	    pay = xstrcat(pay, (char *)"}}}");
+            mqtt_publish(topic, pay);
+            free(pay);
+            pay = NULL;
+    	}
+
+        if (heater_state >= 0) {
+            syslog(LOG_NOTICE, "Set fermenter %s/%s heater %d", node, alias, heater_state);
+            pay = fermenter_paybase();
+            pay = xstrcat(pay, (char *)"{\"heater\":{\"state\":");
+	    snprintf(buf, 64, "%d", heater_state);
+	    pay = xstrcat(pay, buf);
+            pay = xstrcat(pay, (char *)"}}}");
+            mqtt_publish(topic, pay);
+            free(pay);
+            pay = NULL;
+        }
+
+        if (cooler_state >= 0) {
+            syslog(LOG_NOTICE, "Set fermenter %s/%s cooler %d", node, alias, cooler_state);
+            pay = fermenter_paybase();
+            pay = xstrcat(pay, (char *)"{\"cooler\":{\"state\":");
+            snprintf(buf, 64, "%d", cooler_state);
+            pay = xstrcat(pay, buf);
+            pay = xstrcat(pay, (char *)"}}}");
+            mqtt_publish(topic, pay);
+            free(pay);
+            pay = NULL;
+        }
+
+        if (fan_state >= 0) {
+            syslog(LOG_NOTICE, "Set fermenter %s/%s fan %d", node, alias, fan_state);
+            pay = fermenter_paybase();
+            pay = xstrcat(pay, (char *)"{\"fan\":{\"state\":");
+            snprintf(buf, 64, "%d", fan_state);
+            pay = xstrcat(pay, buf);
+            pay = xstrcat(pay, (char *)"}}}");
+            mqtt_publish(topic, pay);
+            free(pay);
+            pay = NULL;
+        }
+
+	if (beeruuid && beercode && beername && (yeast_hi > yeast_lo) && (yeast_lo > 0)) {
+	    syslog(LOG_NOTICE, "Set fermenter %s/%s beer %s %s", node, alias, beercode, beername);
+            pay = fermenter_paybase();
+            pay = xstrcat(pay, (char *)"{\"product\":{\"uuid\":\"");
+	    pay = xstrcat(pay, beeruuid);
+	    pay = xstrcat(pay, (char *)"\",\"code\":\"");
+	    pay = xstrcat(pay, beercode);
+	    pay = xstrcat(pay, (char *)"\",\"name\":\"");
+	    pay = xstrcat(pay, beername);
+	    pay = xstrcat(pay, (char *)"\",\"yeast_lo\":");
+	    snprintf(buf, 64, "%.1f", yeast_lo);
+	    pay = xstrcat(pay, buf);
+	    pay = xstrcat(pay, (char *)",\"yeast_hi\":");
+            snprintf(buf, 64, "%.1f", yeast_hi);
+            pay = xstrcat(pay, buf);
+	    pay = xstrcat(pay, (char *)"}}}");
+            mqtt_publish(topic, pay);
+            free(pay);
+            pay = NULL;
+	}
+
+	if (profile) {
+	    syslog(LOG_NOTICE, "%s", profile);
+	    if (strcmp(profile, (char *)"null") == 0) {
+		syslog(LOG_NOTICE, "Set fermenter %s/%s profile null", node, alias);
+            	pay = fermenter_paybase();
+            	pay = xstrcat(pay, (char *)"{\"profile\":null}}");
+            	mqtt_publish(topic, pay);
+            	free(pay);
+            	pay = NULL;
+	    } else if (profile_uuid && profile_name && profile_steps) {
+		syslog(LOG_NOTICE, "Set fermenter %s/%s profile %s", node, alias, profile_name);
+		pay = fermenter_paybase();
+                pay = xstrcat(pay, (char *)"{\"profile\":{\"uuid\":\"");
+		pay = xstrcat(pay, profile_uuid);
+		pay = xstrcat(pay, (char *)"\",\"name\":\"");
+		pay = xstrcat(pay, profile_name);
+		pay = xstrcat(pay, (char *)"\",\"inittemp\":{\"low\":");
+		snprintf(buf, 64, "%.1f", inittemp_lo);
+		pay = xstrcat(pay, buf);
+		pay = xstrcat(pay, (char *)",\"high\":");
+		snprintf(buf, 64, "%.1f", inittemp_hi);
+                pay = xstrcat(pay, buf);
+		pay = xstrcat(pay, (char *)"},\"fridgemode\":");
+		snprintf(buf, 64, "%d", profile_fridgemode);
+		pay = xstrcat(pay, buf);
+		pay = xstrcat(pay, (char *)",\"steps\":");
+		pay = xstrcat(pay, profile_steps);
+		pay = xstrcat(pay, (char *)"}}}");
+		mqtt_publish(topic, pay);
+                free(pay);
+                pay = NULL;
+	    } else if (profile_command) {
+		syslog(LOG_NOTICE, "Set fermenter %s/%s profile command %s", node, alias, profile_command);
+		pay = fermenter_paybase();
+                pay = xstrcat(pay, (char *)"{\"profile\":{\"command\":\"");
+		pay = xstrcat(pay, profile_command);
+		pay = xstrcat(pay, (char *)"\"}}}");
+                mqtt_publish(topic, pay);
+                free(pay);
+                pay = NULL;
+	    }
+	}
+    }
+
+    free(topic);
+    if (node)
+        free(node);
+    if (alias)
+        free(alias);
+    if (beeruuid)
+        free(beeruuid);
+    if (beercode)
+        free(beercode);
+    if (beername)
+        free(beername);
+    if (mode)
+	free(mode);
+    if (stage)
+	free(stage);
+    if (profile)
+	free(profile);
+    if (profile_uuid)
+	free(profile_uuid);
+    if (profile_name)
+	free(profile_name);
+    if (profile_steps)
+	free(profile_steps);
+    if (profile_command)
+	free(profile_command);
+}
+
+
 
 void fermenter_set(char *edge_node, char *alias, bool birth, char *payload)
 {
     struct json_object	*jobj, *val, *sensor, *temp;
     sys_fermenter_list	*fermenter, *tmpp;
     bool		new_fermenter = true;
-    char		*msg = NULL, buf[65];
 
-//    fprintf(stdout, "fermenter_set: %s/%s %s %s\n", edge_node, alias, birth ? "BIRTH":"DATA", payload);
+    //syslog(LOG_NOTICE, "fermenter_set: %s/%s %s %s", edge_node, alias, birth ? "BIRTH":"DATA", payload);
 
     /*
      * Search fermenter record in the memory array and use it if found.
@@ -267,8 +654,6 @@
     }
     if (json_object_object_get_ex(jobj, "profile", &sensor)) {
 	if (strcmp(json_object_to_json_string_ext(sensor, 0), "null")) {
-//	    printf("profile: %s\n", json_object_to_json_string_ext(sensor, 0));
-
 	    if (json_object_object_get_ex(sensor, "uuid", &val)) {
 		if (fermenter->profile_uuid)
 		    free(fermenter->profile_uuid);
@@ -314,82 +699,11 @@
 	    fermenter->profile_uuid = fermenter->profile_name = fermenter->profile_state = fermenter->profile_steps = NULL;
 	    fermenter->profile_percent = 0;
 	    fermenter->profile_inittemp_high = fermenter->profile_inittemp_low = 0.0;
-	    fermenter->yeast_lo = 12;
-	    fermenter->yeast_hi = 24;
 	}
     }
     json_object_put(jobj);
 
-    msg = xstrcpy((char *)"{\"device\":\"fermenters\",\"node\":\"");
-    msg = xstrcat(msg, edge_node);
-    msg = xstrcat(msg, (char *)"\",\"unit\":\"");
-    msg = xstrcat(msg, alias);
-    msg = xstrcat(msg, (char *)"\",\"online\":");
-    msg = xstrcat(msg, fermenter->online ? (char *)"1":(char *)"0");
-    msg = xstrcat(msg, (char *)",\"mode\":\"");
-    msg = xstrcat(msg, fermenter->mode);
-    msg = xstrcat(msg, (char *)"\",\"yeast_lo\":");
-    snprintf(buf, 64, "%.3f", fermenter->yeast_lo);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"yeast_hi\":");
-    snprintf(buf, 64, "%.3f", fermenter->yeast_hi);
-    msg = xstrcat(msg, buf);
-    if (fermenter->air_address) {
-    	msg = xstrcat(msg, (char *)",\"air\":");
-	snprintf(buf, 64, "%.3f", fermenter->air_temperature);
-	msg = xstrcat(msg, buf);
-    }
-    if (fermenter->beer_address) {
-        msg = xstrcat(msg, (char *)",\"beer\":");
-        snprintf(buf, 64, "%.3f", fermenter->beer_temperature);
-        msg = xstrcat(msg, buf);
-    }
-    if (fermenter->chiller_address) {
-        msg = xstrcat(msg, (char *)",\"chiller\":");
-        snprintf(buf, 64, "%.3f", fermenter->chiller_temperature);
-        msg = xstrcat(msg, buf);
-    }
-    if (fermenter->heater_address) {
-        msg = xstrcat(msg, (char *)",\"heater\":");
-        snprintf(buf, 64, "%d", fermenter->heater_state);
-        msg = xstrcat(msg, buf);
-    }
-    if (fermenter->cooler_address) {
-        msg = xstrcat(msg, (char *)",\"cooler\":");
-        snprintf(buf, 64, "%d", fermenter->cooler_state);
-        msg = xstrcat(msg, buf);
-    }
-    if (fermenter->fan_address) {
-        msg = xstrcat(msg, (char *)",\"fan\":");
-        snprintf(buf, 64, "%d", fermenter->fan_state);
-        msg = xstrcat(msg, buf);
-    }
-    if (fermenter->light_address) {
-        msg = xstrcat(msg, (char *)",\"light\":");
-        snprintf(buf, 64, "%d", fermenter->light_state);
-        msg = xstrcat(msg, buf);
-    }
-    if (fermenter->door_address) {
-        msg = xstrcat(msg, (char *)",\"door\":");
-        snprintf(buf, 64, "%d", fermenter->door_state);
-        msg = xstrcat(msg, buf);
-    }
-    msg = xstrcat(msg, (char *)",\"sp_lo\":");
-    snprintf(buf, 64, "%.3f", fermenter->setpoint_low);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"sp_hi\":");
-    snprintf(buf, 64, "%.3f", fermenter->setpoint_high);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"alarm\":");
-    snprintf(buf, 64, "%d", fermenter->alarm);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"stage\":\"");
-    msg = xstrcat(msg, fermenter->stage);
-    msg = xstrcat(msg, (char *)"\"}");
-    ws_broadcast(msg);
-    free(msg);
-    msg = NULL;
-
+    fermenter_ws_send(fermenter);
 //    fermenter_dump(fermenter);
 
     if (new_fermenter) {

mercurial