# HG changeset patch # User Michiel Broek # Date 1576252190 -3600 # Node ID 6bf0afc33e70dc4ce3ec084aee6954d8c0db1452 # Parent e526dc911bc19a35dca8269c41c4bf2a24a7449a Initial code for iSpindel support in the daemon diff -r e526dc911bc1 -r 6bf0afc33e70 README.design --- 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//tilt Tilt + ispindel//temperature 20.1250 + ispindel//temp_units C + ispindel//battery Volt + ispindel//gravity Gravity + ispindel//interval 900 + ispindel//RSSI WiFi.RSSI() + +Default name: "iSpindel000" + +When data is received add a timestamp. +What about add data to a fermenter. Assign a ispindel?? + diff -r e526dc911bc1 -r 6bf0afc33e70 bmsd/Makefile --- 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 diff -r e526dc911bc1 -r 6bf0afc33e70 bmsd/bms.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). diff -r e526dc911bc1 -r 6bf0afc33e70 bmsd/ispindels.c --- /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 + * + * 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(×tamp); + 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); + } +} + + diff -r e526dc911bc1 -r 6bf0afc33e70 bmsd/ispindels.h --- /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 diff -r e526dc911bc1 -r 6bf0afc33e70 bmsd/mqtt.c --- 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")) { diff -r e526dc911bc1 -r 6bf0afc33e70 bmsd/mysql.c --- 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); +} diff -r e526dc911bc1 -r 6bf0afc33e70 bmsd/mysql.h --- 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