Initial code for iSpindel support in the daemon

Fri, 13 Dec 2019 16:49:50 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Fri, 13 Dec 2019 16:49:50 +0100
changeset 567
6bf0afc33e70
parent 566
e526dc911bc1
child 568
6f3c24e21deb

Initial code for iSpindel support in the daemon

README.design file | annotate | diff | comparison | revisions
bmsd/Makefile file | annotate | diff | comparison | revisions
bmsd/bms.h file | annotate | diff | comparison | revisions
bmsd/ispindels.c file | annotate | diff | comparison | revisions
bmsd/ispindels.h file | annotate | diff | comparison | revisions
bmsd/mqtt.c file | annotate | diff | comparison | revisions
bmsd/mysql.c file | annotate | diff | comparison | revisions
bmsd/mysql.h file | annotate | diff | comparison | revisions
--- a/README.design	Tue Dec 10 20:13:00 2019 +0100
+++ b/README.design	Fri Dec 13 16:49:50 2019 +0100
@@ -66,3 +66,19 @@
 
 https://github.com/beerjson/beerjson
 
+
+--- iSpindel ---
+MQTT mode: 
+ ispindel/<name>/tilt Tilt
+ ispindel/<name>/temperature 20.1250
+ ispindel/<name>/temp_units C
+ ispindel/<name>/battery Volt
+ ispindel/<name>/gravity Gravity
+ ispindel/<name>/interval 900
+ ispindel/<name>/RSSI WiFi.RSSI()
+
+Default name: "iSpindel000"
+
+When data is received add a timestamp.
+What about add data to a fermenter. Assign a ispindel?? 
+
--- a/bmsd/Makefile	Tue Dec 10 20:13:00 2019 +0100
+++ b/bmsd/Makefile	Fri Dec 13 16:49:50 2019 +0100
@@ -1,4 +1,4 @@
-# Makefile for the mbsePi-apps/thermferm.
+# Makefile for the bmsd/bmsd.
 
 include ../Makefile.global
 
@@ -55,11 +55,13 @@
 
 # DO NOT DELETE THIS LINE - MAKE DEPEND RELIES ON IT
 # Dependencies generated by make depend
-mqtt.o: bms.h xutil.h mqtt.h nodes.h fermenters.h co2meters.h
+mqtt.o: bms.h xutil.h mqtt.h nodes.h fermenters.h co2meters.h ispindels.h
 lock.o: lock.h bms.h futil.h
 nodes.o: bms.h xutil.h nodes.h mysql.h
 futil.o: bms.h futil.h
 fermenters.o: bms.h xutil.h fermenters.h mysql.h
+co2meters.o: bms.h xutil.h co2meters.h mysql.h
+ispindels.o: bms.h xutil.h ispindels.h mysql.h nodes.h
 bms.o: bms.h xutil.h futil.h rdconfig.h lock.h mqtt.h mysql.h nodes.h
 xutil.o: bms.h xutil.h
 rdconfig.o: bms.h xutil.h futil.h rdconfig.h
--- a/bmsd/bms.h	Tue Dec 10 20:13:00 2019 +0100
+++ b/bmsd/bms.h	Fri Dec 13 16:49:50 2019 +0100
@@ -105,6 +105,7 @@
     char			*net_address;		///< IPv4 or IPv6 address
     char			*net_ifname;		///< Interface name
     int				net_rssi;		///< RSSI value if wireless.
+    int				interval;		///< Update interval
 } sys_node_list;
 
 
@@ -242,7 +243,7 @@
     int		fan_power;		///< Fan power 0 or 100
     uint64_t	fan_usage;		///< Fan usage counter in seconds
     float	setpoint_low;		///< Target temperature low
-    float	setpoint_high;		///< Tarhet temperature high
+    float	setpoint_high;		///< Target temperature high
     char	*mode;			///< Working mode.
     char	*stage;			///< Fermentation stage
     char	*event;			///< Optional event
