Skip to content

Instantly share code, notes, and snippets.

@CurlyMoo
Created April 12, 2021 19:52
Show Gist options
  • Save CurlyMoo/0ba1bceb26913266ba5c6c8df6c349f1 to your computer and use it in GitHub Desktop.
Save CurlyMoo/0ba1bceb26913266ba5c6c8df6c349f1 to your computer and use it in GitHub Desktop.
Rules library implementation
//Flash ide size: 4194304 bytes
//Flash ide speed: 40000000 Hz
//Flash ide mode: DIO
//Flash Chip configuration ok.
#include <FS.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <Arduino.h>
#include <EasyNTPClient.h>
#include <WiFiUdp.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <LittleFS.h>
#include "mem.h"
#include "rules.h"
#include "commands.h"
#include "decode.h"
#include "timerqueue.h"
typedef struct vm_gvchar_t {
VM_GENERIC_FIELDS
uint8_t rule;
char value[];
} __attribute__((packed)) vm_gvchar_t;
typedef struct vm_gvnull_t {
VM_GENERIC_FIELDS
uint8_t rule;
} __attribute__((packed)) vm_gvnull_t;
typedef struct vm_gvinteger_t {
VM_GENERIC_FIELDS
uint8_t rule;
int value;
} __attribute__((packed)) vm_gvinteger_t;
typedef struct vm_gvfloat_t {
VM_GENERIC_FIELDS
uint8_t rule;
float value;
} __attribute__((packed)) vm_gvfloat_t;
struct timerqueue_t **timerqueue = NULL;
struct timerqueue_t timerqueue_prev;
int timerqueue_size = 0;
struct tm lt;
struct tm st;
unsigned long nexttime = 0;
const char *ssid = "xxxx";
const char *password = "xxxx";
const char *mqttServer = "x.x.x.x";
const int mqttPort = 1883;
WiFiClient espClient;
PubSubClient mqtt_client(espClient);
WiFiUDP ntpUDP;
EasyNTPClient ntpClient(ntpUDP, "pool.ntp.org", 7200); // + 2 hours due to DST
static struct rules_t **rules = NULL;
static int nrrules = 0;
static char out[512];
char hp_values[NUMBER_OF_TOPICS][255];
typedef struct varstack_t {
unsigned char *stack;
unsigned int nrbytes;
unsigned int bufsize;
} varstack_t;
static struct varstack_t global_varstack;
static struct vm_vinteger_t vinteger;
static struct vm_vfloat_t vfloat;
static struct vm_vnull_t vnull;
struct rule_options_t rule_options;
static void vm_global_value_prt(char *out, int size);
static int strnicmp(char const *a, char const *b, size_t len) {
int i = 0;
if(a == NULL || b == NULL) {
return -1;
}
if(len == 0) {
return 0;
}
for(;i++<len; a++, b++) {
int d = tolower(*a) - tolower(*b);
if(d != 0 || !*a || i == len) {
return d;
}
}
return -1;
}
static int stricmp(char const *a, char const *b) {
int i = 0;
if(a == NULL || b == NULL) {
return -1;
}
for(;a; a++, b++) {
int d = tolower(*a) - tolower(*b);
if(d != 0 || !*a) {
return d;
}
}
return -1;
}
static int get_event(struct rules_t *obj) {
struct vm_tstart_t *start = (struct vm_tstart_t *)&obj->ast.buffer[0];
if(obj->ast.buffer[start->go] != TEVENT) {
return -1;
} else {
return start->go;
}
}
static int is_variable(char *text, int *pos, int size) {
int i = 1, x = 0, match = 0;
if(text[*pos] == '$' || text[*pos] == '#' || text[*pos] == '@' || text[*pos] == '%') {
while(isalnum(text[*pos+i])) {
i++;
}
if(text[*pos] == '%') {
if(strnicmp(&text[(*pos)+1], "hour", 4) == 0) {
return 5;
}
}
if(text[*pos] == '@') {
int nrcommands = sizeof(commands)/sizeof(commands[0]);
for(x=0;x<nrcommands;x++) {
if(strnicmp(&text[(*pos)+1], commands[x].name, strlen(commands[x].name)) == 0) {
i = strlen(commands[x].name)+1;
match = 1;
break;
}
}
for(x=0;x<NUMBER_OF_TOPICS;x++) {
if(strnicmp(&text[(*pos)+1], topics[x], strlen(topics[x])) == 0) {
i = strlen(topics[x])+1;
match = 1;
break;
}
}
if(match == 0) {
return -1;
}
}
return i;
}
return -1;
}
static int is_event(char *text, int *pos, int size) {
int i = 1, x = 0, match = 0;
if(text[*pos] == '@') {
int nrcommands = sizeof(commands)/sizeof(commands[0]);
for(x=0;x<nrcommands;x++) {
if(strnicmp(&text[(*pos)+1], commands[x].name, strlen(commands[x].name)) == 0) {
i = strlen(commands[x].name)+1;
match = 1;
break;
}
}
for(x=0;x<NUMBER_OF_TOPICS;x++) {
if(strnicmp(&text[(*pos)+1], topics[x], strlen(topics[x])) == 0) {
i = strlen(topics[x])+1;
match = 1;
break;
}
}
if(match == 0) {
return -1;
}
return i;
}
// for(x=0;x<nrrules;x++) {
// for(i=0;i<rules[x]->nrbytes;i++) {
// if(rules[x]->ast.buffer[0] == TEVENT) {
// if(strnicmp(&text[(*pos)], (char *)&rules[x]->ast.buffer[1], strlen((char *)&rules[x]->ast.buffer[1])) == 0) {
// return strlen((char *)&rules[x]->ast.buffer[1]);
// }
// }
// break;
// }
// }
// return -1;
return size;
}
static int event_cb(struct rules_t *obj, char *name) {
struct rules_t *called = NULL;
int i = 0, x = 0;
if(obj->caller > 0 && name == NULL) {
called = rules[obj->caller-1];
obj->caller = 0;
// sprintf((char *)&out, "...1 %p NULL", obj);
// Serial.println(out);
return rule_run(called, 0);
} else {
for(x=0;x<nrrules;x++) {
if(get_event(rules[x]) > -1) {
if(strnicmp(name, (char *)&rules[x]->ast.buffer[get_event(rules[x])+5], strlen((char *)&rules[x]->ast.buffer[get_event(rules[x])+5])) == 0) {
called = rules[x];
break;
}
}
if(called != NULL) {
break;
}
}
// sprintf((char *)&out, "...2 %p %s %p", obj, name, called);
// Serial.println(out);
if(called != NULL) {
called->caller = obj->nr;
return rule_run(called, 0);
} else {
return rule_run(obj, 0);
}
}
}
static void vm_value_clr(struct rules_t *obj, uint16_t token) {
struct varstack_t *varstack = (struct varstack_t *)obj->userdata;
struct vm_tvar_t *var = (struct vm_tvar_t *)&obj->ast.buffer[token];
if(var->token[1] == '$') {
var->value = 0;
}
}
static void vm_value_cpy(struct rules_t *obj, uint16_t token) {
struct varstack_t *varstack = (struct varstack_t *)obj->userdata;
struct vm_tvar_t *var = (struct vm_tvar_t *)&obj->ast.buffer[token];
int x = 0;
if(var->token[0] == '$') {
varstack = (struct varstack_t *)obj->userdata;
for(x=4;alignedbytes(x)<varstack->nrbytes;x++) {
x = alignedbytes(x);
switch(varstack->stack[x]) {
case VINTEGER: {
struct vm_vinteger_t *val = (struct vm_vinteger_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&obj->ast.buffer[val->ret];
if(strcmp((char *)foo->token, (char *)&var->token) == 0 && val->ret != token) {
var->value = foo->value;
val->ret = token;
foo->value = 0;
return;
}
x += sizeof(struct vm_vinteger_t)-1;
} break;
case VFLOAT: {
struct vm_vfloat_t *val = (struct vm_vfloat_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&obj->ast.buffer[val->ret];
if(strcmp((char *)foo->token, (char *)var->token) == 0 && val->ret != token) {
var->value = foo->value;
val->ret = token;
foo->value = 0;
return;
}
x += sizeof(struct vm_vfloat_t)-1;
} break;
case VNULL: {
struct vm_vnull_t *val = (struct vm_vnull_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&obj->ast.buffer[val->ret];
if(strcmp((char *)foo->token, (char *)&var->token) == 0 && val->ret != token) {
var->value = foo->value;
val->ret = token;
foo->value = 0;
return;
}
x += sizeof(struct vm_vnull_t)-1;
} break;
default: {
return;
} break;
}
}
} else if(var->token[0] == '#') {
varstack = &global_varstack;
for(x=4;alignedbytes(x)<varstack->nrbytes;x++) {
x = alignedbytes(x);
switch(varstack->stack[x]) {
case VINTEGER: {
struct vm_gvinteger_t *val = (struct vm_gvinteger_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
if(strcmp((char *)foo->token, (char *)var->token) == 0 && val->ret != token) {
var->value = x;
val->ret = token;
val->rule = obj->nr;
return;
}
x += sizeof(struct vm_gvinteger_t)-1;
} break;
case VFLOAT: {
struct vm_gvfloat_t *val = (struct vm_gvfloat_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
if(strcmp((char *)foo->token, (char *)var->token) == 0 && val->ret != token) {
var->value = x;
val->ret = token;
val->rule = obj->nr;
return;
}
x += sizeof(struct vm_gvfloat_t)-1;
} break;
case VNULL: {
struct vm_gvnull_t *val = (struct vm_gvnull_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
if(strcmp((char *)foo->token, (char *)var->token) == 0 && val->ret != token) {
var->value = x;
val->ret = token;
val->rule = obj->nr;
return;
}
x += sizeof(struct vm_gvnull_t)-1;
} break;
default: {
return;
} break;
}
}
}
}
static unsigned char *vm_value_get(struct rules_t *obj, uint16_t token) {
struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[token];
int i = 0;
if(node->token[0] == '$') {
struct varstack_t *varstack = (struct varstack_t *)obj->userdata;
if(node->value == 0) {
int ret = varstack->nrbytes, suffix = 0;
// sprintf((char *)&out, ".. %s %d %d", __FUNCTION__, __LINE__, ret);
// Serial.println(out);
unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_vnull_t));
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
struct vm_vnull_t *value = (struct vm_vnull_t *)&varstack->stack[ret];
value->type = VNULL;
value->ret = token;
node->value = ret;
varstack->nrbytes = size;
varstack->bufsize = alignedbuffer(size);
}
const char *key = (char *)node->token;
switch(varstack->stack[node->value]) {
case VINTEGER: {
struct vm_vinteger_t *na = (struct vm_vinteger_t *)&varstack->stack[node->value];
// sprintf((char *)&out, ".. %s %d %s = %d", __FUNCTION__, node->value, key, (int)na->value);
// Serial.println(out);
} break;
case VFLOAT: {
struct vm_vfloat_t *na = (struct vm_vfloat_t *)&varstack->stack[node->value];
// sprintf((char *)&out, ".. %s %d %s = %g", __FUNCTION__, node->value, key, na->value);
// Serial.println(out);
} break;
case VNULL: {
struct vm_vnull_t *na = (struct vm_vnull_t *)&varstack->stack[node->value];
// sprintf((char *)&out, ".. %s %d %s = NULL", __FUNCTION__, node->value, key);
// Serial.println(out);
} break;
case VCHAR: {
struct vm_vchar_t *na = (struct vm_vchar_t *)&varstack->stack[node->value];
// sprintf((char *)&out, ".. %s %d %s = %s", __FUNCTION__, node->value, key, na->value);
// Serial.println(out);
} break;
}
return &varstack->stack[node->value];
}
if(node->token[0] == '#') {
struct varstack_t *varstack = &global_varstack;
if(node->value == 0) {
int ret = varstack->nrbytes, suffix = 0;
// sprintf((char *)&out, ".. %s %d %d", __FUNCTION__, __LINE__, ret);
// Serial.println(out);
unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_gvnull_t));
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
struct vm_gvnull_t *value = (struct vm_gvnull_t *)&varstack->stack[ret];
value->type = VNULL;
value->ret = token;
value->rule = obj->nr;
node->value = ret;
varstack->nrbytes = size;
varstack->bufsize = alignedbuffer(size);
}
const char *key = (char *)node->token;
switch(varstack->stack[node->value]) {
case VINTEGER: {
struct vm_gvinteger_t *na = (struct vm_gvinteger_t *)&varstack->stack[node->value];
memset(&vinteger, 0, sizeof(struct vm_vinteger_t));
vinteger.type = VINTEGER;
vinteger.value = (int)na->value;
// sprintf((char *)&out, ".. %s %d %s = %d", __FUNCTION__, node->value, key, (int)na->value);
// Serial.println(out);
return (unsigned char *)&vinteger;
} break;
case VFLOAT: {
struct vm_gvfloat_t *na = (struct vm_gvfloat_t *)&varstack->stack[node->value];
memset(&vfloat, 0, sizeof(struct vm_vfloat_t));
vfloat.type = VFLOAT;
vfloat.value = na->value;
// sprintf((char *)&out, ".. %s %d %s = %g", __FUNCTION__, node->value, key, na->value);
// Serial.println(out);
return (unsigned char *)&vfloat;
} break;
case VNULL: {
struct vm_gvnull_t *na = (struct vm_gvnull_t *)&varstack->stack[node->value];
memset(&vnull, 0, sizeof(struct vm_vnull_t));
vnull.type = VNULL;
// sprintf((char *)&out, ".. %s %d %s = NULL", __FUNCTION__, node->value, key);
// Serial.println(out);
return (unsigned char *)&vnull;
} break;
case VCHAR: {
Serial.println("a");
exit(-1);
} break;
}
Serial.println("b");
exit(-1);
}
if(node->token[0] == '@') {
for(i=0;i<NUMBER_OF_TOPICS;i++) {
if(stricmp(topics[i], (char *)&node->token[1]) == 0) {
float var = atof(hp_values[i]);
float nr = 0;
// mosquitto_publish
if(modff(var, &nr) == 0) {
memset(&vinteger, 0, sizeof(struct vm_vinteger_t));
vinteger.type = VINTEGER;
vinteger.value = (int)var;
// sprintf((char *)&out, "%s %s = %d", __FUNCTION__, (char *)node->token, (int)var);
// Serial.println(out);
return (unsigned char *)&vinteger;
} else {
memset(&vfloat, 0, sizeof(struct vm_vfloat_t));
vfloat.type = VFLOAT;
vfloat.value = var;
// sprintf((char *)&out, "%s %s = %g", __FUNCTION__, (char *)node->token, var);
// Serial.println(out);
return (unsigned char *)&vfloat;
}
}
}
}
if(node->token[0] == '%') {
if(stricmp((char *)&node->token[1], "hour") == 0) {
memset(&vinteger, 0, sizeof(struct vm_vinteger_t));
vinteger.type = VINTEGER;
vinteger.value = (int)lt.tm_hour;
// sprintf((char *)&out, "%s %s = %d", __FUNCTION__, (char *)node->token, (int)lt.tm_hour);
// Serial.println(out);
return (unsigned char *)&vinteger;
}
}
return NULL;
}
static int vm_value_del(struct rules_t *obj, uint16_t idx) {
struct varstack_t *varstack = (struct varstack_t *)obj->userdata;
int x = 0, ret = 0;
if(idx == varstack->nrbytes) {
return -1;
}
switch(varstack->stack[idx]) {
case VINTEGER: {
ret = alignedbytes(sizeof(struct vm_vinteger_t));
memmove(&varstack->stack[idx], &varstack->stack[idx+ret], varstack->nrbytes-idx-ret);
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
varstack->nrbytes -= ret;
varstack->bufsize = alignedbuffer(varstack->nrbytes);
} break;
case VFLOAT: {
ret = alignedbytes(sizeof(struct vm_vfloat_t));
memmove(&varstack->stack[idx], &varstack->stack[idx+ret], varstack->nrbytes-idx-ret);
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack,alignedbuffer(varstack->nrbytes-ret))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
varstack->nrbytes -= ret;
varstack->bufsize = alignedbuffer(varstack->nrbytes);
} break;
case VNULL: {
ret = alignedbytes(sizeof(struct vm_vnull_t));
memmove(&varstack->stack[idx], &varstack->stack[idx+ret], varstack->nrbytes-idx-ret);
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
varstack->nrbytes -= ret;
varstack->bufsize = alignedbuffer(varstack->nrbytes);
} break;
default: {
return -1;
} break;
}
/*
* Values are linked back to their root node,
* by their absolute position in the bytecode.
* If a value is deleted, these positions changes,
* so we need to update all nodes.
*/
for(x=idx;alignedbytes(x)<varstack->nrbytes;x++) {
x = alignedbytes(x);
switch(varstack->stack[x]) {
case VINTEGER: {
struct vm_vinteger_t *node = (struct vm_vinteger_t *)&varstack->stack[x];
if(node->ret > 0) {
struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret];
tmp->value = x;
}
x += sizeof(struct vm_vinteger_t)-1;
} break;
case VFLOAT: {
struct vm_vfloat_t *node = (struct vm_vfloat_t *)&varstack->stack[x];
if(node->ret > 0) {
struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret];
tmp->value = x;
}
x += sizeof(struct vm_vfloat_t)-1;
} break;
default: {
return -1;
} break;
}
}
return ret;
}
static void vm_value_set(struct rules_t *obj, uint16_t token, uint16_t val) {
struct varstack_t *varstack = NULL;
struct vm_tvar_t *var = (struct vm_tvar_t *)&obj->ast.buffer[token];
int ret = 0, x = 0, loop = 1;
if(var->token[0] == '$') {
varstack = (struct varstack_t *)obj->userdata;
const char *key = (char *)var->token;
switch(obj->varstack.buffer[val]) {
case VINTEGER: {
struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, ".. %s %d %s = %d", __FUNCTION__, val, key, (int)na->value);
// Serial.println(out);
} break;
case VFLOAT: {
struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, ".. %s %d %s = %g", __FUNCTION__, val, key, na->value);
// Serial.println(out);
} break;
case VNULL: {
struct vm_vnull_t *na = (struct vm_vnull_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, ".. %s %d %s = NULL", __FUNCTION__, val, key);
// Serial.println(out);
} break;
case VCHAR: {
struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, ".. %s %d %s = %s", __FUNCTION__, val, key, na->value);
// Serial.println(out);
} break;
}
/*
* Remove previous value linked to
* the variable being set.
*/
for(x=4;alignedbytes(x)<varstack->nrbytes && loop == 1;x++) {
x = alignedbytes(x);
switch(varstack->stack[x]) {
case VINTEGER: {
struct vm_vinteger_t *node = (struct vm_vinteger_t *)&varstack->stack[x];
struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret];
if(strcmp((char *)var->token, (char *)tmp->token) == 0) {
var->value = 0;
vm_value_del(obj, x);
loop = 0;
break;
}
x += sizeof(struct vm_vinteger_t)-1;
} break;
case VFLOAT: {
struct vm_vfloat_t *node = (struct vm_vfloat_t *)&varstack->stack[x];
struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret];
if(strcmp((char *)var->token, (char *)tmp->token) == 0) {
var->value = 0;
vm_value_del(obj, x);
loop = 0;
break;
}
x += sizeof(struct vm_vfloat_t)-1;
} break;
case VNULL: {
struct vm_vnull_t *node = (struct vm_vnull_t *)&varstack->stack[x];
struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret];
if(strcmp((char *)var->token, (char *)tmp->token) == 0) {
var->value = 0;
vm_value_del(obj, x);
loop = 0;
break;
}
x += sizeof(struct vm_vnull_t)-1;
} break;
default: {
return;
} break;
}
}
var = (struct vm_tvar_t *)&obj->ast.buffer[token];
if(var->value > 0) {
vm_value_del(obj, var->value);
}
var = (struct vm_tvar_t *)&obj->ast.buffer[token];
ret = varstack->nrbytes;
var->value = ret;
switch(obj->varstack.buffer[val]) {
case VINTEGER: {
unsigned int size = alignedbytes(varstack->nrbytes+sizeof(struct vm_vinteger_t));
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
struct vm_vinteger_t *cpy = (struct vm_vinteger_t *)&obj->varstack.buffer[val];
struct vm_vinteger_t *value = (struct vm_vinteger_t *)&varstack->stack[ret];
value->type = VINTEGER;
value->ret = token;
value->value = (int)cpy->value;
varstack->nrbytes = size;
varstack->bufsize = alignedbuffer(size);
} break;
case VFLOAT: {
unsigned int size = alignedbytes(varstack->nrbytes+sizeof(struct vm_vfloat_t));
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
struct vm_vfloat_t *cpy = (struct vm_vfloat_t *)&obj->varstack.buffer[val];
struct vm_vfloat_t *value = (struct vm_vfloat_t *)&varstack->stack[ret];
value->type = VFLOAT;
value->ret = token;
value->value = cpy->value;
varstack->nrbytes = size;
varstack->bufsize = alignedbuffer(size);
} break;
case VNULL: {
unsigned int size = alignedbytes(varstack->nrbytes+sizeof(struct vm_vnull_t));
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
struct vm_vnull_t *value = (struct vm_vnull_t *)&varstack->stack[ret];
value->type = VNULL;
value->ret = token;
varstack->nrbytes = size;
varstack->bufsize = alignedbuffer(size);
} break;
default: {
return;
} break;
}
} else if(var->token[0] == '#') {
varstack = &global_varstack;
const char *key = (char *)var->token;
switch(obj->varstack.buffer[val]) {
case VINTEGER: {
struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, ".. %s %d %d %s = %d", __FUNCTION__, __LINE__, val, key, (int)na->value);
// Serial.println(out);
} break;
case VFLOAT: {
struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, ".. %s %d %d %s = %g", __FUNCTION__, __LINE__, val, key, na->value);
// Serial.println(out);
} break;
case VCHAR: {
struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, ".. %s %d %d %s = %s", __FUNCTION__, __LINE__, val, key, na->value);
// Serial.println(out);
} break;
case VNULL: {
struct vm_vnull_t *na = (struct vm_vnull_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, ".. %s %d %d %s = NULL", __FUNCTION__, __LINE__, val, key);
// Serial.println(out);
} break;
}
var = (struct vm_tvar_t *)&obj->ast.buffer[token];
int move = 0;
for(x=4;alignedbytes(x)<varstack->nrbytes;x++) {
x = alignedbytes(x);
switch(varstack->stack[x]) {
case VINTEGER: {
struct vm_gvinteger_t *val = (struct vm_gvinteger_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
// sprintf((char *)&out, ".. %s %d %d %d %d %s = %d", __FUNCTION__, __LINE__, x, val->ret, val->rule, foo->token, val->value);
// Serial.println(out);
if(strcmp((char *)foo->token, (char *)var->token) == 0) {
move = 1;
ret = alignedbytes(sizeof(struct vm_gvinteger_t));
memmove(&varstack->stack[x], &varstack->stack[x+ret], varstack->nrbytes-x-ret);
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
varstack->nrbytes -= ret;
varstack->bufsize = alignedbuffer(varstack->nrbytes);
}
} break;
case VFLOAT: {
struct vm_gvfloat_t *val = (struct vm_gvfloat_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
// sprintf((char *)&out, ".. %s %d %d %d %d %s = %g", __FUNCTION__, __LINE__, x, val->ret, val->rule, foo->token, val->value);
// Serial.println(out);
if(strcmp((char *)foo->token, (char *)var->token) == 0) {
move = 1;
ret = alignedbytes(sizeof(struct vm_gvfloat_t));
memmove(&varstack->stack[x], &varstack->stack[x+ret], varstack->nrbytes-x-ret);
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
varstack->nrbytes -= ret;
varstack->bufsize = alignedbuffer(varstack->nrbytes);
}
} break;
case VNULL: {
struct vm_gvnull_t *val = (struct vm_gvnull_t *)&varstack->stack[x];
struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
// sprintf((char *)&out, ".. %s %d %d %d %d %s = NULL", __FUNCTION__, __LINE__, x, val->ret, val->rule, foo->token);
// Serial.println(out);
if(strcmp((char *)foo->token, (char *)var->token) == 0) {
move = 1;
ret = alignedbytes(sizeof(struct vm_gvnull_t));
memmove(&varstack->stack[x], &varstack->stack[x+ret], varstack->nrbytes-x-ret);
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
varstack->nrbytes -= ret;
varstack->bufsize = alignedbuffer(varstack->nrbytes);
}
} break;
default: {
return;
} break;
}
if(x == varstack->nrbytes) {
break;
}
switch(varstack->stack[x]) {
case VINTEGER: {
if(move == 1 && x < varstack->nrbytes) {
struct vm_gvinteger_t *node = (struct vm_gvinteger_t *)&varstack->stack[x];
if(node->ret > 0) {
struct vm_tvar_t *tmp = (struct vm_tvar_t *)&rules[node->rule-1]->ast.buffer[node->ret];
tmp->value = x;
}
}
x += sizeof(struct vm_gvinteger_t)-1;
} break;
case VFLOAT: {
if(move == 1 && x < varstack->nrbytes) {
struct vm_gvfloat_t *node = (struct vm_gvfloat_t *)&varstack->stack[x];
if(node->ret > 0) {
struct vm_tvar_t *tmp = (struct vm_tvar_t *)&rules[node->rule-1]->ast.buffer[node->ret];
tmp->value = x;
}
}
x += sizeof(struct vm_gvfloat_t)-1;
} break;
case VNULL: {
if(move == 1 && x < varstack->nrbytes) {
struct vm_gvnull_t *node = (struct vm_gvnull_t *)&varstack->stack[x];
if(node->ret > 0) {
struct vm_tvar_t *tmp = (struct vm_tvar_t *)&rules[node->rule-1]->ast.buffer[node->ret];
tmp->value = x;
}
}
x += sizeof(struct vm_gvnull_t)-1;
} break;
}
}
var = (struct vm_tvar_t *)&obj->ast.buffer[token];
ret = varstack->nrbytes;
var->value = ret;
switch(obj->varstack.buffer[val]) {
case VINTEGER: {
unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_gvinteger_t));
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
struct vm_vinteger_t *cpy = (struct vm_vinteger_t *)&obj->varstack.buffer[val];
struct vm_gvinteger_t *value = (struct vm_gvinteger_t *)&varstack->stack[ret];
value->type = VINTEGER;
value->ret = token;
value->value = (int)cpy->value;
value->rule = obj->nr;
// sprintf((char *)&out, ".. %s %d %d %d %d %s = %d", __FUNCTION__, __LINE__, ret, token, obj->nr, var->token, cpy->value);
// Serial.println(out);
varstack->nrbytes = size;
varstack->bufsize = alignedbuffer(size);
} break;
case VFLOAT: {
unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_gvfloat_t));
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
struct vm_vfloat_t *cpy = (struct vm_vfloat_t *)&obj->varstack.buffer[val];
struct vm_gvfloat_t *value = (struct vm_gvfloat_t *)&varstack->stack[ret];
value->type = VFLOAT;
value->ret = token;
value->value = cpy->value;
value->rule = obj->nr;
// sprintf((char *)&out, ".. %s %d %d %d %d %s = %g", __FUNCTION__, __LINE__, ret, token, obj->nr, var->token, cpy->value);
// Serial.println(out);
varstack->nrbytes = size;
varstack->bufsize = alignedbuffer(size);
} break;
case VNULL: {
unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_gvnull_t));
if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) {
OUT_OF_MEMORY /*LCOV_EXCL_LINE*/
}
struct vm_gvnull_t *value = (struct vm_gvnull_t *)&varstack->stack[ret];
value->type = VNULL;
value->ret = token;
value->rule = obj->nr;
// sprintf((char *)&out, ".. %s %d %d %d %d %s = NULL", __FUNCTION__, __LINE__, ret, token, obj->nr, var->token);
// Serial.println(out);
varstack->nrbytes = size;
varstack->bufsize = alignedbuffer(size);
} break;
default: {
return;
} break;
}
} else if(var->token[0] == '@') {
char *topic = NULL, *payload = NULL;
const char *key = (char *)var->token;
int len = 0;
switch(obj->varstack.buffer[val]) {
case VINTEGER: {
struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, "%s %d %s = %d", __FUNCTION__, val, key, (int)na->value);
// Serial.println(out);
len = snprintf(NULL, 0, "%d", (int)na->value);
if((payload = (char *)MALLOC(len+1)) == NULL) {
OUT_OF_MEMORY
}
snprintf(payload, len+1, "%d", (int)na->value);
} break;
case VFLOAT: {
struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, "%s %d %s = %g", __FUNCTION__, val, key, na->value);
// Serial.println(out);
len = snprintf(NULL, 0, "%g", (float)na->value);
if((payload = (char *)MALLOC(len+1)) == NULL) {
OUT_OF_MEMORY
}
snprintf(payload, len+1, "%g", (float)na->value);
} break;
case VCHAR: {
struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[val];
// sprintf((char *)&out, "%s %d %s = %s", __FUNCTION__, val, key, na->value);
// Serial.println(out);
len = snprintf(NULL, 0, "%s", na->value);
if((payload = (char *)MALLOC(len+1)) == NULL) {
OUT_OF_MEMORY
}
snprintf(payload, len+1, "%s", na->value);
} break;
}
len = snprintf(NULL, 0, "panasonic_heat_pump/commands/%s", &var->token[1]);
if((topic = (char *)MALLOC(len+1)) == NULL) {
OUT_OF_MEMORY
}
snprintf(topic, len+1, "panasonic_heat_pump/commands/%s", &var->token[1]);
// mosquitto_publish(mosq, NULL, topic, strlen(payload), payload, 0, 0);
FREE(topic);
FREE(payload);
}
}
static void vm_value_prt(struct rules_t *obj, char *out, int size) {
struct varstack_t *varstack = (struct varstack_t *)obj->userdata;
int x = 0, pos = 0;
for(x=4;alignedbytes(x)<varstack->nrbytes;x++) {
if(alignedbytes(x) < varstack->nrbytes) {
x = alignedbytes(x);
switch(varstack->stack[x]) {
case VINTEGER: {
struct vm_vinteger_t *val = (struct vm_vinteger_t *)&varstack->stack[x];
switch(obj->ast.buffer[val->ret]) {
case TVAR: {
struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret];
pos += snprintf(&out[pos], size - pos, "%s = %d\n", node->token, val->value);
} break;
default: {
// printf("err: %s %d %d\n", __FUNCTION__, __LINE__, obj->ast.buffer[val->ret]);
// exit(-1);
} break;
}
x += sizeof(struct vm_vinteger_t)-1;
} break;
case VFLOAT: {
struct vm_vfloat_t *val = (struct vm_vfloat_t *)&varstack->stack[x];
switch(obj->ast.buffer[val->ret]) {
case TVAR: {
struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret];
pos += snprintf(&out[pos], size - pos, "%s = %g\n", node->token, val->value);
} break;
default: {
// printf("err: %s %d\n", __FUNCTION__, __LINE__);
// exit(-1);
} break;
}
x += sizeof(struct vm_vfloat_t)-1;
} break;
case VNULL: {
struct vm_vnull_t *val = (struct vm_vnull_t *)&varstack->stack[x];
switch(obj->ast.buffer[val->ret]) {
case TVAR: {
struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret];
pos += snprintf(&out[pos], size - pos, "%s = NULL\n", node->token);
} break;
default: {
// printf("err: %s %d\n", __FUNCTION__, __LINE__);
// exit(-1);
} break;
}
x += sizeof(struct vm_vnull_t)-1;
} break;
default: {
return;
} break;
}
}
}
}
static void vm_global_value_prt(char *out, int size) {
struct varstack_t *varstack = &global_varstack;
int x = 0, pos = 0;
for(x=4;alignedbytes(x)<varstack->nrbytes;x++) {
x = alignedbytes(x);
switch(varstack->stack[x]) {
case VINTEGER: {
struct vm_gvinteger_t *val = (struct vm_gvinteger_t *)&varstack->stack[x];
switch(rules[val->rule-1]->ast.buffer[val->ret]) {
case TVAR: {
struct vm_tvar_t *node = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
pos += snprintf(&out[pos], size - pos, "%d %s = %d\n", x, node->token, val->value);
} break;
default: {
// printf("err: %s %d %d\n", __FUNCTION__, __LINE__, obj->ast.buffer[val->ret]);
// exit(-1);
} break;
}
x += sizeof(struct vm_gvinteger_t)-1;
} break;
case VFLOAT: {
struct vm_gvfloat_t *val = (struct vm_gvfloat_t *)&varstack->stack[x];
switch(rules[val->rule-1]->ast.buffer[val->ret]) {
case TVAR: {
struct vm_tvar_t *node = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
pos += snprintf(&out[pos], size - pos, "%d %s = %g\n", x, node->token, val->value);
} break;
default: {
// printf("err: %s %d\n", __FUNCTION__, __LINE__);
// exit(-1);
} break;
}
x += sizeof(struct vm_gvfloat_t)-1;
} break;
case VNULL: {
struct vm_gvnull_t *val = (struct vm_gvnull_t *)&varstack->stack[x];
switch(rules[val->rule-1]->ast.buffer[val->ret]) {
case TVAR: {
struct vm_tvar_t *node = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret];
pos += snprintf(&out[pos], size - pos, "%d %s = NULL\n", x, node->token);
} break;
default: {
// printf("err: %s %d\n", __FUNCTION__, __LINE__);
// exit(-1);
} break;
}
x += sizeof(struct vm_gvnull_t)-1;
} break;
default: {
return;
} break;
}
}
}
void vm_clear_values(struct rules_t *obj) {
int i = 0, x = 0;
for(i=x;alignedbytes(i)<obj->ast.nrbytes;i++) {
i = alignedbytes(i);
switch(obj->ast.buffer[i]) {
case TSTART: {
i+=sizeof(struct vm_tstart_t)-1;
} break;
case TEOF: {
i+=sizeof(struct vm_teof_t)-1;
} break;
case VNULL: {
i+=sizeof(struct vm_vnull_t)-1;
} break;
case TIF: {
i+=sizeof(struct vm_tif_t)-1;
} break;
case LPAREN: {
struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[i];
node->value = 0;
i+=sizeof(struct vm_lparen_t)-1;
} break;
case TFALSE:
case TTRUE: {
struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[i];
i+=sizeof(struct vm_ttrue_t)+(sizeof(node->go[0])*node->nrgo)-1;
} break;
case TFUNCTION: {
struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[i];
node->value = 0;
i+=sizeof(struct vm_tfunction_t)+(sizeof(node->go[0])*node->nrgo)-1;
} break;
case TCEVENT: {
struct vm_tcevent_t *node = (struct vm_tcevent_t *)&obj->ast.buffer[i];
i+=sizeof(struct vm_tcevent_t)+strlen((char *)node->token);
} break;
case TVAR: {
struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[i];
node->value = 0;
i+=sizeof(struct vm_tvar_t)+strlen((char *)node->token);
} break;
case TEVENT: {
struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[i];
i += sizeof(struct vm_tevent_t)+strlen((char *)node->token);
} break;
case TNUMBER: {
struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[i];
i+=sizeof(struct vm_tnumber_t)+strlen((char *)node->token);
} break;
case VINTEGER: {
struct vm_vinteger_t *node = (struct vm_vinteger_t *)&obj->ast.buffer[i];
i+=sizeof(struct vm_vinteger_t)-1;
} break;
case VFLOAT: {
struct vm_vfloat_t *node = (struct vm_vfloat_t *)&obj->ast.buffer[i];
i+=sizeof(struct vm_vfloat_t)-1;
} break;
case TOPERATOR: {
struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[i];
node->value = 0;
i+=sizeof(struct vm_toperator_t)-1;
} break;
default: {
} break;
}
}
}
void mqtt_callback(char *topic, byte *payload, unsigned int length) {
int i = 0, offset = strlen("panasonic_heat_pump/main");
/*
* This value has been overridden so ignore.
*/
if(strstr(topic, "panasonic_heat_pump/main/Room_Thermostat_Temp") != NULL) {
return;
}
if(strstr(topic, "woonkamer/temperature/temperature") != NULL) {
topic = "panasonic_heat_pump/main/Room_Thermostat_Temp";
}
for(i=0;i<NUMBER_OF_TOPICS;i++) {
if(strstr(topic, "panasonic_heat_pump/main") != NULL) {
if(strcmp(topics[i], &topic[offset+1]) == 0 && length < 255) {
memcpy(hp_values[i], (char *)payload, length);
hp_values[i][length] = 0;
}
}
}
if((
strstr(topic, "panasonic_heat_pump/main") != NULL ||
strstr(topic, "woonkamer/temperature/temperature") != NULL
)
) {
for(i=0;i<nrrules;i++) {
if(get_event(rules[i]) > -1 && rules[i]->ast.buffer[get_event(rules[i])+5] == '@' &&
strnicmp((char *)&rules[i]->ast.buffer[get_event(rules[i])+6], (char *)&topic[offset+1], strlen(&topic[offset+1])) == 0) {
sprintf((char *)&out, "\n\n==== %s ====\n", (char *)&rules[i]->ast.buffer[get_event(rules[i])+6]);
Serial.println(out);
sprintf((char *)&out, ">>> rule %d nrbytes: %d", i, rules[i]->ast.nrbytes);
Serial.println(out);
sprintf((char *)&out, ">>> global stack nrbytes: %d", global_varstack.nrbytes);
Serial.println(out);
rules[i]->timestamp.first = micros();
rule_run(rules[i], 0);
rules[i]->timestamp.second = micros();
sprintf((char *)&out, "rule #%d was executed in %d microseconds", rules[i]->nr, rules[i]->timestamp.second - rules[i]->timestamp.first);
Serial.println(out);
Serial.println("\n>>> local variables");
vm_value_prt(rules[i], (char *)&out, 512);
Serial.println(out);
Serial.println(">>> global variables");
vm_global_value_prt((char *)&out, 512);
Serial.println(out);
}
}
int x = 0;
for(i=0;i<nrrules;i++) {
x += rules[i]->ast.nrbytes;
}
sprintf((char *)&out, ">>> total rule bytes: %d", x+global_varstack.nrbytes);
Serial.println(out);
}
}
void setup() {
memset(&hp_values, 0, 255*NUMBER_OF_TOPICS);
int tmp = ESP.getFreeHeap();
Serial.begin(115200);
Serial.print("Start: ");
Serial.println(tmp, DEC);
Serial.print("Serial: ");
Serial.println(ESP.getFreeHeap(), DEC);
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
Serial.print("WiFI: ");
Serial.println(ESP.getFreeHeap(), DEC);
time_t t = ntpClient.getUnixTime();
memset(&lt, 0, sizeof(struct tm));
memset(&st, 0, sizeof(struct tm));
localtime_r(&t, &lt);
localtime_r(&t, &st);
mqtt_client.setServer(mqttServer, mqttPort);
mqtt_client.setCallback(mqtt_callback);
while(!mqtt_client.connected()) {
Serial.println("Connecting to MQTT...");
if(mqtt_client.connect("ESPHeishaDaemon", NULL, NULL)) {
Serial.println("connected");
} else {
Serial.print("failed with state ");
Serial.print(mqtt_client.state());
delay(2000);
}
}
Serial.print("MQTT: ");
Serial.println(ESP.getFreeHeap(), DEC);
LittleFS.begin();
if(LittleFS.exists("/rules.txt")) {
Serial.println("Reading rules");
File frules = LittleFS.open("/rules.txt", "r");
int len = frules.size();
char *content = (char *)malloc(len+1);
memset(content, 0, len+1);
frules.readBytes(content, len);
Serial.print("Rules read: ");
Serial.println(ESP.getFreeHeap(), DEC);
global_varstack.stack = NULL;
global_varstack.nrbytes = 4;
memset(&rule_options, 0, sizeof(struct rule_options_t));
rule_options.is_token_cb = is_variable;
rule_options.is_event_cb = is_event;
rule_options.set_token_val_cb = vm_value_set;
rule_options.get_token_val_cb = vm_value_get;
rule_options.prt_token_val_cb = vm_value_prt;
rule_options.cpy_token_val_cb = vm_value_cpy;
rule_options.clr_token_val_cb = vm_value_clr;
rule_options.event_cb = event_cb;
struct varstack_t *varstack = (struct varstack_t *)MALLOC(sizeof(struct varstack_t));
if(varstack == NULL) {
OUT_OF_MEMORY
}
varstack->stack = NULL;
varstack->nrbytes = 4;
varstack->bufsize = 4;
Serial.print("Varstack: ");
Serial.println(ESP.getFreeHeap(), DEC);
while(rule_initialize(&content, &rules, &nrrules, varstack) == 0) {
Serial.print("Rules read: ");
Serial.println(ESP.getFreeHeap(), DEC);
varstack = (struct varstack_t *)MALLOC(sizeof(struct varstack_t));
if(varstack == NULL) {
OUT_OF_MEMORY
}
varstack->stack = NULL;
varstack->nrbytes = 4;
varstack->bufsize = 4;
}
FREE(varstack);
FREE(content);
int i = 0;
for(i=0;i<nrrules;i++) {
vm_clear_values(rules[i]);
}
FREE(global_varstack.stack);
global_varstack.stack = NULL;
global_varstack.nrbytes = 4;
/*
* Clear all timers
*/
// struct itimerval it_val;
struct timerqueue_t *node = NULL;
while((node = timerqueue_pop()) != NULL) {
FREE(node);
}
// it_val.it_value.tv_sec = 0;
// it_val.it_value.tv_usec = 0;
// it_val.it_interval = it_val.it_value;
// setitimer(ITIMER_REAL, &it_val, NULL);
frules.close();
free(content);
}
Serial.println("done");
for(int i=0;i<nrrules;i++) {
if(get_event(rules[i]) > -1 && strcmp((char *)&rules[i]->ast.buffer[get_event(rules[i])+5], "System#Boot") == 0) {
sprintf((char *)&out, "\n\n==== SYSTEM#BOOT ====\n");
rule_run(rules[i], 0);
}
}
mqtt_client.subscribe("panasonic_heat_pump/main/#");
mqtt_client.subscribe("woonkamer/temperature/temperature");
}
void loop() {
if (millis() > nexttime) {
time_t t = ntpClient.getUnixTime();
memset(&lt, 0, sizeof(struct tm));
localtime_r(&t, &lt);
Serial.println(asctime(&st));
nexttime = millis() + 1000;
}
mqtt_client.loop();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment