The co2meters monitor screen rebuild to use websockets instead of data polling.

Thu, 14 May 2020 14:38:20 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 14 May 2020 14:38:20 +0200
changeset 678
14322825cb3d
parent 677
6e82fece1f8f
child 679
48f8f3fce7c0

The co2meters monitor screen rebuild to use websockets instead of data polling.

bmsd/Makefile file | annotate | diff | comparison | revisions
bmsd/co2meters.c file | annotate | diff | comparison | revisions
bmsd/co2meters.h file | annotate | diff | comparison | revisions
bmsd/websocket.c file | annotate | diff | comparison | revisions
www/Makefile file | annotate | diff | comparison | revisions
www/cmd_co2meter.php file | annotate | diff | comparison | revisions
www/js/global.js file | annotate | diff | comparison | revisions
www/js/mon_co2meter.js file | annotate | diff | comparison | revisions
--- a/bmsd/Makefile	Tue May 12 16:08:30 2020 +0200
+++ b/bmsd/Makefile	Thu May 14 14:38:20 2020 +0200
@@ -66,4 +66,5 @@
 xutil.o: bms.h xutil.h
 rdconfig.o: bms.h xutil.h futil.h rdconfig.h
 mysql.o: bms.h xutil.h mysql.h nodes.h
+websocket.o: bms.h xutil.h websocket.h co2meters.h
 # End of generated dependencies
--- a/bmsd/co2meters.c	Tue May 12 16:08:30 2020 +0200
+++ b/bmsd/co2meters.c	Thu May 14 14:38:20 2020 +0200
@@ -40,12 +40,147 @@
 
 
 
+void co2meter_ws_send(sys_co2meter_list *co2meter)
+{
+    char	*msg = NULL, buf[65];
+
+    msg = xstrcpy((char *)"{\"device\":\"co2meters\",\"node\":\"");
+    msg = xstrcat(msg, co2meter->node);
+    msg = xstrcat(msg, (char *)"\",\"unit\":\"");
+    msg = xstrcat(msg, co2meter->alias);
+    msg = xstrcat(msg, (char *)"\",\"online\":");
+    msg = xstrcat(msg, co2meter->online ? (char *)"1":(char *)"0");
+    msg = xstrcat(msg, (char *)",\"mode\":\"");
+    msg = xstrcat(msg, co2meter->mode);
+    msg = xstrcat(msg, (char *)"\",\"beercode\":\"");
+    msg = xstrcat(msg, co2meter->beercode);
+    msg = xstrcat(msg, (char *)"\",\"beername\":\"");
+    msg = xstrcat(msg, co2meter->beername);
+    msg = xstrcat(msg, (char *)"\",\"temperature\":");
+    snprintf(buf, 64, "%.3f", co2meter->temperature);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"pressure_channel\":");
+    snprintf(buf, 64, "%d", co2meter->pressure_channel);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"pressure_voltage\":");
+    snprintf(buf, 64, "%.3f", co2meter->pressure_voltage);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"pressure_zero\":");
+    snprintf(buf, 64, "%.3f", co2meter->pressure_zero);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"pressure_bar\":");
+    snprintf(buf, 64, "%.3f", co2meter->pressure_bar);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)",\"alarm\":");
+    snprintf(buf, 64, "%d", co2meter->alarm);
+    msg = xstrcat(msg, buf);
+    msg = xstrcat(msg, (char *)"}");
+    ws_broadcast(msg);
+    free(msg);
+    msg = NULL;
+
+}
+
+
+
+void co2meter_ws_receive(char *payload)
+{
+    struct json_object	*jobj, *val;
+    sys_co2meter_list	*tmpp;
+    char		*node = NULL, *alias = NULL, *beeruuid = NULL, *beercode = NULL, *beername = NULL;
+    char		query[512], *end;
+    MYSQL               *con2 = NULL;
+
+    syslog(LOG_NOTICE, "co2meter_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));
+    json_object_put(jobj);
+
+    /*
+     * Search co2meter record in the memory array and use it if found.
+     */
+    if (co2meters) {
+        for (tmpp = co2meters; tmpp; tmpp = tmpp->next) {
+            if ((strcmp(tmpp->alias, alias) == 0) && (strcmp(tmpp->node, node) == 0)) {
+		if (beeruuid && beercode && beername) {
+		    con2 = mysql_init(NULL);
+		    if (con2 == NULL) {
+			syslog(LOG_NOTICE, "MySQL: mysql_init() failed");
+		    } else {
+			if (mysql_real_connect(con2, Config.mysql_host, Config.mysql_user, Config.mysql_pass, Config.mysql_database, Config.mysql_port, NULL, 0) == NULL) {
+			    syslog(LOG_NOTICE, "MySQL: mysql_real_connect() %s", mysql_error(con2));
+			} else {
+			    end = stpcpy(query, "UPDATE mon_co2meters SET beeruuid='");
+			    end += mysql_real_escape_string(con2, end, beeruuid, strlen(beeruuid));
+			    end = stpcpy(end, "', beercode='");
+			    end += mysql_real_escape_string(con2, end, beercode, strlen(beercode));
+			    end = stpcpy(end, "', beername='");
+			    end += mysql_real_escape_string(con2, end, beername, strlen(beername));
+			    end = stpcpy(end, "' WHERE node='");
+			    end += mysql_real_escape_string(con2, end, node, strlen(node));
+			    end = stpcpy(end, "' AND alias='");
+			    end += mysql_real_escape_string(con2, end, alias, strlen(alias));
+			    end = stpcpy(end, "'");
+
+			    if (mysql_real_query(con2, query, (unsigned int) (end - query))) {
+				syslog(LOG_NOTICE, "MySQL: `%s' error %u (%s))", query, mysql_errno(con2), mysql_error(con2));
+			    } else {
+				/* Database updated, now update internal memory */
+				//syslog(LOG_NOTICE, "MySQL: `%s' Ok", query);
+				if (tmpp->beercode)
+                                    free(tmpp->beercode);
+                            	tmpp->beercode = xstrcpy(beercode);
+                            	if (tmpp->beername)
+                                    free(tmpp->beername);
+                            	tmpp->beername = xstrcpy(beername);
+				if (tmpp->beeruuid)
+				    free(tmpp->beeruuid);
+				tmpp->beeruuid = xstrcpy(beeruuid);
+				/* Report new state to the client */
+				co2meter_ws_send(tmpp);
+				syslog(LOG_NOTICE, "Set co2meter %s/%s new beer %s %s", node, alias, beercode, beername);
+			    }
+			    mysql_close(con2);
+			}
+		    }
+		}
+                break;
+            }
+        }
+    }
+
+    if (node)
+	free(node);
+    if (alias)
+	free(alias);
+    if (beeruuid)
+	free(beeruuid);
+    if (beercode)
+	free(beercode);
+    if (beername)
+	free(beername);
+}
+
+
+
 void co2meter_set(char *edge_node, char *alias, char *payload)
 {
     struct json_object	*jobj, *val, *sensor;
     sys_co2meter_list	*co2meter, *tmpp;
     bool		new_co2meter = true;
-    char		*msg = NULL, buf[65];
 
 //    fprintf(stdout, "co2meter_set: %s/%s %s\n", edge_node, alias, payload);
 
@@ -139,36 +274,7 @@
     }
     json_object_put(jobj);
 
-    msg = xstrcpy((char *)"{\"device\":\"co2meters\",\"node\":\"");
-    msg = xstrcat(msg, edge_node);
-    msg = xstrcat(msg, (char *)"\",\"unit\":\"");
-    msg = xstrcat(msg, alias);
-    msg = xstrcat(msg, (char *)"\",\"online\":");
-    msg = xstrcat(msg, co2meter->online ? (char *)"1":(char *)"0");
-    msg = xstrcat(msg, (char *)",\"mode\":\"");
-    msg = xstrcat(msg, co2meter->mode);
-    msg = xstrcat(msg, (char *)"\",\"temperature\":");
-    snprintf(buf, 64, "%.3f", co2meter->temperature);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"pressure_channel\":");
-    snprintf(buf, 64, "%d", co2meter->pressure_channel);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"pressure_voltage\":");
-    snprintf(buf, 64, "%.3f", co2meter->pressure_voltage);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"pressure_zero\":");
-    snprintf(buf, 64, "%.3f", co2meter->pressure_zero);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"pressure_bar\":");
-    snprintf(buf, 64, "%.3f", co2meter->pressure_bar);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)",\"alarm\":");
-    snprintf(buf, 64, "%d", co2meter->alarm);
-    msg = xstrcat(msg, buf);
-    msg = xstrcat(msg, (char *)"}");
-    ws_broadcast(msg);
-    free(msg);
-    msg = NULL;
+    co2meter_ws_send(co2meter);
 
 //    co2meter_dump(co2meter);
 
