bmsd/ispindels.c

Wed, 06 Dec 2023 20:26:00 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Wed, 06 Dec 2023 20:26:00 +0100
changeset 855
2d328a2a4025
parent 853
f54decd31acb
permissions
-rw-r--r--

Fixed init scripts names in Makefile. Update crontasks to use the database to check the log entries for products.

/**
 * @file ispindels.c
 * @brief Handle ispindels data
 * @author Michiel Broek <mbroek at mbse dot eu>
 *
 * Copyright (C) 2019-2023
 *
 * 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"
#include "websocket.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;



double plato_to_sg(double plato)
{
    return 1.00001 + (0.0038661 * plato) + (1.3488e-5 * pow(plato, 2)) + (4.3074e-8 * pow(plato, 3));
}



void ispindel_ws_send(sys_ispindel_list *ispindel)
{
    char	*msg = NULL, buf[65];

    msg = xstrcpy((char *)"{\"device\":\"ispindels\",\"node\":\"");
    msg = xstrcat(msg, ispindel->node);
    msg = xstrcat(msg, (char *)"\",\"unit\":\"");
    msg = xstrcat(msg, ispindel->alias);
    msg = xstrcat(msg, (char *)"\",\"online\":");
    msg = xstrcat(msg, ispindel->online ? (char *)"1":(char *)"0");
    msg = xstrcat(msg, (char *)",\"mode\":\"");
    msg = xstrcat(msg, ispindel->mode);
    msg = xstrcat(msg, (char *)"\",\"beeruuid\":\"");
    msg = xstrcat(msg, ispindel->beeruuid);
    msg = xstrcat(msg, (char *)"\",\"beercode\":\"");
    msg = xstrcat(msg, ispindel->beercode);
    msg = xstrcat(msg, (char *)"\",\"beername\":\"");
    msg = xstrcat(msg, ispindel->beername);
    msg = xstrcat(msg, (char *)"\",\"yeast_lo\":");
    snprintf(buf, 64, "%.3f", ispindel->yeast_lo);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)",\"yeast_hi\":");
    snprintf(buf, 64, "%.3f", ispindel->yeast_hi);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)",\"temperature\":");
    snprintf(buf, 64, "%.4f", ispindel->temperature);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)",\"angle\":");
    snprintf(buf, 64, "%.6f", ispindel->angle);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)",\"battery\":");
    snprintf(buf, 64, "%.6f", ispindel->battery);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)",\"gravity\":");
    snprintf(buf, 64, "%.6f", ispindel->gravity);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)",\"og_gravity\":");
    snprintf(buf, 64, "%.6f", ispindel->og_gravity);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)",\"alarm\":");
    snprintf(buf, 64, "%d", ispindel->alarm);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)",\"lastseen\":");
    snprintf(buf, 64, "%ld", ispindel->lastseen);
    msg = xstrcat(msg, buf);
    msg = xstrcat(msg, (char *)"}");
    ws_broadcast(msg);
    free(msg);
    msg = NULL;
}



void ispindel_ws_receive(char *payload)
{
    struct json_object  *jobj, *val;
    sys_ispindel_list   *tmpp;
    char                *node = NULL, *alias = NULL, *mode = NULL, *beeruuid = NULL, *beercode = NULL, *beername = NULL;
    float		yeast_lo = 20, yeast_hi = 25;
    char                query[512], buf[65], *end;
    MYSQL               *con2 = NULL;

    /*
     * 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, "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, "mode", &val))
	mode = xstrcpy((char *)json_object_get_string(val));
    json_object_put(jobj);

    /*
     * 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) {
		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 {
			if (beeruuid && beercode && beername) {
			    end = stpcpy(query, "UPDATE mon_ispindels 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, "', og_gravity='0.0', yeast_lo='");
			    snprintf(buf, 64, "%.3f", yeast_lo);
			    end = stpcpy(end, buf);
			    end = stpcpy(end, "', yeast_hi='");
			    snprintf(buf, 64, "%.3f", yeast_hi);
			    end = stpcpy(end, buf);
			    end = stpcpy(end, "' WHERE node='");
                            end += mysql_real_escape_string(con2, end, node, strlen(node));
                            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 */
                                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);
				tmpp->og_gravity = 0.0;
				tmpp->yeast_lo = yeast_lo;
				tmpp->yeast_hi = yeast_hi;
                                /* Report new state to the client */
                                ispindel_ws_send(tmpp);
                                syslog(LOG_NOTICE, "Set ispindel %s/%s new beer %s %s", node, alias, beercode, beername);
			    }
			}
			if (mode) {
			    end = stpcpy(query, "UPDATE mon_ispindels SET mode='");
                            end += mysql_real_escape_string(con2, end, mode, strlen(mode));
                            end = stpcpy(end, "' WHERE node='");
                            end += mysql_real_escape_string(con2, end, node, strlen(node));
                            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 */
                                if (tmpp->mode)
                                    free(tmpp->mode);
                                tmpp->mode = xstrcpy(mode);
                                /* Report new state to the client */
                                ispindel_ws_send(tmpp);
                                syslog(LOG_NOTICE, "Set ispindel %s/%s new mode %s", node, alias, mode);
                            }
			}
			mysql_close(con2);
		    }
		}
		break;
            }
        }
    }

    if (node)
        free(node);
    if (alias)
        free(alias);
    if (mode)
	free(mode);
    if (beeruuid)
        free(beeruuid);
    if (beercode)
        free(beercode);
    if (beername)
        free(beername);
}



