class sch { datalen = 8; idlen = 8; id = 1; messages = {} cb_event = null constructor(name) { this.name = name; this.ID = name; } add_schema_from_event(k, s) { var payload = []; for (var [name, ap] of Object.entries(s.args)) { switch (ap.type) { case 'string': payload.push({ name: `${name}_len`, size: Math.ceil(Math.log2(ap.max_len || 8)) + 1, descr: `Length of ${ap.descr}`, }) payload.push({ name, size: 8, n: `${name}_len`, descr: ap.descr }) break; case 'uint8_t': payload.push({ name, size: 8, descr: ap.descr }) break; case 'uint16_t': payload.push({ name, size: 16, descr: ap.descr }) break; case 'uint64_t': payload.push({ name, size: 64, descr: ap.descr }) break; default: payload.push({ name, size: 32, descr: ap.descr }) } } this.messages[k] = { descr: s.descr, id: s.id, payload, } } build_c() { var s = "" var h = "" var max_len = 0; s += `#include "stdlib.h"\n`; s += `#include "mqttsn_util.h"\n`; s += `#include \n`; s += `#include \n`; s += `#include "${"sm_" + this.name + ".h"}"\n`; s += "\n"; s += "\n"; s += `void sm_${this.ID}_event_decode(sm_ctx_t *ctx, int len, unsigned char *buf)\n{\n` s += ` sm_${this.ID}_event_set(ctx, 0);\n`; s += ` if (!len || !buf) return;\n`; s += ` MQTT_get_bits(len, buf, 0, MQTTSN_datalen);\n`; s += ` int id = MQTT_get_bits(len, buf, MQTTSN_datalen, MQTTSN_idlen);\n`; s += ` int bit_position = MQTTSN_datalen + MQTTSN_idlen ;\n` s += ` sm_${this.ID}_event_set(ctx, id);\n` s += ` switch (id)\n {\n` h += "\n"; h += "#pragma once\n"; h += "\n"; h += `#define MQTTSN_idlen ${this.idlen}\n` h += `#define MQTTSN_datalen ${this.datalen}\n` h += "\n"; h += "enum {\n"; for (const [key, msg] of Object.entries(this.messages)) { h += ` sm_${this.ID}_event_${key} = 0x${sprintf("%02X", msg.id)},\n` } h += "};\n"; h += "\n"; h += `void sm_${this.ID}_event_decode(sm_ctx_t *ctx, int len, unsigned char *buf);\n` for (const [key, msg] of Object.entries(this.messages)) { s += ` case sm_${this.ID}_event_${key}:\n` s += ` {\n` var i = 0; var bit_position = this.idlen; for (var p of msg.payload) { var type = "unsigned int" var name = p.name; var wname = p.name; if (p.size <= 8) type = "uint8_t" else type = `uint${p.size}_t` if (p.n) name = name + "[64]" if (p.n) wname = "*" + p.name if (p.n) { if (typeof p.n == "string") s += ` ${type} ${name};\n`; else s += ` ${type} ${p.name}[${p.n}];\n`; s += ` for (int i = 0; i < ${p.n}; i++)\n`; s += ` ${p.name}[i] = MQTT_get_bits(len, buf, bit_position + ${p.size} * i, ${p.size});\n`; s += ` ${p.name}[name_len] = 0;\n`; bit_position += p.size s += ` bit_position += ${p.size} * ${p.n};\n` } else { s += ` sm_${this.ID}_${key}__${p.name}_set(ctx, MQTT_get_bits(len, buf, bit_position, ${p.size}));\n`; s += ` bit_position += ${p.size};\n` } i++; } s += ` ctx->on_event(ctx);\n\n`; s += ` }\n` s += ` break;\n` } s += ` }\n` s += `}\n\n` for (const [key, msg] of Object.entries(this.messages)) { var proto = `int sm_${this.name}_event_encode_${key}(sm_ctx_t *ctx, char *buf, int maxlen`; var len = 0; proto += `)`; h += `${proto};\n`; s += `${proto}\n{\n`; s += `\n`; s += ` int bit_counter = MQTTSN_datalen;\n\n`; s += ` bit_counter += MQTT_push_bits(buf, maxlen, bit_counter, MQTTSN_idlen, sm_${this.ID}_event_${key});\n`; for (var p of msg.payload) { var type = "int" if (p.size <= 8) type = "unsigned char" else if (p.size <= 16) type = "unsigned short int" if (p.n) { s += ` char *s = sm_${this.ID}_${key}__${p.name}_get();\n`; s += ` for (int i = 0; i < ${p.n}; i++)\n`; s += ` bit_counter += MQTT_push_bits(buf, maxlen, bit_counter, ${p.size}, s[i]);\n`; len += p.size * p.n; } else { len += p.size; s += ` bit_counter += MQTT_push_bits(buf, maxlen, bit_counter, ${p.size}, sm_${this.ID}_${key}__${p.name}_get(ctx));\n`; } } s += ` MQTT_push_bits(buf, maxlen, 0, MQTTSN_datalen, (bit_counter + 7) / 8);\n`; len = Math.floor((len + this.idlen + this.datalen + 7) / 8) s += ` // packet len: ${len}\n`; s += "\n return (bit_counter + 7) / 8;\n" s += "}\n\n"; if (len > max_len) max_len = len; } h += `\n#define sm_${this.ID}_max_len ${max_len}\n`; return { schema_c: s, schema_h: h }; } } log_magenta("lib/l_schema.js loaded"); class fsm { start_stamp = 0; current_state = 'INIT'; size_t = "uint64_t" float_t = "float" v = {} files = {} event_q = []; remote = false remote_vars = {} remote_key = false remote_state = '' fatal(s) { console.error(`**** FATAL: ${s}\nEXITING.`); $$$("NHMI").add_log({ fatal: s, event: 'FATAL', new_state: '' }) } stamp() { var now = (new Date).getTime(); if (this.start_stamp == 0) { this.start_stamp = now; return 0; } return now - this.start_stamp; } push_event(e, o) { this.event_q.push({ e, o, stamp: this.stamp(), }); } constructor(ID, cb_event) { this.cb_event = cb_event; this.ID = ID; this.sch = new sch(this.ID); this.fsm = window[`json_sm_${this.ID}`]; //json5.parse(fs.readFileSync(fn).toString()) this.log_fn = `${this.ID}.csv`; this.running = true; var c_i = 0; var set_var = (dom, k, p) => { p.domain = dom; p.changed = false; if (!p.type) p.type = "uint32_t"; if (p.type == "uint32_t") p.type = this.size_t; //on pc p.numerical = p.type.match(/int/) != null p.string = p.type.match(/string/) != null p.boolean = p.type.match(/bool/) != null c_i = `e_sm_${this.ID}_${k}`; if (p.numerical) { if (!p.decimals) p.decimals = 0; if (!p.val) p.val = 0; p.get = () => { return p.val / Math.pow(10, p.decimals) } if (!p.decimals) p.c_get = `${p.type} sm_${this.ID}_${k}_get(sm_ctx_t *ctx) { return ctx->var[${c_i}].val;} ` else p.c_get = `${this.float_t} sm_${this.ID}_${k}_get(sm_ctx_t *ctx) { return ((${this.float_t})ctx->var[${c_i}].val)/${Math.pow(10, p.decimals)};} ` } else { p.get = () => { return p.val } if (p.string) { if (!p.val) p.val = ''; } } if (['timers'].indexOf(dom) != -1) { p.enabled = false; p.set = (v) => { if (v) { p.val = this.stamp() - 1; p.enabled = true; p.changed = true; } else if (p.enabled) { p.enabled = false; p.changed = true; } } p.c_set = `void sm_${this.ID}_${k}_set(sm_ctx_t *ctx,${this.size_t} val) { if (val) {ctx->var[${c_i}].val = stamp() - 1 ;\nctx->var[${c_i}].enabled=true;\nctx->var[${c_i}].changed=true;} else if (ctx->var[${c_i}].enabled) {ctx->var[${c_i}].enabled=false;\nctx->var[${c_i}].changed=true;} } ` p.get = () => { if (p.enabled) return Math.max(this.stamp() - p.val, 0); else return 0; } p.c_get = `${p.type} sm_${this.ID}_${k}_get(sm_ctx_t *ctx) { return stamp() - ctx->var[${c_i}].val;} ` } else if (['private', 'output', 'input', 'params'].indexOf(dom) != -1) { var name = k; if (dom == 'input') name = 'internal_' + k if (p.numerical) { p.set = (v) => { var v = v * Math.pow(10, p.decimals); if (v != p.val) { p.val = v; p.changed = true; } } if (!p.decimals) p.c_set = `void sm_${this.ID}_${name}_set(sm_ctx_t *ctx, ${p.type} val) { if (ctx->var[${c_i}].val == val) return;\nctx->var[${c_i}].val = val;\nctx->var[${c_i}].changed = true; } ` else p.c_set = `void sm_${this.ID}_${name}_set(sm_ctx_t *ctx, ${this.float_t} val) { ctx->var[${c_i}].val = val * ${Math.pow(10, p.decimals)}; } ` } else { p.set = (v) => { if (p.val != v) { p.val = v p.changed = true; } } if (p.string) { p.c_set = `void sm_${this.ID}_${k}_set(sm_ctx_t *ctx, char *val) { ctx->var[${c_i}].val = (${this.size_t})val; } ` p.c_get = `char *sm_${this.ID}_${k}_get(sm_ctx_t *ctx) { return (char *)(ctx->var[${c_i}].val);}` } else { //if (p.boolean) p.c_set = `void sm_${this.ID}_${k}_set(sm_ctx_t *ctx, ${p.type} val) { if (ctx->var[${c_i}].val == val) return;\n ctx->var[${c_i}].val = (${this.size_t})val;\nctx->var[${c_i}].changed = true; } ` p.c_get = `${p.type} sm_${this.ID}_${k}_get(sm_ctx_t *ctx) { return ctx->var[${c_i}].val;}` } } } else { p.set = (v) => { this.fatal(`CANNOT MODIFY '${k}' as it's domain is '${dom}'`) } p.c_set = `void sm_${this.ID}_${k}_set(sm_ctx_t *ctx, ${p.type} val) { /* CANNOT MODIFY '${k}' as it's domain is '${dom}' */ } ` } p.gett = () => { if (this.remote) return this.remote_vars[k] else return p.get() } this.v[k] = p; c_i += 1; } set_var('private', 'tick', {}); set_var('timers', 'state_tick', {}); set_var('private', 'event', { type: "uint32_t" }); for (var [k, p] of Object.entries(this.fsm.vars.params)) set_var('params', k, p); for (var [k, p] of Object.entries(this.fsm.vars.input)) set_var('input', k, p); for (var [k, p] of Object.entries(this.fsm.vars.private)) set_var('private', k, p); for (var [k, p] of Object.entries(this.fsm.vars.output)) set_var('output', k, p); for (var [k, p] of Object.entries(this.fsm.vars.timers)) set_var('timers', k, p); for (var [k, p] of Object.entries(this.fsm.events)) { this.sch.add_schema_from_event(k, this.fsm.events[k]); for (var [ak, ap] of Object.entries(this.fsm.events[k].args)) { set_var('private', `${k}__${ak}`, ap); } } for (var [k, p] of Object.entries(this.fsm.funcs)) { var dom = 'funcs'; var ops = [] var c_ops = [] for (var op of p.split(/ +/)) { var c_op = op; if ( ((op.substr(0, 1) >= 'a') && (op.substr(0, 1) <= 'z')) || ((op.substr(0, 1) >= 'A') && (op.substr(0, 1) <= 'Z')) ) { // if (op.indexOf('.') != -1) { // op = '100'; // } else c_op = `sm_${this.ID}_${op}_get(ctx)`; op = `this.v.${op}.get()`; } ops.push(op) if (c_op.substr(0, 1) == '"') { //needs to be general string .. == & != with literals/string types either side var op2 = c_ops.pop(); var op1 = c_ops.pop(); c_ops.push(`(strcmp(${op1},${c_op})==0)`) } else c_ops.push(c_op) } p = ops.join(" "); var c_p = c_ops.join(" "); var pp = this.v[k] = { domain: dom, get: eval(`() => { return Math.round(${p}); }`), c_get: `bool sm_${this.ID}_${k}_get(sm_ctx_t *ctx) { return ${c_p};} `, set: (v) => { this.fatal(`CANNOT MODIFY '${k}' as it's domain is '${dom}'`) }, // descr: p, } this.v[k].gett = () => { if (this.remote) return this.remote_vars[k] else return pp.get() } var logs = "STAMP;CUR_STATE;NEXT_STATE;"; for (var [k, p] of Object.entries(this.v)) { logs += `${k};`; } //fs.writeFileSync(this.log_fn, logs + "\n"); } var c = []; var ch = []; var ch_internal = []; var c_fsm_v = []; var vars_cnt = Object.keys(this.v).length; c.push(`// FSM ${this.ID}`); c.push(``); c.push(`#include "stdlib.h"`); c.push(`#include `); c.push(`#include `); c.push(`#include `); c.push(`#include "sm_${this.ID}.h"`); c.push(`#include "sm_${this.ID}_internal.h"`); ch.push(`#pragma once`); var MAX_SOURCE_STATES = 0; for (var [k, p] of Object.entries(this.fsm.transitions)) { if (p.sources.length > MAX_SOURCE_STATES) MAX_SOURCE_STATES = p.sources.length } //ch.push(`#define MAX_SOURCE_STATES ${MAX_SOURCE_STATES}`); ch.push(`#define sm_${this.ID}_max_source_states ${MAX_SOURCE_STATES}`); ch.push(``); ch.push(``); // ch.push(`#if sm_t2_max_source_states > MAX_SOURCE_STATES`); // ch.push(`#error "sm_t2_max_source_states < MAX_SOURCE_STATES"`); // ch.push(`#endif`); ch.push(``); ch.push(`#include "sm.h"`); ch.push(`#include "sm_${this.ID}_states.h"`); ch.push(`#include "sm_${this.ID}_transitions.h"`); ch.push(`#include "sm_${this.ID}_internal.h"`); ch.push(`#include "sm_${this.ID}_schema.h"`); ch.push(``); ch.push(`extern sm_var_info_t sm_${this.ID}_var_info[];`); ch.push(`extern sm_var_t sm_${this.ID}_var_init[];`); ch.push(``); c_fsm_v.push(``); c_fsm_v.push(``); c_fsm_v.push(`sm_var_info_t sm_${this.ID}_var_info[] = {`); for (var [k, p] of Object.entries(this.v)) { c_fsm_v.push(`{"${k}","${p.descr || ''}","${p.domain}"}, // ${JSON.stringify(p)}`); } c_fsm_v.push(`};`); c_fsm_v.push(``); c_fsm_v.push(`sm_var_t sm_${this.ID}_var_init[] = {`); ch.push(`enum {`); var i = 0; for (var [k, p] of Object.entries(this.v)) { ch.push(`e_sm_${this.ID}_${k} = ${i++},`); } ch.push(`e_sm_${this.ID}_variables = ${i}`); ch.push(`};`); ch.push(``); for (var [k, p] of Object.entries(this.v)) { c.push(``); c.push(`// ${k} ${p.domain}`); if (p.c_get) { c.push(p.c_get); ch.push(p.c_get.replace('/n', ' ').replace(/\ *\{.+/, ';')); } if (p.c_set) { c.push(p.c_set) var first_line = p.c_set.split('\n')[0]; if (first_line.indexOf("_internal_") == -1) ch.push(first_line.replace(/\ *\{.+/, ';')); else ch_internal.push(first_line.replace(/\ *\{.+/, ';')); } c_fsm_v.push(` {${p.val || 0} , false, false,},`); } c_fsm_v.push(`};`); c_fsm_v.push(``); var c_states_c = []; var c_states_h = []; c_states_h.push(`#pragma once`); c_states_h.push(``); c_states_h.push(`#include "sm.h"`); c_states_h.push(``); c_states_h.push(`extern sm_states_t sm_${this.ID}_state[];`); c_states_h.push(``); c_states_h.push(`enum {`); c_states_c.push(`#include "stdlib.h"`); c_states_c.push(`#include `); c_states_c.push(`#include `); c_states_c.push(`#include `); c_states_c.push(`#include "sm_${this.ID}.h"`); c_states_c.push(`#include "sm_${this.ID}_states.h"`); c_states_c.push(``); for (var [k, p] of Object.entries(this.fsm.states)) { if (p.on_entry) { c_states_c.push(`void sm_${this.ID}_state_${k}_on_entry(sm_ctx_t *ctx) {`); for (var [kk, val] of Object.entries(p.on_entry)) { c_states_c.push(`sm_${this.ID}_${kk}_set(ctx, ${val});`); } c_states_c.push(`}`); c_states_c.push(``); } if (p.on_exit) { c_states_c.push(`void sm_${this.ID}_state_${k}_on_exit(sm_ctx_t *ctx) {`); for (var [kk, val] of Object.entries(p.on_exit)) { c_states_c.push(`sm_${this.ID}_${kk}_set(ctx, ${val});`); } c_states_c.push(`}`); c_states_c.push(``); } } c_states_c.push(`sm_states_t sm_${this.ID}_state[] ={`); var i = 0; for (var [k, p] of Object.entries(this.fsm.states)) { c_states_h.push(`sm_${this.ID}_e_state_${k} = ${i++},`); var on_entry = 'NULL'; var on_exit = 'NULL'; if (p.on_entry) on_entry = `sm_${this.ID}_state_${k}_on_entry` if (p.on_exit) on_exit = `sm_${this.ID}_state_${k}_on_exit` c_states_c.push(`{"${k}","${p.descr}",${on_entry},${on_exit}},`); } c_states_c.push(`};`); c_states_h.push(`sm_${this.ID}_e_states = ${i}`); c_states_h.push(`};`); var c_transitions_c = []; var c_transitions_h = []; c_transitions_c.push(`#include "stdlib.h"`); c_transitions_c.push(`#include `); c_transitions_c.push(`#include `); c_transitions_c.push(`#include `); c_transitions_c.push(`#include "sm_${this.ID}.h"`); c_transitions_c.push(`#include "sm_${this.ID}_states.h"`); c_transitions_c.push(`#include "sm_${this.ID}_transitions.h"`); c_transitions_c.push(``); c_transitions_h.push(`#pragma once`); c_transitions_h.push(``); c_transitions_h.push(`#include "sm.h"`); c_transitions_h.push(``); c_transitions_h.push(`extern sm_transitions_t sm_${this.ID}_transitions[];`); c_transitions_h.push(``); for (var [k, p] of Object.entries(this.fsm.transitions)) { if (p.set) { c_transitions_c.push(`void sm_${this.ID}_transition_${k}_set(sm_ctx_t *ctx) {`); for (var [kk, val] of Object.entries(p.set)) { c_transitions_c.push(`sm_${this.ID}_${kk}_set(ctx, ${val});`); } c_transitions_c.push(`}`); c_transitions_c.push(``); } } c_transitions_h.push(`enum {`); c_transitions_c.push(`sm_transitions_t sm_${this.ID}_transitions[] ={`); var i = 0; for (var [k, p] of Object.entries(this.fsm.transitions)) { c_transitions_h.push(`sm_${this.ID}_e_transition_${k} = ${i++},`); var sources = []; for (var src of p.sources) { if (src != '*') sources.push(`sm_${this.ID}_e_state_${src}`) else sources.push(`MATCH_ANY_STATE`) } while (sources.length < MAX_SOURCE_STATES) sources.push(`MATCH_NO_STATE`) var set = "NULL" if (p.set) set = `sm_${this.ID}_transition_${k}_set`; var when = "NULL" if (p.when && (p.when !== true)) when = `sm_${this.ID}_${p.when}_get`; c_transitions_c.push(`{"${k}",sm_${this.ID}_e_state_${p.target},{${sources.join(',')}},${set},${when}},`); } c_transitions_h.push(`sm_${this.ID}_e_transitions = ${i}`); c_transitions_h.push(`};`); c_transitions_c.push(`};`); this.files = { transitions_c: c_transitions_c.join("\n"), transitions_h: c_transitions_h.join("\n"), states_c: c_states_c.join("\n"), states_h: c_states_h.join("\n"), c: c.join("\n") + c_fsm_v.join("\n"), h: ch.join("\n"), internal_h: ch_internal.join("\n"), ...this.sch.build_c(), } } event(e, args) { if (e in this.fsm.events) { console.log("eventti", this.fsm.events[e], args); this.do_event(e, args); } else this.fatal(`UNKNOWN Event ${e}`); } do_event() { var now = this.stamp(); // for (var [k, p] of Object.entries(this.v)) { // this.v[k].changed = false; // } if (!this.running) return this.v.event.set(0); if (this.event_q.length) { var ev = this.event_q.pop() this.v.event.set(this.fsm.events[ev.e].id); console.log("EVENTTI", ev, this.fsm.events[ev.e]); for (var [k, p] of Object.entries(ev.o)) { console.log("EVENTTI set", `${ev.e}__${k}`, p); this.v[`${ev.e}__${k}`].set(p); } if (this.cb_event) this.cb_event(this, ev.e, ev.o); } var hit = false; var old_state = this.current_state; for (var [k, t] of Object.entries(this.fsm.transitions)) { if ((t.sources == "*") || (t.sources.indexOf(this.current_state) != -1)) { hit = false; if (t.when === true) hit = true; else { hit = this.v[t.when].get(); } if (hit) { console.log("hits", this.current_state, t.target, k, t.set, "\n") var set_list = {}; var setter = (sk, v) => { if (sk in set_list) { if (v != set_list[sk]) { this.fatal(`CONFLICT SETTING ${sk}: ${v}/${set_list[sk]}) at state '${this.current_state}' transition '${k}'`) } } set_list[sk] = v; } if ("on_exit" in this.fsm.states[this.current_state]) { for (var [sk, sv] of Object.entries(this.fsm.states[this.current_state].on_exit)) setter(sk, sv); } this.current_state = t.target; this.v.state_tick.set(true); for (var [sk, sv] of Object.entries(t.set)) setter(sk, sv); if ("on_entry" in this.fsm.states[this.current_state]) { for (var [sk, sv] of Object.entries(this.fsm.states[this.current_state].on_entry)) setter(sk, sv); } for (var [sk, sv] of Object.entries(set_list)) { console.log("settin", sk, sv); this.v[sk].set(sv); } if (this.cb_event) this.cb_event(this, k, t.set); // $$$("NHMI").add_log({ // event: k, // old_state, // new_state: t.target, // info: JSON.stringify(t.set), // }) break; } } } // for (var [k, p] of Object.entries(this.v)) { // this.v[k].changed = false; // } this.v.event.set(0); var do_log = () => { var logs = sprintf("%d,%s,%s,", this.stamp(), old_state, this.current_state); for (var [k, p] of Object.entries(this.v)) { //logs += `${p.val || ""},`; if (p.domain != 'params') logs += `${this.v[k].get() || ""},`; } log_magenta(logs); } if (hit) { do_log() } else if ((this.v.tick.get() % 100) == 0) { var logs = sprintf("%d,%s,%s,", this.stamp(), this.current_state, ""); for (var [k, p] of Object.entries(this.v)) { if (p.domain != 'params') logs += `${this.v[k].get() || ""},`; } //fs.appendFileSync(this.log_fn, logs + "\n"); } this.v.tick.set(this.v.tick.get() + 1); return hit; } } window.sch = sch; window.fsm = fsm;