--- a/bmsd/co2meters.h	Tue May 12 16:08:30 2020 +0200
+++ b/bmsd/co2meters.h	Thu May 14 14:38:20 2020 +0200
@@ -8,6 +8,12 @@
 void co2meter_dump(sys_co2meter_list *co2meter);
 
 /**
+ * @brief Process received command from a websocket.
+ * @param payload The received data in JSON format.
+ */
+void co2meter_ws_receive(char *payload);
+
+/**
  * @brief Birth of a co2meter or data update. Create it in the database if 
  *        never seen before, else just update the database entry.
  * @param topic The MQTT topic string, contains the co2meter type and name.
--- a/bmsd/websocket.c	Tue May 12 16:08:30 2020 +0200
+++ b/bmsd/websocket.c	Thu May 14 14:38:20 2020 +0200
@@ -27,6 +27,7 @@
 #include "bms.h"
 #include "xutil.h"
 #include "websocket.h"
+#include "co2meters.h"
 #include <libwebsockets.h>
 
 
@@ -64,7 +65,8 @@
 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;
-    int n, m;
+    int		n, m;
+    char	buf[513];
 
     switch (reason) {
 
@@ -110,31 +112,14 @@
                 break;
 
 	case LWS_CALLBACK_RECEIVE:
-                if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) {
-                    syslog(LOG_NOTICE, "ws: dropping!");
-                    goto choke;
-                }
-
-                if (ringbuffer[ringbuffer_head].payload)
-                    free(ringbuffer[ringbuffer_head].payload);
 
-                ringbuffer[ringbuffer_head].payload = malloc(LWS_PRE + len);
-                ringbuffer[ringbuffer_head].len = len;
-                memcpy((char *)ringbuffer[ringbuffer_head].payload + LWS_PRE, in, len);
-                if (ringbuffer_head == (MAX_MESSAGE_QUEUE - 1))
-                    ringbuffer_head = 0;
-                else
-                    ringbuffer_head++;
+		memcpy(buf, in, len);
+		buf[len] = '\0';
+		syslog(LOG_NOTICE, "ws: reveived %ld bytes %s", len, buf);
+		if (strncmp(buf, (char *)"{\"device\":\"co2meters\",", 22) == 0) {
+		    co2meter_ws_receive(buf);
+		}
 
-                if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2))
-                    goto done;
-
-choke:
-                syslog(LOG_NOTICE, "ws: LWS_CALLBACK_RECEIVE: throttling");
-                lws_rx_flow_control(wsi, 0);
-
-done:
-                lws_callback_on_writable_all_protocol(lws_get_context(wsi), lws_get_protocol(wsi));
                 break;
 
 	case LWS_CALLBACK_CLOSED:
@@ -152,7 +137,7 @@
 
 
 static struct lws_protocols protocols[] = {
-	{ "bmsd-protocol", callback_ws, sizeof(struct per_session_data__lws_mirror), 128 },
+	{ "bmsd-protocol", callback_ws, sizeof(struct per_session_data__lws_mirror), 512 },
         { NULL, NULL, 0, 0 } /* terminator */
 };
 
--- a/www/Makefile	Tue May 12 16:08:30 2020 +0200
+++ b/www/Makefile	Thu May 14 14:38:20 2020 +0200
@@ -3,7 +3,7 @@
 
 include ../Makefile.global
 
-SRC		= cmd_fermenter.php cmd_co2meter.php cmd_ispindel.php config.php.dist crontasks.php \
+SRC		= cmd_fermenter.php cmd_ispindel.php config.php.dist crontasks.php \
 		  export_equipments.php export_fermentables.php export_hops.php export_mashs.php \
 		  export_miscs.php export_styles.php export_suppliers.php export_waters.php \
 		  export_yeasts.php favicon.ico gen_about.php \
--- a/www/cmd_co2meter.php	Tue May 12 16:08:30 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-<?php
-require_once('config.php');
-
-#Connect to the database
-$connect = mysqli_connect(DBASE_HOST, DBASE_USER, DBASE_PASS, DBASE_NAME);
-if (! $connect) {
-        die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
-}
-mysqli_set_charset($connect, "utf8" );
-
-$sql  = "UPDATE `mon_co2meters` SET ";
-$sql .=    "beername='" . mysqli_real_escape_string($connect, $_POST['beername']);
-$sql .= "', beercode='" . mysqli_real_escape_string($connect, $_POST['beercode']);
-$sql .= "', beeruuid='" . mysqli_real_escape_string($connect, $_POST['beeruuid']);
-$sql .= "' WHERE uuid='" . $_POST['uuid'] . "';";
-
-$result = mysqli_query($connect, $sql);
-if (! $result) {
-	syslog(LOG_NOTICE, "cmd_co2meters: result: ".mysqli_error($connect));
-} else {
-	syslog(LOG_NOTICE, "cmd_co2meters: updated record ".$_POST['uuid']);
-}
-echo $result;
-?>
--- a/www/js/global.js	Tue May 12 16:08:30 2020 +0200
+++ b/www/js/global.js	Thu May 14 14:38:20 2020 +0200
@@ -750,6 +750,28 @@
 },
 mashlist = new $.jqx.dataAdapter(mashProfileSource);
 
+/* Websocket interface. Place "websocket.onmessage = function(evt) {}" in the user script. */
+var websocket = new WebSocket('ws://'+location.hostname+'/ws');
+
+websocket.onopen = function(evt) {
+ console.log('WebSocket connection opened');
+ $('#wsstatus').html('WebSocket open');
+}
+
+websocket.onclose = function(evt) {
+ console.log('Websocket connection closed');
+ $('#wsstatus').html('WebSocket closed');
+}
+
+websocket.onerror = function(event) {
+ console.log('Websocket error: ' + event.data);
+ $('#wsstatus').html('WebSocket error: ' + event.data);
+}
+
+/* Handle global menu manipulation called by the user script. */
+function ws_global(msg) {
+
+}
 
 
 $(document).ready(function() {
@@ -762,38 +784,6 @@
   theme: theme
  });
  $('#jqxWidget').css('visibility', 'visible');
-
- var websocket = new WebSocket('ws://'+location.hostname+'/ws');
-
- websocket.onopen = function(evt) {
-  console.log('WebSocket connection opened');
-  document.getElementById("wsstatus").innerHTML = "";
- }
-
- websocket.onmessage = function(evt) {
-    var msg = evt.data;
-    var value;
-
-  console.log('ws got: ' + msg);
-//    switch (msg.charAt(0)) {
-//        case '{':
-//                BrewBoard.p_msg(evt.data);
-//                break;
-//        default:
-//                document.getElementById("output").innerHTML = evt.data;
-//                break;
-//    }
- }
-
- websocket.onclose = function(evt) {
-  console.log('Websocket connection closed');
-//  $('#wsstatus').html('WebSocket closed');
- }
-
- websocket.onerror = function(event) {
-  console.log('Websocket error: ' + event.data);
-  $('#wsstatus').html('WebSocket error: ' + event.data);
- }
 });
 
 
--- a/www/js/mon_co2meter.js	Tue May 12 16:08:30 2020 +0200
+++ b/www/js/mon_co2meter.js	Thu May 14 14:38:20 2020 +0200
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * Copyright (C) 2019
+ * Copyright (C) 2019-2020
  *
  * Michiel Broek <mbroek at mbse dot eu>
  *
@@ -18,15 +18,13 @@
  * 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.
