pull/16/head
Martin Ger 2017-08-17 10:23:13 +02:00
rodzic 4d941993f5
commit 30260a110d
6 zmienionych plików z 173 dodań i 106 usunięć

Wyświetl plik

@ -73,110 +73,125 @@ By default the "remote" MQTT client is disabled. It can be enabled by setting th
# Scripting
The esp_uMQTT_broker comes with a build-in scripting engine. A script enables the ESP not just to act as a passive broker but to react on events (publications and timing events) and to send out its own items.
Here is a demo of a script to give you an idea of the power of the scripting feature. This script controls a Sonoff switch module. It connects to a remote MQTT broker and in parallel offers locally its own. On both brokers it subscribes to a topic named '/martinshome/switch/1/command', where it receives commands, and it publishes the topic '/martinshome/switch/1/status' with the current state of the switch relay. It understands the commands 'on','off', 'toggle', and 'blink'. Blinking is realized via a timer event. Local status is stored in the two variables $1 (switch state) and $2 (blinking on/off). The 'on gpio_interrupt' clause reacts on pressing the pushbutton of the Sonnoff and simply toggles the switch (and stops blinking). The last two 'on clock' clauses implement a daily on and off period:
Here is a demo of a script to give you an idea of the power of the scripting feature. This script controls a Sonoff switch module. It connects to a remote MQTT broker and in parallel offers locally its own. The device has a number stored in the variable $device_number. On both brokers it subscribes to a topic named '/martinshome/switch/($device_number)/command', where it receives commands, and it publishes the topic '/martinshome/switch/($device_number)/status' with the current state of the switch relay. It understands the commands 'on','off', 'toggle', and 'blink'. Blinking is realized via a timer event. Local status is stored in the two variables $relay_status and $blink (blinking on/off). The 'on gpio_interrupt' clause reacts on pressing the pushbutton of the Sonoff and simply toggles the switch (and stops blinking). The last two 'on clock' clauses implement a daily on and off period:
```
% Config params, overwrite any previous settings from the commandline
config ap_ssid MyAP
config ap_password stupidPassword
config ntp_server 1.pool.ntp.org
config mqtt_host 192.168.1.20
config ntp_server 1.de.pool.ntp.org
config broker_user Martin
config broker_password secret
config mqtt_host martinshome.fritz.box
config speed 160
% Now the initialization, this is done once after booting
on init
do
% $1 status of the relay
setvar $1=0
gpio_out 12 $1
gpio_out 13 not ($1)
% Device number
setvar $device_number = 1
publish local /martinshome/switch/1/status $1 retained
publish remote /martinshome/switch/1/status $1 retained
% Status of the relay
setvar $relay_status=0
gpio_out 12 $relay_status
gpio_out 13 not ($relay_status)
% $2 is blink flag
setvar $2=0
% Blink flag
setvar $blink=0
% Command topic
setvar $command_topic="/martinshome/switch/" | $device_number | "/command"
% Status topic
setvar $status_topic="/martinshome/switch/" | $device_number | "/status"
publish local $status_topic $relay_status retained
publish remote $status_topic $relay_status retained
% local subscriptions once in 'init'
subscribe local /martinshome/switch/1/command
subscribe local $command_topic
% Now the MQTT client init, this is done each time the client connects
on mqttconnect
do
% remote subscriptions for each connection in 'mqttconnect'
subscribe remote /martinshome/switch/1/command
subscribe remote $command_topic
% Now the events, checked whenever something happens
% Is there a remote command?
on topic remote /martinshome/switch/1/command
on topic remote $command_topic
do
println "Received remote command: " | $this_data
% republish this locally - this does the action
publish local /martinshome/switch/1/command $this_data
publish local $command_topic $this_data
% Is there a local command?
on topic local /martinshome/switch/1/command
on topic local $command_topic
do
println "Received local command: " | $this_data
if $this_data = "on" then
setvar $1 = 1
setvar $2 = 0
gpio_out 12 $1
gpio_out 13 not ($1)
setvar $relay_status = 1
setvar $blink = 0
gpio_out 12 $relay_status
gpio_out 13 not ($relay_status)
endif
if $this_data = "off" then
setvar $1 = 0
setvar $2 = 0
gpio_out 12 $1
gpio_out 13 not ($1)
setvar $relay_status = 0
setvar $blink = 0
gpio_out 12 $relay_status
gpio_out 13 not ($relay_status)
endif
if $this_data = "toggle" then
setvar $1 = not ($1)
gpio_out 12 $1
gpio_out 13 not ($1)
setvar $relay_status = not ($relay_status)
gpio_out 12 $relay_status
gpio_out 13 not ($relay_status)
endif
if $this_data = "blink" then
setvar $2 = 1
setvar $blink = 1
settimer 1 500
endif
publish local /martinshome/switch/1/status $1 retained
publish remote /martinshome/switch/1/status $1 retained
publish local $status_topic $relay_status retained
publish remote $status_topic $relay_status retained
% The local pushbutton
on gpio_interrupt 0 pullup
do
println "New state GPIO 0: " | $this_gpio
if $this_gpio = 0 then
setvar $2 = 0
publish local /martinshome/switch/1/command "toggle"
setvar $blink = 0
publish local $command_topic "toggle"
endif
% Blinking
on timer 1
do
if $2 = 1 then
publish local /martinshome/switch/1/command "toggle"
if $blink = 1 then
publish local $command_topic "toggle"
settimer 1 500
endif
% Switch on in the evening
on clock 19:30:00
do
publish local /martinshome/switch/1/command "on"
publish local $command_topic "on"
% Switch off at night
on clock 01:00:00
do
publish local /martinshome/switch/1/command "off"
publish local $command_topic "off"
```
Currently the interpreter is configured for a maximum of 10 variables, with a significant id length of 15. Some (additional) vars contain special status: $this_topic and $this_data are only defined in 'on topic' clauses and contain the current topic and its data. $this_gpio contains the state of the GPIO in an 'on gpio_pinmode' clause and $timestamp contains the current time of day in 'hh:mm:ss' format.
In general, scripts have the following BNF:
```
@ -206,7 +221,7 @@ In general, scripts have the following BNF:
<op> := '=' | '>' | gte | str_ge | str_gte | '+' | '-' | '*' | '|' | div
<val> := <string> | <const> | #<hex-string> | $<num> | gpio_in(<num>) |
<val> := <string> | <const> | #<hex-string> | $[any ASCII]* | gpio_in(<num>) |
$this_item | $this_data | $this_gpio | $timestamp
<string> := "[any ASCII]*" | [any ASCII]*

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -22,7 +22,10 @@ if (interpreter_status==SYNTAX_CHECK && next_token+(x) >= max_token) \
#define syn_chk (interpreter_status==SYNTAX_CHECK)
typedef struct _var_entry_t {
uint8_t data[MAX_VAR_LEN];
uint8_t name[15];
uint8_t free;
uint32_t buffer_len;
uint8_t *data;
uint32_t data_len;
Value_Type data_type;
} var_entry_t;
@ -61,6 +64,24 @@ static os_timer_t timers[MAX_TIMERS];
static var_entry_t vars[MAX_VARS];
static timestamp_entry_t timestamps[MAX_TIMESTAMPS];
var_entry_t ICACHE_FLASH_ATTR *find_var(const uint8_t *name, var_entry_t **free_var) {
int i;
*free_var = NULL;
for (i = 0; i<MAX_VARS; i++) {
if (!vars[i].free) {
if (os_strncmp(name, vars[i].name, 14) == 0) {
lang_debug("var %s found at %d\r\n", vars[i].name, i);
return &vars[i];
}
} else {
if (*free_var == NULL)
*free_var = &vars[i];
}
}
return NULL;
}
static void ICACHE_FLASH_ATTR lang_timers_timeout(void *arg) {
interpreter_timer = (int)arg;
@ -700,11 +721,24 @@ int ICACHE_FLASH_ATTR parse_action(int next_token, bool doit) {
else if (is_token(next_token, "setvar")) {
len_check(3);
if (syn_chk && 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");
if (syn_chk && (my_token[next_token + 1][0] != '$' || my_token[next_token + 1][1] == '\0'))
return syntax_error(next_token, "invalid var identifier");
var_entry_t *this_var, *free_var;
this_var = find_var(&(my_token[next_token + 1][1]), &free_var);
if (this_var == NULL) {
if (free_var == NULL)
return syntax_error(next_token, "too many vars used");
this_var = free_var;
this_var->free = 0;
os_strncpy(this_var->name, &(my_token[next_token + 1][1]), 14);
this_var->name[14] = '\0';
this_var->data = (uint8_t *)os_malloc(DEFAULT_VAR_LEN);
this_var->buffer_len = DEFAULT_VAR_LEN;
}
if (syn_chk && os_strcmp(my_token[next_token + 2], "=") != 0)
return syntax_error(next_token + 2, "'=' expected");
@ -715,15 +749,22 @@ int ICACHE_FLASH_ATTR parse_action(int next_token, bool doit) {
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;
lang_debug("setvar $%s\r\n", this_var->name);
if (var_len > this_var->buffer_len - 1) {
os_free(this_var->data);
this_var->data = (uint8_t *)os_malloc(var_len+1);
this_var->buffer_len = var_len+1;
if (this_var->data == NULL) {
os_printf("Out of mem for var $%s\r\n", this_var->name);
this_var->data = (uint8_t *)os_malloc(DEFAULT_VAR_LEN);
this_var->buffer_len = DEFAULT_VAR_LEN;
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;
os_memcpy(this_var->data, var_data, var_len);
this_var->data[var_len] = '\0';
this_var->data_len = var_len;
this_var->data_type = var_type;
}
}
#ifdef GPIO
@ -928,20 +969,6 @@ int ICACHE_FLASH_ATTR parse_value(int next_token, char **data, int *data_len, Va
return next_token + 1;
}
else if (my_token[next_token][0] == '$' && my_token[next_token][1] <= '9') {
lang_debug("val var\r\n");
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");
@ -1024,6 +1051,21 @@ int ICACHE_FLASH_ATTR parse_value(int next_token, char **data, int *data_len, Va
return next_token + 1;
}
#endif
else if (my_token[next_token][0] == '$' && my_token[next_token][1] != '\0') {
lang_debug("val var %s\r\n", &(my_token[next_token][1]));
var_entry_t *this_var, *free_var;
this_var = find_var(&(my_token[next_token][1]), &free_var);
if (this_var == NULL)
return syntax_error(next_token, "unknown var name");
*data = this_var->data;
*data_len = this_var->data_len;
*data_type = this_var->data_type;
return next_token + 1;
}
else {
lang_debug("val num/str(%s)\r\n", my_token[next_token]);
@ -1037,6 +1079,13 @@ int ICACHE_FLASH_ATTR parse_value(int next_token, char **data, int *data_len, Va
int ICACHE_FLASH_ATTR interpreter_syntax_check() {
lang_debug("interpreter_syntax_check\r\n");
int i;
for (i = 0; i<MAX_VARS; i++) {
vars[i].free = 1;
vars[i].data = "";//(uint8_t *)os_malloc(MAX_VAR_LEN);
vars[i].data_len = 0;
}
os_sprintf(tmp_buffer, "Syntax okay");
interpreter_status = SYNTAX_CHECK;
interpreter_topic = interpreter_data = "";

Wyświetl plik

@ -1,6 +1,6 @@
% Config params, overwrite any previous settings from the commandline
config ap_ssid DerKluge
config ap_password Bonn2016
config ap_ssid MyAP
config ap_password stupidPassword
config ntp_server 1.de.pool.ntp.org
config broker_user Martin
config broker_password secret
@ -10,72 +10,75 @@ config speed 160
% Now the initialization, this is done once after booting
on init
do
% $1 status of the relay
setvar $1=0
gpio_out 12 $1
gpio_out 13 not ($1)
% Device number
setvar $device_number = 1
% $2 is blink flag
setvar $2=0
% Status of the relay
setvar $relay_status=0
gpio_out 12 $relay_status
gpio_out 13 not ($relay_status)
% $3 is the command topic
setvar $3="/martinshome/switch/1/command"
% Blink flag
setvar $blink=0
% $4 is the status topic
setvar $4="/martinshome/switch/1/status"
% Command topic
setvar $command_topic="/martinshome/switch/" | $device_number | "/command"
publish local $4 $1 retained
publish remote $4 $1 retained
% Status topic
setvar $status_topic="/martinshome/switch/" | $device_number | "/status"
publish local $status_topic $relay_status retained
publish remote $status_topic $relay_status retained
% local subscriptions once in 'init'
subscribe local $3
subscribe local $command_topic
% Now the MQTT client init, this is done each time the client connects
on mqttconnect
do
% remote subscriptions for each connection in 'mqttconnect'
subscribe remote $3
subscribe remote $command_topic
% Now the events, checked whenever something happens
% Is there a remote command?
on topic remote $3
on topic remote $command_topic
do
println "Received remote command: " | $this_data
% republish this locally - this does the action
publish local $3 $this_data
publish local $command_topic $this_data
% Is there a local command?
on topic local $3
on topic local $command_topic
do
println "Received local command: " | $this_data
if $this_data = "on" then
setvar $1 = 1
setvar $2 = 0
gpio_out 12 $1
gpio_out 13 not ($1)
setvar $relay_status = 1
setvar $blink = 0
gpio_out 12 $relay_status
gpio_out 13 not ($relay_status)
endif
if $this_data = "off" then
setvar $1 = 0
setvar $2 = 0
gpio_out 12 $1
gpio_out 13 not ($1)
setvar $relay_status = 0
setvar $blink = 0
gpio_out 12 $relay_status
gpio_out 13 not ($relay_status)
endif
if $this_data = "toggle" then
setvar $1 = not ($1)
gpio_out 12 $1
gpio_out 13 not ($1)
setvar $relay_status = not ($relay_status)
gpio_out 12 $relay_status
gpio_out 13 not ($relay_status)
endif
if $this_data = "blink" then
setvar $2 = 1
setvar $blink = 1
settimer 1 500
endif
publish local $4 $1 retained
publish remote $4 $1 retained
publish local $status_topic $relay_status retained
publish remote $status_topic $relay_status retained
% The local pushbutton
@ -83,16 +86,16 @@ on gpio_interrupt 0 pullup
do
println "New state GPIO 0: " | $this_gpio
if $this_gpio = 0 then
setvar $2 = 0
publish local $3 "toggle"
setvar $blink = 0
publish local $command_topic "toggle"
endif
% Blinking
on timer 1
do
if $2 = 1 then
publish local $3 "toggle"
if $blink = 1 then
publish local $command_topic "toggle"
settimer 1 500
endif
@ -101,9 +104,9 @@ do
% Switch on in the evening
on clock 19:30:00
do
publish local $3 "on"
publish local $command_topic "on"
% Switch off at night
on clock 01:00:00
do
publish local $3 "off"
publish local $command_topic "off"

Wyświetl plik

@ -40,8 +40,8 @@ typedef enum {SIG_DO_NOTHING=0, SIG_START_SERVER=1, SIG_UART0, SIG_TOPIC_RECEIVE
#define MAX_SCRIPT_SIZE 0x1000
#define MAX_TIMERS 4
#define MAX_GPIOS 3
#define MAX_VARS 8
#define MAX_VAR_LEN 64
#define MAX_VARS 10
#define DEFAULT_VAR_LEN 16
#define MAX_TIMESTAMPS 6
//