@@ -305,19 +306,24 @@
 } brewer_list;
 
 
-// Make it universal and make it connectable with a fermenter.
+// Make it universal and make it connectable with a beer.
 typedef struct _ispindel_list {
     struct _ispindel_list	*next;
-    char			*uuid;			///< Fixed uuid string
-    char			*name;			///< Name or description (Red iSpindle).
-    char			*beercode;		///< Beer code if in use.
-    float			temperature;		///< Temperature of the beer.
-    float			gravity;		///< Measured gravity
-    							// What nore, battery?
-} ispindel_list;
+    char			*node;			///< Node name received.
+    bool			online;			///< Is considered online.
+    uint32_t			alarm;			///< Alarm flags.
+    char			*beercode;              ///< Beer unique code
+    char                        *beername;              ///< Beer name being measured
+    char                        *beeruuid;              ///< Beer uuid being measured
+    float			tilt;			///< Tilt angle in degrees
+    float			temperature;		///< Temperature in C
+    float			battery;		///< Battery voltage
+    float			gravity;		///< Gravity in plato?
+    int				interval;		///< Measure interval
+    int8_t			rssi;			///< WiFi RSSI
+} sys_ispindel_list;
 
 
-// Hergisting meters.
 
 /**
  * @brief Standalone temperature loggers. (Freezers, refrigerators, chambers).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bmsd/ispindels.c	Fri Dec 13 16:49:50 2019 +0100
@@ -0,0 +1,320 @@
+/**
+ * @file ispindels.c
+ * @brief Handle ispindels status
+ * @author Michiel Broek <mbroek at mbse dot eu>
+ *
+ * Copyright (C) 2019
+ *
+ * This file is part of the bms (Brewery Management System)
+ *
+ * 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.
+ *
+ * bms 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 "bms.h"
+#include "xutil.h"
+#include "ispindels.h"
+#include "mysql.h"
+#include "nodes.h"
+
+
+sys_ispindel_list	*ispindels = NULL;
+
+extern int		debug;
+extern sys_config       Config;
+extern MYSQL		*con;
+extern MYSQL_RES	*res_set;
+extern MYSQL_ROW	row;
+
+
+
+void ispindel_set(char *node, char *key, char *payload)
+{
+    sys_ispindel_list	*ispindel, *tmpp;
+    bool		new_ispindel = true, do_update = false;
+    char		*t, *p;
+    float		temperature = 20;
+    uint8_t		temp_units = 'C';
+
+    fprintf(stdout, "ispindel_set: %s %s\n", node, payload);
+
+    /*
+     * Search ispindel record in the memory array and use it if found.
+     */
+    if (ispindels) {
+	for (tmpp = ispindels; tmpp; tmpp = tmpp->next) {
+	    if (strcmp(tmpp->node, node) == 0) {
+		new_ispindel = false;
+		ispindel = tmpp;
+		break;
+	    }
+	}
+    }
+
+    printf("new_ispindel %s\n", new_ispindel ? "true":"false");
+
+    /*
+     * Allocate new ispindel if not yet known.
+     */
+    if (new_ispindel) {
+	ispindel = (sys_ispindel_list *)malloc(sizeof(sys_ispindel_list));
+	memset(ispindel, 0, sizeof(sys_ispindel_list));
+	ispindel->node = xstrcpy(node);
+    }
+
+    if (! ispindel->online) {
+    	ispindel->online = true;
+    	syslog(LOG_NOTICE, "Online ispindel %s", node);
+    }
+
+    /*
+     * Process the simple iSpindel MQTT payload.
+     */
+    if (strcmp(key, "tilt") == 0) {
+	ispindel->tilt = atof(payload);
+    } else if (strcmp(key, "temperature") == 0) {
+	temperature = atof(payload);
+    } else if (strcmp(key, "temp_units") == 0) {
+	temp_units = payload[0];
+    } else if (strcmp(key, "battery") == 0) {
+	ispindel->battery = atof(payload);
+    } else if (strcmp(key, "gravity") == 0) {
+	ispindel->gravity = atof(payload);
+    } else if (strcmp(key, "interval") == 0) {
+	ispindel->interval = atoi(payload);
+    } else if (strcmp(key, "RSSI") == 0) {
+	ispindel->rssi = atoi(payload);
+	do_update = true;
+	if (temp_units == 'C') {
+	    ispindel->temperature = temperature;
+	} else if (temp_units == 'F') {
+	    ispindel->temperature = temperature / 1.8 - 32;
+	} else if (temp_units == 'K') {
+	    ispindel->temperature = temperature - 273.15;
+	} else {
+	    ispindel->temperature = temperature;
+	}
+    } else {
+	syslog(LOG_NOTICE, "Unknown keyword `%s' from `%s'", key, node);
+    }
+
+    ispindel_dump(ispindel);
+
+    if (new_ispindel || do_update) {
+	char	buf[21];
+
+    	t = xstrcpy((char *)"mbv1.0/ispindels/NBIRTH/");
+    	t = xstrcat(t, node);
+
+    	p = xstrcpy((char *)"{\"metric\":{\"properties\":{\"hardwaremake\":\"MBSE\",\"hardwaremodel\":\"Wemos D1 mini\"},\"net\":{\"rssi\":");
+    	sprintf(buf, "%d", ispindel->rssi);
+	p = xstrcat(p, buf);
+    	p = xstrcat(p, (char *)"}}}");
+	node_birth_data(t, p);
+    	free(t);
+    	free(p);
+    }
+
+    if (new_ispindel) {
+    	if (ispindels == NULL) {
+	    ispindels = ispindel;
+	} else {
+	    for (tmpp = ispindels; tmpp; tmpp = tmpp->next) {
+		if (tmpp->next == NULL) {
+		    tmpp->next = ispindel;
+		    break;
+		}
+	    }
+	}
+	ispindel_mysql_insert(ispindel);
+    } else if (do_update) {
+	ispindel_mysql_update(ispindel);
+    }
+}
+
+
+
+/*
+ * Process iSpindel MQTT message.
+ */
+void ispindel_mqtt(char *topic, char *payload)
+{
+    char		*namespace, *node, *keyword;
+
+    namespace = strtok(topic, "/"); // must be ispindel
+    node = strtok(NULL, "/");
+    keyword = strtok(NULL, "/\0");
+
+    if (strcmp(namespace, "ispindels")) {
+	syslog(LOG_NOTICE, "ispindel_mqtt(%s, %s) error", topic, payload);
+	return;
+    }
+
+    ispindel_set(node, keyword, payload);
+}
+
+
+
+void ispindel_log(char *topic, char *payload)
+{
+    char                *edge_node, *alias, *line, buf[128], *logfile;
+    struct json_object  *jobj, *val, *metric;
+    co2pressure_log	*log;
+    struct tm		*mytime;
+    time_t		timestamp;
+    FILE		*fp;
+
+    strtok(topic, "/"); // ignore namespace
+    strtok(NULL, "/");	// group_id
+    strtok(NULL, "/");	// message_type
+    edge_node = strtok(NULL, "/\0");
+    alias = strtok(NULL, "/\0");
+
+    log = (co2pressure_log *)malloc(sizeof(co2pressure_log));
+    memset(log, 0, sizeof(co2pressure_log));
+
+    log->node = xstrcpy(edge_node);
+    log->alias = xstrcpy(alias);
+    jobj = json_tokener_parse(payload);
+
+    timestamp = time(NULL);
+    log->datetime = malloc(73);
+    mytime = localtime(&timestamp);
+    snprintf(log->datetime, 72, "%04d-%02d-%02d %02d:%02d:%02d",
+	mytime->tm_year + 1900, mytime->tm_mon + 1, mytime->tm_mday, mytime->tm_hour, mytime->tm_min, mytime->tm_sec);
+
+    if (json_object_object_get_ex(jobj, "metric", &metric)) {
+	if (json_object_object_get_ex(metric, "uuid", &val)) {
+	    if (strcmp((char *)"(null)", json_object_get_string(val)))
+	    	log->uuid = xstrcpy((char *)json_object_get_string(val));
+	}
+	if (json_object_object_get_ex(metric, "temperature", &val)) {
+	    log->temperature = json_object_get_double(val);
+	}
+	if (json_object_object_get_ex(metric, "pressure", &val)) {
+            log->pressure = json_object_get_double(val);
+        }
+    }
+    json_object_put(jobj);
+
+    /*
+     * Because ispindels are not so smart and don't hold product information
+     * search the missing pieces in the database.
+     */
+    snprintf(buf, 127, "SELECT beercode,beername,beeruuid FROM mon_ispindels WHERE uuid='%s'", log->uuid);
+    if (mysql_query(con, buf)) {
+        syslog(LOG_NOTICE, "MySQL: %s error %u (%s))", buf, mysql_errno(con), mysql_error(con));
+    } else {
+        res_set = mysql_store_result(con);
+        if (res_set == NULL) {
+            syslog(LOG_NOTICE, "MySQL: mysq_store_result error %u (%s))", mysql_errno(con), mysql_error(con));
+        } else {
+            if ((row = mysql_fetch_row(res_set)) != NULL) {
+		/*
+		 * Ignore when the beer_name or beer_code is not set.
+		 */
+		if ((int)strlen(row[0]) == 0 || (int)strlen(row[1]) == 0) {
+		    if (log->datetime)
+        		free(log->datetime);
+    		    if (log->uuid)
+        		free(log->uuid);
+    		    if (log->node)
+        		free(log->node);
+    		    if (log->alias)
+        		free(log->alias);
+    		    free(log);
+		    return;
+		}
+		log->product_code = xstrcpy(row[0]);
+		log->product_name = xstrcpy(row[1]);
+		log->product_uuid = xstrcpy(row[2]);
+	    }
+	}
+    }
+
+    /*
+     * Build csv log line
+     */
+    line = xstrcpy(log->datetime);
+    line = xstrcat(line, (char *)",");
+    snprintf(buf, 64, "%.3f", log->temperature);
+    line = xstrcat(line, buf);
+    line = xstrcat(line, (char *)",");
+    snprintf(buf, 64, "%.3f", log->pressure);
+    line = xstrcat(line, buf);
+    line = xstrcat(line, (char *)",");
+    line = xstrcat(line, log->uuid);
+
+    /*
+     * Build logfile name
+     */
+    logfile = xstrcpy(Config.web_root);
+    logfile = xstrcat(logfile, (char *)"/log/co2pressure/");
+    logfile = xstrcat(logfile, log->product_code);
+    logfile = xstrcat(logfile, (char *)" ");
+    logfile = xstrcat(logfile, log->product_name);
+    logfile = xstrcat(logfile, (char *)".log");
+
+    if (debug)
+	fprintf(stdout, "%s %s\n", logfile, line);
+
+    fp = fopen(logfile, "a");
+    if (fp) {
+	fprintf(fp, "%s\n", line);
+	fclose(fp);
+    } else {
+	syslog(LOG_NOTICE, "cannot append to `%s'", logfile);
+    }
+
+    free(logfile);
+    logfile = NULL;
+    free(line);
+    line = NULL;
+
+    if (log->datetime)
+    	free(log->datetime);
+    if (log->product_uuid )
+	free(log->product_uuid );
+    if (log->product_code )
+	free(log->product_code );
+    if (log->product_name )
+	free(log->product_name );
+    if (log->uuid)
+	free(log->uuid);
+    if (log->node)
+	free(log->node);
+    if (log->alias)
+	free(log->alias);
+    free(log);
+}
+
+
+
+void ispindel_dump(sys_ispindel_list *ispindel)
+{
+    if (debug) {
+    	printf("node        %s\n", ispindel->node);
+	printf("online      %s\n", ispindel->online ? "yes":"no");
+	printf("product     %s / %s\n", ispindel->beercode, ispindel->beername);
+        printf("tilt        %.3f\n", ispindel->tilt);
+	printf("temperature %.3f\n", ispindel->temperature);
+	printf("battery     %.3f\n", ispindel->battery);
+	printf("gravity     %.3f\n", ispindel->gravity);
+	printf("interval    %d\n", ispindel->interval);
+	printf("rssi        %d\n", ispindel->rssi);
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bmsd/ispindels.h	Fri Dec 13 16:49:50 2019 +0100
@@ -0,0 +1,19 @@
+/**
+ * @file ispindels.h
+ */
+
+#ifndef _ISPINDEL_H
+#define _ISPINDEL_H
+
+void ispindel_dump(sys_ispindel_list *ispindel);
+
+/**
+ * @brief Birth of a ispindel 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 ispindel type and name.
+ * @param payload The JSON formatted payload with the ispindel details.
+ */
+void ispindel_mqtt(char *topic, char *payload);
+
+
+#endif
--- a/bmsd/mqtt.c	Tue Dec 10 20:13:00 2019 +0100
+++ b/bmsd/mqtt.c	Fri Dec 13 16:49:50 2019 +0100
@@ -26,6 +26,7 @@
 #include "nodes.h"
 #include "fermenters.h"
 #include "co2meters.h"
+#include "ispindels.h"
 
 
 extern sys_config       Config;
@@ -179,6 +180,9 @@
             co2meter_log(message->topic, (char *)message->payload);
             return;
         }
+	if (strstr(message->topic, (char *)"ispindel")) {
+	    ispindel_mqtt(message->topic, (char *)message->payload);
+	}
 	syslog(LOG_NOTICE, "MQTT: message callback %s :: %d", message->topic, message->payloadlen);
     } else {
 	if (strstr(message->topic, (char *)"NBIRTH")) {
--- a/bmsd/mysql.c	Tue Dec 10 20:13:00 2019 +0100
+++ b/bmsd/mysql.c	Fri Dec 13 16:49:50 2019 +0100
@@ -36,6 +36,7 @@
 extern sys_node_list		*nodes;
 extern sys_fermenter_list	*fermenters;
 extern sys_co2meter_list	*co2meters;
+extern sys_ispindel_list	*ispindels;
 extern int              	debug;
 
 
@@ -62,7 +63,8 @@
     sys_node_list	*node, *tmpp;
     sys_fermenter_list	*fermenter, *tmpf;
     sys_co2meter_list	*co2meter, *tmpc;
-    int			ccnt = 0, ncnt = 0, fcnt = 0;
+    sys_ispindel_list   *ispindel, *tmpi;
+    int			icnt = 0, ccnt = 0, ncnt = 0, fcnt = 0;
 
     con = mysql_init(NULL);
     if (con == NULL) {
@@ -115,6 +117,7 @@
 		node->net_address   = xstrcpy(row[18]);
 		node->net_ifname    = xstrcpy(row[19]);
 		node->net_rssi      = atoi(row[20]);
+		node->interval      = atoi(row[21]);
 
 		if (nodes == NULL) {
 		    nodes = node;
@@ -272,7 +275,47 @@
 	}
     }
 
-    syslog(LOG_NOTICE, "MySQL: loaded %d nodes, %d fermenters, %d co2meters", ncnt, fcnt, ccnt);
+    if (mysql_query(con, "SELECT * FROM mon_ispindels")) {
+        syslog(LOG_NOTICE, "MySQL: SELECT * FROM mon_ispindels error %u (%s))", mysql_errno(con), mysql_error(con));
+    } else {
+        res_set = mysql_store_result(con);
+        if (res_set == NULL) {
+            syslog(LOG_NOTICE, "MySQL: mysq_store_result error %u (%s))", mysql_errno(con), mysql_error(con));
+        } else {
+            while ((row = mysql_fetch_row(res_set)) != NULL) {
+                ispindel = (sys_ispindel_list *)malloc(sizeof(sys_ispindel_list));
+                memset(ispindel, 0, sizeof(sys_ispindel_list));
+                ispindel->next                = NULL;
+		ispindel->node                = xstrcpy(row[1]);
+		ispindel->online              = 0;  // Will be set later
+		ispindel->alarm               = atoi(row[3]);
+		ispindel->beercode            = xstrcpy(row[4]);
+                ispindel->beername            = xstrcpy(row[5]);
+                ispindel->beeruuid            = xstrcpy(row[6]);
+		ispindel->tilt                = atof(row[7]);
+		ispindel->temperature         = atof(row[8]);
+                ispindel->battery             = atof(row[9]);
+		ispindel->gravity             = atof(row[10]);
+		ispindel->interval            = atoi(row[11]);
+		ispindel->rssi                = atoi(row[12]);
+
+                if (ispindels == NULL) {
+                    ispindels = ispindel;
+                } else {
+                    for (tmpi = ispindels; tmpi; tmpi = tmpi->next) {
+                        if (tmpi->next == NULL) {
+                            tmpi->next = ispindel;
+                            break;
+                        }
+                    }
+                }
+                icnt++;
+            }
+            mysql_free_result(res_set);
+        }
+    }
+
+    syslog(LOG_NOTICE, "MySQL: loaded %d nodes, %d fermenters, %d co2meters %d ispindels", ncnt, fcnt, ccnt, icnt);
     return 0;
 }
 
@@ -657,3 +700,46 @@
 }
 
 
+
+void ispindel_mysql_insert(sys_ispindel_list *ispindel)
+{
+    char        *query = malloc(2560);
+
+    snprintf(query, 2559,
+        "INSERT INTO mon_ispindels SET node='%s', online='%d', alarm='%d', " \
+        "tilt='%.3f', temperature='%.3f', battery='%.3f', gravity='%.3f', interval='%d', rssi='%d'",
+        ispindel->node, ispindel->online ? 1:0, ispindel->alarm,
+        ispindel->tilt, ispindel->temperature, ispindel->battery, ispindel->gravity, ispindel->interval, ispindel->rssi);
+
+    if (bms_mysql_query(query) == 0) {
+        syslog(LOG_NOTICE,  "MySQL: insert new ispindel %s", ispindel->node);
+    }
+    free(query);
+}
+
+
+
+void ispindel_mysql_update(sys_ispindel_list *ispindel)
+{
+    char        *query = malloc(2560);
+
+    snprintf(query, 2559,
+        "UPDATE mon_ispindels SET online='%d', alarm='%d', " \
+	"tilt='%.3f', temperature='%.3f', battery='%.3f', gravity='%.3f', interval='%d', rssi='%d' WHERE node='%s'",
+        ispindel->online ? 1:0, ispindel->alarm,
+	ispindel->tilt, ispindel->temperature, ispindel->battery, ispindel->gravity, ispindel->interval, ispindel->rssi, ispindel->node);
+
+    bms_mysql_query(query);
+    free(query);
+}
+
+
+
+void ispindel_mysql_death(char *node)
+{
+    char        *query = malloc(512);
+
+    snprintf(query, 511, "UPDATE mon_ispindels SET online='0' WHERE node='%s'", node);
+    bms_mysql_query(query);
+    free(query);
+}
--- a/bmsd/mysql.h	Tue Dec 10 20:13:00 2019 +0100
+++ b/bmsd/mysql.h	Fri Dec 13 16:49:50 2019 +0100
@@ -41,5 +41,8 @@
 void co2meter_mysql_update(sys_co2meter_list *co2meter);
 void co2meter_mysql_death(char *node, char *alias);
 
+void ispindel_mysql_insert(sys_ispindel_list *ispindel);
+void ispindel_mysql_update(sys_ispindel_list *ispindel);
+void ispindel_mysql_death(char *alias);
 
 #endif

mercurial