- *****************************************************************************/
+ */
 
 
 $(document).ready(function() {
 
  var record = {},
  blank = {},
- newProduct = false,
- schedule = 0,
 
  productSource = {
   datatype: 'json',
@@ -168,40 +166,8 @@
  $('#gaugeContainer_pressure').jqxGauge(gaugeoptionsp);
  $('#gaugeContainer_pressure').jqxGauge({ caption: { value: 'Bar: 00.000' }});
 
- function sendProduct(code, name, uuid) {
-
-  console.log('sendProduct(' + code + ', ' + name + ', ' + uuid + ')');
-  var data = 'uuid=' + record.uuid + '&beeruuid=' + uuid + '&beercode=' + code + '&beername=' + name;
-  $.ajax({
-   url: 'cmd_co2meter.php',
-   data: data,
-   type: 'POST',
-   success: function(data) {},
-   error: function(jqXHR, textStatus, errorThrown) { console.log('sendProduct() error'); }
-  });
- }
-
- // Get the data immediatly and then at regular intervals to refresh.
+ // Get the data
  dataAdapter.dataBind();
- setInterval(function() {
-  var skip = false;
-  if (newProduct) {
-   sendProduct(record.beercode, record.beername, record.beeruuid);
-   newProduct = false;
-   skip = true;
-  }
-  if (skip) {
-   schedule = 4; // 2 seconds wait to get the results
-  } else {
-   if (schedule > 0)
-    schedule--;
-  }
-
-  if (schedule <= 0) {
-   dataAdapter.dataBind();
-   schedule = 20;
-  }
- }, 500);
 
  $('#select_beer').on('select', function(event) {
   if (event.args) {
@@ -210,7 +176,10 @@
    record.beercode = datarecord.code;
    record.beername = datarecord.name;
    record.beeruuid = datarecord.uuid;
-   newProduct = true;
+   console.log('Select beer ' + record.beercode + ', ' + record.beername);
+   var msg = '{"device":"co2meters","node":"' + record.node + '","unit":"' + record.alias +
+             '","beeruuid":"' + record.beeruuid + '","beercode":"' + record.beercode + '","beername":"' + record.beername + '"}';
+   websocket.send(msg);
   }
  });
 
@@ -219,4 +188,41 @@
  $('#FLog').click(function() {
   window.open('log_co2pressure.php?code=' + record.beercode + '&name=' + record.beername);
  });
+
+ websocket.onmessage = function(evt) {
+  var msg = evt.data;
+  var obj = JSON.parse(msg);
+  if (obj.device == "co2meters" && obj.node == record.node && obj.unit == record.alias) {
+   console.log('ws got this device ' + msg);
+   if (obj.online) {
+    $('#info_online').html('On-line');
+   } else {
+    $('#info_online').html('Off-line');
+   }
+   $('#info_beer').html(obj.beercode + ' - ' + obj.beername);
+   $('#info_mode').html(obj.mode);
+   if (obj.online && obj.mode != 'OFF') {
+    $('#co2meter_powerled').html('<div class="LEDblue_on"></div>Power');
+    $('#select_beer').jqxDropDownList({ disabled: true });
+    $('#select_beer').jqxDropDownList('clearSelection');
+    $('#select_beer').hide();
+   } else {
+    $('#co2meter_powerled').html('<div class="LEDblue_off"></div>Power');
+    $('#select_beer').show();
+    $('#select_beer').jqxDropDownList({ disabled: false });
+   }
+   if (obj.online && (obj.alarm != '0')) {
+    $('#co2meter_alarmled').html('<div class="LEDred_on"></div>Alarm');
+   } else {
+    $('#co2meter_alarmled').html('<div class="LEDred_off"></div>Alarm');
+   }
+
+   $('#gaugeContainer_temperature').jqxGauge({ caption: { value: 'Temp: ' + obj.temperature.toFixed(3) }});
+   $('#gaugeContainer_temperature').jqxGauge({ value: obj.temperature });
+   $('#gaugeContainer_pressure').jqxGauge({ caption: { value: 'Bar: ' + obj.pressure_bar.toFixed(2) }});
+   $('#gaugeContainer_pressure').jqxGauge({ value: obj.pressure_bar });
+
+  }
+  ws_global(msg);
+ }
 });

mercurial