esp_mqtt/user/lang.c

801 wiersze
21 KiB
C
Czysty Zwykły widok Historia

2017-07-21 14:35:45 +00:00
#include "c_types.h"
#include "mem.h"
#include "osapi.h"
#include "lang.h"
#include "user_config.h"
#include "mqtt_topics.h"
#include "ntp.h"
#include "easygpio.h"
#define lang_debug //os_printf
#define lang_info //os_printf
extern void do_command(char *t1, char *t2, char *t3);
extern void con_print(uint8_t *str);
#define len_check(x) \
if (interpreter_status==SYNTAX_CHECK && next_token+(x) >= max_token) \
return syntax_error(next_token+(x), "end of text")
#define syn_chk (interpreter_status==SYNTAX_CHECK)
#define ON "\xf0"
#define CONFIG "\xf1"
typedef struct _var_entry_t {
uint8_t data[MAX_VAR_LEN];
uint32_t data_len;
Value_Type data_type;
} var_entry_t;
typedef struct _timestamp_entry_t {
uint8_t *ts;
bool happened;
} timestamp_entry_t;
char **my_token;
int max_token;
bool script_enabled = false;
bool in_topic_statement;
Interpreter_Status interpreter_status;
char *interpreter_topic;
char *interpreter_data;
int interpreter_data_len;
int interpreter_timer;
char *interpreter_timestamp;
int ts_counter;
static os_timer_t timers[MAX_TIMERS];
static var_entry_t vars[MAX_VARS];
static timestamp_entry_t timestamps[MAX_TIMESTAMPS];
static void ICACHE_FLASH_ATTR lang_timers_timeout(void *arg) {
interpreter_timer = (int)arg;
os_timer_disarm(&timers[interpreter_timer]);
if (!script_enabled)
return;
lang_debug("timer %d expired\r\n", interpreter_timer + 1);
interpreter_topic = interpreter_data = "";
interpreter_data_len = 0;
interpreter_status = TIMER;
parse_statement(0);
}
void ICACHE_FLASH_ATTR init_timestamps(uint8_t * curr_time) {
int i;
for (i = 0; i < ts_counter; i++) {
if (os_strcmp(curr_time, timestamps[i].ts) >= 0) {
timestamps[i].happened = true;
} else {
timestamps[i].happened = false;
}
}
}
void ICACHE_FLASH_ATTR check_timestamps(uint8_t * curr_time) {
int i;
if (!script_enabled)
return;
for (i = 0; i < ts_counter; i++) {
if (os_strcmp(curr_time, timestamps[i].ts) >= 0) {
if (timestamps[i].happened)
continue;
lang_info("timerstamp %s happened\r\n", timestamps[i].ts);
interpreter_topic = interpreter_data = "";
interpreter_data_len = 0;
interpreter_status = CLOCK;
interpreter_timestamp = timestamps[i].ts;
parse_statement(0);
timestamps[i].happened = true;
} else {
timestamps[i].happened = false;
}
}
}
void ICACHE_FLASH_ATTR test_tokens(void) {
int i;
for (i = 0; i < max_token; i++) {
lang_debug("<%s>", my_token[i]);
}
lang_debug("\r\n");
}
int ICACHE_FLASH_ATTR text_into_tokens(char *str) {
char *p, *q;
int token_count = 0;
bool in_token = false;
// preprocessing
lang_debug("lexxer preprocessing\r\n");
for (p = q = str; *p != 0; p++) {
// special case "on" keyword - replace by special token ON (0xf0)
if (!in_token && *p == 'o' && *(p + 1) == 'n' && *(p + 2) <= ' ') {
*q++ = '\xf0';
p += 1;
continue;
}
// special case "config" keyword - replace by special token CONFIG (0xf1)
if (!in_token && *p == 'c' && *(p + 1) == 'o' && *(p + 2) == 'n'
&& *(p + 3) == 'f' && *(p + 4) == 'i' && *(p + 5) == 'g' && *(p + 6) <= ' ') {
*q++ = '\xf1';
p += 5;
continue;
}
if (*p == '\\') {
// next char is quoted, copy that - skip this one
if (*(p + 1) != 0)
*q++ = *++p;
} else if (*p == '\"') {
// string quotation
in_token = !in_token;
*q++ = 1;
} else if (*p == '%' && !in_token) {
// comment till eol
for (; *p != 0; p++)
if (*p == '\n')
break;
} else if (*p <= ' ' && !in_token) {
// mark this as whitespace
*q++ = 1;
} else {
*q++ = *p;
}
}
*q = 0;
// eliminate double whitespace and count tokens
lang_debug("lexxer whitespaces\r\n");
in_token = false;
for (p = q = str; *p != 0; p++) {
if (*p == 1) {
if (in_token) {
*q++ = 1;
in_token = false;
}
} else {
if (!in_token) {
token_count++;
in_token = true;
}
*q++ = *p;
}
}
*q = 0;
lang_debug("found %d tokens\r\n", token_count);
my_token = (char **)os_malloc(token_count * sizeof(char *));
if (my_token == 0)
return 0;
// assign tokens
lang_debug("lexxer tokenize\r\n");
in_token = false;
token_count = 0;
for (p = str; *p != 0; p++) {
if (*p == 1) {
*p = '\0';
in_token = false;
} else {
if (!in_token) {
my_token[token_count++] = p;
in_token = true;
}
}
}
max_token = token_count;
return max_token;
}
void ICACHE_FLASH_ATTR free_tokens(void) {
if (my_token != NULL)
os_free((uint32_t *) my_token);
my_token = NULL;
}
bool ICACHE_FLASH_ATTR is_token(int i, char *s) {
if (i >= max_token)
return false;
//os_printf("cmp: %s %s\r\n", s, my_token[i]);
return os_strcmp(my_token[i], s) == 0;
}
int ICACHE_FLASH_ATTR search_token(int i, char *s) {
for (; i < max_token; i++)
if (is_token(i, s))
return i;
return max_token;
}
int ICACHE_FLASH_ATTR syntax_error(int i, char *message) {
int j;
os_sprintf(syntax_error_buffer, "Error (%s) at >>", message);
for (j = i; j < i + 5 && j < max_token; j++) {
int pos = os_strlen(syntax_error_buffer);
if (is_token(j, ON))
my_token[j] = "on";
if (is_token(j, CONFIG))
my_token[j] = "config";
if (sizeof(syntax_error_buffer) - pos - 2 > os_strlen(my_token[j])) {
os_sprintf(syntax_error_buffer + pos, "%s ", my_token[j]);
}
}
return -1;
}
int ICACHE_FLASH_ATTR parse_statement(int next_token) {
bool event_happened;
int on_token;
while (next_token < max_token) {
in_topic_statement = false;
if (!syn_chk)
next_token = search_token(next_token, ON);
if (is_token(next_token, ON)) {
lang_debug("statement on\r\n");
if ((next_token = parse_event(next_token + 1, &event_happened)) == -1)
return -1;
if (!syn_chk && !event_happened)
continue;
if (syn_chk && !is_token(next_token, "do"))
return syntax_error(next_token, "'do' expected");
if ((next_token = parse_action(next_token + 1, event_happened)) == -1)
return -1;
} else if (is_token(next_token, CONFIG)) {
next_token += 3;
} else {
return syntax_error(next_token, "'on' or 'config' expected");
}
}
return next_token;
}
int ICACHE_FLASH_ATTR parse_event(int next_token, bool * happend) {
*happend = false;
if (is_token(next_token, "init")) {
lang_debug("event init\r\n");
*happend = (interpreter_status == INIT || interpreter_status == RE_INIT);
return next_token + 1;
}
if (is_token(next_token, "topic")) {
lang_debug("event topic\r\n");
in_topic_statement = true;
len_check(2);
if (is_token(next_token + 1, "remote")) {
if (interpreter_status != TOPIC_REMOTE)
return next_token + 3;
} else if (is_token(next_token + 1, "local")) {
if (interpreter_status != TOPIC_LOCAL)
return next_token + 3;
} else {
return syntax_error(next_token + 1, "'local' or 'remote' expected");
}
*happend = Topics_matches(my_token[next_token + 2], true, interpreter_topic);
if (*happend)
lang_info("topic %s %s %s match\r\n", my_token[next_token + 1],
my_token[next_token + 2], interpreter_topic);
return next_token + 3;
}
if (is_token(next_token, "timer")) {
lang_debug("event timer\r\n");
len_check(1);
uint32_t timer_no = atoi(my_token[next_token + 1]);
if (timer_no == 0 || timer_no > MAX_TIMERS)
return syntax_error(next_token + 1, "invalid timer number");
if (interpreter_status == TIMER && interpreter_timer == --timer_no) {
lang_info("timer %s expired\r\n", my_token[next_token + 1]);
*happend = true;
}
return next_token + 2;
}
else if (is_token(next_token, "clock")) {
lang_debug("event clock\r\n");
len_check(1);
if (syn_chk && os_strlen(my_token[next_token + 1]) != 8)
return syntax_error(next_token, "invalid timestamp");
if (syn_chk) {
if (ts_counter >= MAX_TIMESTAMPS)
return syntax_error(next_token, "too many timestamps");
timestamps[ts_counter++].ts = my_token[next_token + 1];
}
*happend = (interpreter_status == CLOCK && os_strcmp(interpreter_timestamp, my_token[next_token + 1]) == 0);
return next_token + 2;
}
return syntax_error(next_token, "'init', 'topic', 'clock', or 'timer' expected");
}
int ICACHE_FLASH_ATTR parse_action(int next_token, bool doit) {
while (next_token < max_token && !is_token(next_token, ON)
&& !is_token(next_token, CONFIG) && !is_token(next_token, "endif")) {
lang_debug("action %s %s\r\n", my_token[next_token], doit ? "do" : "ignore");
bool is_nl = false;
if ((is_nl = is_token(next_token, "println")) || is_token(next_token, "print")) {
char *p_char;
int p_len;
Value_Type p_type;
len_check(1);
if ((next_token = parse_expression(next_token + 1, &p_char, &p_len, &p_type)) == -1)
return -1;
if (doit) {
con_print(p_char);
if (is_nl)
con_print("\r\n");
}
}
else if (is_token(next_token, "publish")) {
bool retained = false;
char *data;
int data_len;
Value_Type data_type;
char *topic;
int topic_len;
Value_Type topic_type;
int lr_token = next_token + 1;
len_check(3);
if ((next_token = parse_value(next_token + 2, &topic, &topic_len, &topic_type)) == -1)
return -1;
if ((next_token = parse_value(next_token, &data, &data_len, &data_type)) == -1)
return -1;
if (next_token < max_token && is_token(next_token, "retained")) {
retained = true;
next_token++;
}
if (doit) {
if (data_type == STRING_T && data_len > 0)
data_len--;
if (topic_type != STRING_T || Topics_hasWildcards(topic)) {
os_printf("invalid topic string\r\n");
return next_token;
}
}
#ifdef MQTT_CLIENT
if (is_token(lr_token, "remote")) {
if (doit && mqtt_connected) {
MQTT_Publish(&mqttClient, topic, data, data_len, 0, retained);
lang_info("published remote %s len: %d\r\n", topic, data_len);
}
} else
#endif
if (is_token(lr_token, "local")) {
if (doit && interpreter_status != RE_INIT) {
MQTT_local_publish(topic, data, data_len, 0, retained);
lang_info("published local %s len: %d\r\n", topic, data_len);
}
} else {
return syntax_error(lr_token, "'local' or 'remote' expected");
}
}
else if (is_token(next_token, "subscribe")) {
bool retval;
len_check(2);
#ifdef MQTT_CLIENT
if (is_token(next_token + 1, "remote")) {
if (doit && mqtt_connected) {
retval = MQTT_Subscribe(&mqttClient, my_token[next_token + 2], 0);
lang_info("subsrcibe remote %s %s\r\n", my_token[next_token + 2], retval ? "success" : "failed");
}
} else
#endif
if (is_token(next_token + 1, "local")) {
if (doit && interpreter_status != RE_INIT) {
retval = MQTT_local_subscribe(my_token[next_token + 2], 0);
lang_info("subsrcibe local %s %s\r\n", my_token[next_token + 2], retval ? "success" : "failed");
}
} else {
return syntax_error(next_token + 1, "'local' or 'remote' expected");
}
next_token += 3;
}
else if (is_token(next_token, "unsubscribe")) {
bool retval;
len_check(2);
#ifdef MQTT_CLIENT
if (is_token(next_token + 1, "remote")) {
if (doit && mqtt_connected) {
retval = MQTT_UnSubscribe(&mqttClient, my_token[next_token + 2]);
lang_info("unsubsrcibe remote %s %s\r\n", my_token[next_token + 2], retval ? "success" : "failed");
}
} else
#endif
if (is_token(next_token + 1, "local")) {
if (doit && interpreter_status != RE_INIT) {
retval = MQTT_local_unsubscribe(my_token[next_token + 2]);
lang_info("unsubsrcibe local %s %s\r\n", my_token[next_token + 2], retval ? "success" : "failed");
}
} else {
return syntax_error(next_token + 1, "'local' or 'remote' expected");
}
next_token += 3;
}
else if (is_token(next_token, "if")) {
uint32_t if_val;
char *if_char;
int if_len;
Value_Type if_type;
len_check(3);
if ((next_token = parse_expression(next_token + 1, &if_char, &if_len, &if_type)) == -1)
return -1;
if (syn_chk && !is_token(next_token, "then"))
return syntax_error(next_token, "'then' expected");
if (doit) {
if_val = atoi(if_char);
lang_info("if %s\r\n", if_val != 0 ? "done" : "not done");
}
if ((next_token = parse_action(next_token + 1, doit && if_val != 0)) == -1)
return -1;
}
else if (is_token(next_token, "settimer")) {
len_check(2);
uint32_t timer_no = atoi(my_token[next_token + 1]);
if (timer_no == 0 || timer_no > MAX_TIMERS)
return syntax_error(next_token + 1, "invalid timer number");
uint32_t timer_val;
char *timer_char;
int timer_len;
Value_Type timer_type;
if ((next_token = parse_value(next_token + 2, &timer_char, &timer_len, &timer_type)) == -1)
return -1;
if (doit) {
timer_val = atoi(timer_char);
lang_info("settimer %d %d\r\n", timer_no, timer_val);
timer_no--;
os_timer_disarm(&timers[timer_no]);
if (timer_val != 0) {
os_timer_setfn(&timers[timer_no], (os_timer_func_t *) lang_timers_timeout, timer_no);
os_timer_arm(&timers[timer_no], timer_val, 0);
}
}
}
else if (is_token(next_token, "setvar")) {
len_check(2);
if (my_token[next_token + 1][0] != '$')
return syntax_error(next_token + 1, "invalid var identifier");
uint32_t var_no = atoi(&(my_token[next_token + 1][1]));
if (var_no == 0 || var_no > MAX_VARS)
return syntax_error(next_token + 1, "invalid var number");
char *var_data;
int var_len;
Value_Type var_type;
if ((next_token = parse_expression(next_token + 2, &var_data, &var_len, &var_type)) == -1)
return -1;
if (doit) {
lang_info("setvar $%d \r\n", var_no);
if (var_len > MAX_VAR_LEN) {
os_printf("Var $%d too long '%s'\r\n", var_no, var_data);
return next_token;
}
var_no--;
os_memcpy(vars[var_no].data, var_data, var_len);
vars[var_no].data_len = var_len;
vars[var_no].data_type = var_type;
}
}
#ifdef GPIO
else if (is_token(next_token, "gpio_out")) {
len_check(2);
uint32_t gpio_no = atoi(my_token[next_token + 1]);
if (gpio_no > 16)
return syntax_error(next_token + 1, "invalid gpio number");
char *gpio_data;
int gpio_len;
Value_Type gpio_type;
if ((next_token = parse_expression(next_token + 2, &gpio_data, &gpio_len, &gpio_type)) == -1)
return -1;
if (doit) {
lang_info("gpio_out %d %d\r\n", gpio_no, atoi(gpio_data) != 0);
if (easygpio_pinMode(gpio_no, EASYGPIO_NOPULL, EASYGPIO_OUTPUT))
easygpio_outputSet(gpio_no, atoi(gpio_data) != 0);
}
}
#endif
else
return syntax_error(next_token, "action command expected");
}
if (is_token(next_token, "endif"))
next_token++;
return next_token;
}
int ICACHE_FLASH_ATTR parse_expression(int next_token, char **data, int *data_len, Value_Type * data_type) {
if (is_token(next_token, "not")) {
len_check(1);
lang_debug("expr not\r\n");
if ((next_token = parse_expression(next_token + 1, data, data_len, data_type)) == -1)
return -1;
*data = atoi(*data) ? "0" : "1";
*data_len = 1;
*data_type = STRING_T;
}
else {
if ((next_token = parse_value(next_token, data, data_len, data_type)) == -1)
return -1;
// if it is not some kind of binary operation - finished
if (!is_token(next_token, "eq")
&& !is_token(next_token, "add")
&& !is_token(next_token, "sub")
&& !is_token(next_token, "mult")
&& !is_token(next_token, "div")
&& !is_token(next_token, "gt")
&& !is_token(next_token, "gte")
&& !is_token(next_token, "str_gt")
&& !is_token(next_token, "str_gte"))
return next_token;
// okay, it is an operation
int op = next_token;
char *r_data;
int r_data_len;
Value_Type r_data_type;
static char res_str[10];
// parse second operand
if ((next_token = parse_expression(next_token + 1, &r_data, &r_data_len, &r_data_type)) == -1)
return -1;
//os_printf("l:%s(%d) r:%s(%d)\r\n", *data, *data_len, r_data, r_data_len);
*data_len = 1;
*data_type = STRING_T;
if (is_token(op, "eq")) {
*data = os_strcmp(*data, r_data) ? "0" : "1";
} else if (is_token(op, "gt")) {
*data = atoi(*data) > atoi(r_data) ? "1" : "0";
} else if (is_token(op, "gte")) {
*data = atoi(*data) >= atoi(r_data) ? "1" : "0";
} else if (is_token(op, "str_gt")) {
*data = os_strcmp(*data, r_data) > 0 ? "1" : "0";
} else if (is_token(op, "str_gte")) {
*data = os_strcmp(*data, r_data) >= 0 ? "1" : "0";
} else if (is_token(op, "add")) {
os_sprintf(res_str, "%d", atoi(*data) + atoi(r_data));
*data = res_str;
*data_len = os_strlen(res_str);
} else if (is_token(op, "sub")) {
os_sprintf(res_str, "%d", atoi(*data) - atoi(r_data));
*data = res_str;
*data_len = os_strlen(res_str);
} else if (is_token(op, "mult")) {
os_sprintf(res_str, "%d", atoi(*data) * atoi(r_data));
*data = res_str;
*data_len = os_strlen(res_str);
} else if (is_token(op, "div")) {
os_sprintf(res_str, "%d", atoi(*data) / atoi(r_data));
*data = res_str;
*data_len = os_strlen(res_str);
}
}
return next_token;
}
int ICACHE_FLASH_ATTR parse_value(int next_token, char **data, int *data_len, Value_Type * data_type) {
if (is_token(next_token, "$this_data")) {
lang_debug("val $this_data\r\n");
if (!in_topic_statement)
return syntax_error(next_token, "undefined $this_data");
*data = interpreter_data;
*data_len = interpreter_data_len;
*data_type = DATA_T;
return next_token + 1;
}
else if (is_token(next_token, "$this_topic")) {
lang_debug("val $this_topic\r\n");
if (!in_topic_statement)
return syntax_error(next_token, "undefined $this_topic");
*data = interpreter_topic;
*data_len = os_strlen(interpreter_topic) + 1;
*data_type = STRING_T;
return next_token + 1;
}
#ifdef NTP
else if (is_token(next_token, "$timestamp")) {
lang_debug("val $timestamp\r\n");
if (ntp_sync_done())
*data = get_timestr();
else
*data = "invalid";
*data_len = os_strlen(*data) + 1;
*data_type = STRING_T;
return next_token + 1;
}
#endif
/* else if (my_token[next_token][0] == '\'') {
char *p = &(my_token[next_token][1]);
int *b = (int*)&val_buf[0];
*b = atoi(htonl(p));
*data = val_buf;
*data_len = sizeof(int);
return next_token+1;
}
*/
else if (my_token[next_token][0] == '$' && my_token[next_token][1] <= '9') {
uint32_t var_no = atoi(&(my_token[next_token][1]));
if (var_no == 0 || var_no > MAX_VARS)
return syntax_error(next_token + 1, "invalid var number");
var_no--;
*data = vars[var_no].data;
*data_len = vars[var_no].data_len;
*data_type = vars[var_no].data_type;
return next_token + 1;
}
else if (my_token[next_token][0] == '#') {
lang_debug("val hexbinary\r\n");
// Convert it in place to binary once during syntax check
// Format: Byte 0: '#', Byte 1: len (max 255), Byte 2 to len: binary data, Byte len+1: 0
if (syn_chk) {
int i, j, len = os_strlen(my_token[next_token]);
uint8_t a, *p = &(my_token[next_token][1]);
if (len < 3)
return syntax_error(next_token, "hexbinary too short");
if (len > 511)
return syntax_error(next_token, "hexbinary too long");
for (i = 0, j = 1; i < len - 1; i += 2, j++) {
if (p[i] <= '9')
a = p[i] - '0';
else
a = toupper(p[i]) - 'A' + 10;
a <<= 4;
if (p[i + 1] <= '9')
a += p[i + 1] - '0';
else
a += toupper(p[i + 1]) - 'A' + 10;
p[j] = a;
}
p[j] = '\0';
p[0] = (uint8_t) j - 2;
}
*data = &my_token[next_token][2];
*data_len = my_token[next_token][1];
*data_type = DATA_T;
return next_token + 1;
}
else {
*data = my_token[next_token];
*data_len = os_strlen(my_token[next_token]) + 1;
*data_type = STRING_T;
return next_token + 1;
}
}
int ICACHE_FLASH_ATTR interpreter_syntax_check() {
lang_debug("interpreter_syntax_check\r\n");
os_sprintf(syntax_error_buffer, "Syntax okay");
interpreter_status = SYNTAX_CHECK;
interpreter_topic = interpreter_data = "";
interpreter_data_len = 0;
os_bzero(&timestamps, sizeof(timestamps));
ts_counter = 0;
return parse_statement(0);
}
int ICACHE_FLASH_ATTR interpreter_config() {
int next_token = 0;
while ((next_token = search_token(next_token, CONFIG)) < max_token) {
lang_debug("statement config\r\n");
len_check(2);
do_command("set", my_token[next_token + 1], my_token[next_token + 2]);
next_token += 3;
}
return next_token;
}
int ICACHE_FLASH_ATTR interpreter_init() {
if (!script_enabled)
return -1;
lang_debug("interpreter_init\r\n");
interpreter_status = INIT;
interpreter_topic = interpreter_data = "";
interpreter_data_len = 0;
return parse_statement(0);
}
int ICACHE_FLASH_ATTR interpreter_init_reconnect(void) {
if (!script_enabled)
return -1;
lang_debug("interpreter_init_reconnect\r\n");
interpreter_status = RE_INIT;
interpreter_topic = interpreter_data = "";
interpreter_data_len = 0;
return parse_statement(0);
}
int ICACHE_FLASH_ATTR interpreter_topic_received(const char *topic, const char *data, int data_len, bool local) {
if (!script_enabled)
return -1;
lang_debug("interpreter_topic_received\r\n");
interpreter_status = (local) ? TOPIC_LOCAL : TOPIC_REMOTE;
interpreter_topic = (char *)topic;
interpreter_data_len = data_len;
if ((interpreter_data = (uint8_t *) os_malloc(data_len + 1)) == 0) {
os_printf("Out of Memory\r\n");
return -1;
}
os_memcpy(interpreter_data, data, data_len);
interpreter_data[data_len] = '\0';
int retval = parse_statement(0);
os_free(interpreter_data);
return retval;
}