void ispindel_set(char *node, char *payload)
{
    sys_ispindel_list	*ispindel, *tmpp;
    struct json_object  *jobj, *metric, *val;
    bool		new_ispindel = true;
    char		*datetime, *query = malloc(512);
    struct tm           *mytime;
    time_t              timestamp;

    //syslog(LOG_NOTICE, "ispindel_set: %s %s", 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;
	    }
	}
    }

//    if (debug)
//    	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);
	ispindel->mode = xstrcpy((char *)"OFF");
    }

    if (! ispindel->online) {
    	ispindel->online = true;
    	syslog(LOG_NOTICE, "Online ispindel %s mode %s", node, ispindel->mode);
    }

    /*
     * Process the JSON formatted payload.
     * Update only the fields that are found in the payload.
     */
    jobj = json_tokener_parse(payload);

    if (json_object_object_get_ex(jobj, "unit", &metric)) {
	ispindel->lastseen = time(NULL);
	if (json_object_object_get_ex(metric, "uuid", &val)) {
            if (ispindel->uuid)
                free(ispindel->uuid);
            ispindel->uuid = xstrcpy((char *)json_object_get_string(val));
        }
	if (json_object_object_get_ex(metric, "alias", &val)) {
            if (ispindel->alias)
                free(ispindel->alias);
            ispindel->alias = xstrcpy((char *)json_object_get_string(val));
        }
	if (json_object_object_get_ex(metric, "alarm", &val))
            ispindel->alarm = json_object_get_int(val);
	if (json_object_object_get_ex(metric, "interval", &val))
            ispindel->interval = json_object_get_int(val);
	if (json_object_object_get_ex(metric, "angle", &val))
            ispindel->angle = json_object_get_double(val);
	if (json_object_object_get_ex(metric, "temperature", &val))
            ispindel->temperature = json_object_get_double(val);
	if (json_object_object_get_ex(metric, "battery", &val))
            ispindel->battery = json_object_get_double(val);
	if (json_object_object_get_ex(metric, "gravity", &val))
            ispindel->gravity = json_object_get_double(val);
    }

    //ispindel_dump(ispindel);

    /*
     * If a new iSpindel, insert record.
     */
    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);
    }

    /*
     * Calculate Plato using the calibration data.
     * Then update again and make it available.
     */
    ispindel->gravity = ispindel_mysql_plato(ispindel->uuid, ispindel->angle);
    if (ispindel->gravity > ispindel->og_gravity)
	ispindel->og_gravity = ispindel->gravity;
    ispindel_mysql_update(ispindel);
    ispindel_ws_send(ispindel);

    /*
     * The data is complete, see if we can write a log entry.
     */
    if (ispindel->beercode && strlen(ispindel->beercode) && ispindel->beername && strlen(ispindel->beername) &&
	ispindel->online && (strcmp(ispindel->mode, (char *)"ON") == 0)) {
	datetime = malloc(72);
	timestamp = time(NULL);
	mytime = localtime(&timestamp);
    	snprintf(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);

	snprintf(query, 511, "INSERT IGNORE INTO log_ispindel SET code='%s', datetime='%s', " \
			"temperature='%.4f', plato='%.5f', sg='%.5f', battery='%.6f', " \
			"angle='%.5f', refresh='%d', uuid='%s'",
			ispindel->beercode, datetime, ispindel->temperature, ispindel->gravity, plato_to_sg(ispindel->gravity),
			ispindel->battery, ispindel->angle, ispindel->interval, ispindel->uuid);
	//syslog(LOG_NOTICE, "%s", query);
	bms_mysql_query(query);

	free(datetime);
	datetime = NULL;
    }
}



/*
 * Process iSpindel MQTT message.
 */
void ispindel_birth_data(char *topic, char *payload)
{
    char		*message_type, *edge_node;

    strtok(topic, "/"); // ignore namespace
    strtok(NULL, "/");
    message_type = strtok(NULL, "/");
    edge_node = strtok(NULL, "/\0");

    if (strcmp("DBIRTH", message_type) == 0) {
	ispindel_set(edge_node, payload);
    }
}



void ispindel_dump(sys_ispindel_list *ispindel)
{
    if (debug) {
    	printf("node/alias  %s / %s\n", ispindel->node, ispindel->alias);
	printf("uuid        %s\n", ispindel->uuid);
	printf("online      %s\n", ispindel->online ? "yes":"no");
	printf("mode        %s\n", ispindel->mode);
	printf("product     %s / %s\n", ispindel->beercode, ispindel->beername);
        printf("tilt        %.5f\n", ispindel->angle);
	printf("temperature %.4f\n", ispindel->temperature);
	printf("battery     %.6f\n", ispindel->battery);
	printf("gravity     %.5f\n", ispindel->gravity);
	printf("last seen   %ld\n", ispindel->lastseen);
	printf("interval    %d\n", ispindel->interval);
	printf("og_gravity  %.5f\n", ispindel->og_gravity);
	printf("yeast_lo    %.1f\n", ispindel->yeast_lo);
	printf("yeast_hi    %.1f\n", ispindel->yeast_hi);
	printf("calibrate   %s\n", ispindel->calibrate);
    }
}

mercurial