kopia lustrzana https://github.com/martin-ger/esp_mqtt
added var ids
rodzic
4d941993f5
commit
30260a110d
89
README.md
89
README.md
|
@ -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.
105
user/lang.c
105
user/lang.c
|
@ -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 = "";
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
//
|
||||
|
|
Ładowanie…
Reference in New Issue