queuing of 'on topic' events

pull/16/head
Martin Ger 2017-08-15 22:42:41 +02:00
rodzic aef3f73e03
commit 10700b7b94
13 zmienionych plików z 281 dodań i 130 usunięć

Wyświetl plik

@ -33,7 +33,7 @@ ESPPORT ?= /dev/ttyUSB0
TARGET = app
# which modules (subdirectories) of the project to include in compiling
MODULES = driver user mqtt ntp easygpio
MODULES = driver user mqtt ntp easygpio cJSON
#EXTRA_INCDIR = $(BUILD_AREA)/esp-open-sdk/esp-open-lwip/include include
EXTRA_INCDIR = include

121
README.md
Wyświetl plik

@ -36,9 +36,9 @@ Basic commands (enough to get it working in nearly all environments):
- set [ap_ssid|ap_password] _value_: changes the settings for the soft-AP of the ESP (for your stations)
- show [config|stats|script|mqtt]: prints the current config or some status information and statistics
- save: saves the current config parameters to flash
- reset [factory]: resets the esp, optionally resets WiFi params to default values
- lock [_password_]: saves and locks the current config, changes are not allowed. Password can be left open if already set before
- unlock _password_: unlocks the config, requires password from the lock command
- reset [factory]: resets the esp, 'factory' optionally resets WiFi params to default values (works on a locked device only from serial console)
- quit: terminates a remote session
Advanced commands (most of the set-commands are effective only after save and reset):
@ -70,7 +70,8 @@ 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 small script to give you an idea of the power of the scripting feature:
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 toggle 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
@ -81,58 +82,94 @@ config mqtt_host 192.168.1.20
% Now the initialization, this is done once after booting
on init
do
println "MQTT Script 1.0 starting"
subscribe local /test/#
settimer 1 1000 % once per second
% $1 status of the relay
setvar $1=0
gpio_out 12 $1
gpio_out 13 not ($1)
publish local /martinshome/switch/1/status $1 retained
publish remote /martinshome/switch/1/status $1 retained
% $2 is blink flag
setvar $2=0
setvar $3=10
gpio_pinmode 5 input pullup % configure GPIO 5 as input
% local subscriptions once in 'init'
subscribe local /martinshome/switch/1/command
% 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
% Now the events, checked whenever something happens
% Here a remote re-publish, of any local topic starting with "/test/"
on topic local /test/#
do
publish remote $this_topic $this_data
% Now a check for local GPIOs
on gpio_interrupt 4 pullup
% Is there a remote command?
on topic remote /martinshome/switch/1/command
do
println "New state GPIO 4: " | $this_gpio
publish local /t/gpio4 $this_gpio
println "Received remote command: " | $this_data
% When timer 1 expires, do some stuff
% republish this locally - this does the action
publish local /martinshome/switch/1/command $this_data
% Is there a local command?
on topic local /martinshome/switch/1/command
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)
endif
if $this_data = "off" then
setvar $1 = 0
setvar $2 = 0
gpio_out 12 $1
gpio_out 13 not ($1)
endif
if $this_data = "toggle" then
setvar $1 = not ($1)
gpio_out 12 $1
gpio_out 13 not ($1)
endif
if $this_data = "blink" then
setvar $2 = 1
settimer 1 500
endif
publish local /martinshome/switch/1/status $1 retained
publish remote /martinshome/switch/1/status $1 retained
% The local pushbotton
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"
endif
% Blinking
on timer 1
do
% publish the current status of GPIO 5 as two byte binary val
if gpio_in(5)=0 then
publish local /t/gpio5 #0000
endif
if gpio_in(5)=1 then
publish local /t/gpio5 #0001
if $2 = 1 then
publish local /martinshome/switch/1/command "toggle"
settimer 1 500
endif
% Let the LED on GPIO 2 blink
gpio_out 2 $1
setvar $1 = not($1)
% Count timer 1 ticks in var $2
setvar $2=$2+1
% And each time if we have reached 10, print that to the console
if $2 = $3 then
println "We have reached "|$2| " at " |$timestamp
setvar $3=$2+10
endif
% Reload the timer
settimer 1 1000
% Here a local publication once each day at noon
on clock 12:00:00
% Switch on in the evening
on clock 19:30:00
do
publish local /t/2 "High Noon"
publish local /martinshome/switch/1/command "on"
% Switch off at night
on clock 01:00:00
do
publish local /martinshome/switch/1/command "off"
```
In general, scripts have the following BNF:

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -1,61 +0,0 @@
% 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
% Now the initialization, this is done once after booting
on init
do
println "MQTT Script 1.0 starting"
subscribe local /test/#
settimer 1 1000 % once per second
setvar $1=0
setvar $2=0
setvar $3=10
gpio_pinmode 5 input pullup % configure GPIO 5 as input
% Now the events, checked whenever something happens
% Here a remote re-publish, of any local topic starting with "/test/"
on topic local /test/#
do
publish remote $this_topic $this_data
% Now a check for local GPIOs
on gpio_interrupt 4 pullup
do
println "New state GPIO 4: " | $this_gpio
publish local /t/gpio4 $this_gpio
% When timer 1 expires, do some stuff
on timer 1
do
% publish the current status of GPIO 5 as two byte binary val
if gpio_in(5)=0 then
publish local /t/gpio5 #0000
endif
if gpio_in(5)=1 then
publish local /t/gpio5 #0001
endif
% Let the LED on GPIO 2 blink
gpio_out 2 $1
setvar $1 = not($1)
% Count timer 1 ticks in var $2
setvar $2=$2+1
% And each time if we have reached 10, print that to the console
if $2 = $3 then
println "We have reached "|$2| " at " |$timestamp
setvar $3=$2+10
endif
% Reload the timer
settimer 1 1000
% Here a local publication once each day at noon
on clock 12:00:00
do
publish local /t/2 "High Noon"

Wyświetl plik

@ -407,7 +407,7 @@ int ICACHE_FLASH_ATTR parse_statement(int next_token) {
}
}
lang_info("Interpreter loop: %d us\r\n", (system_get_time()-start));
lang_debug("Interpreter loop: %d us\r\n", (system_get_time()-start));
if (interpreter_status == INIT)
loop_time = system_get_time()-start;
else
@ -1075,16 +1075,8 @@ int ICACHE_FLASH_ATTR interpreter_topic_received(const char *topic, const char *
interpreter_status = (local) ? TOPIC_LOCAL : TOPIC_REMOTE;
interpreter_topic = (char *)topic;
interpreter_data = (char *)data;
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;
return parse_statement(0);
}

Wyświetl plik

@ -1,3 +1,6 @@
#ifndef _LANG_
#define _LANG_
#include "mqtt_server.h"
extern MQTT_Client mqttClient;
@ -32,3 +35,5 @@ void check_timestamps(uint8_t *curr_time);
void init_gpios();
void stop_gpios();
#endif /* _LANG_ */

65
user/pub_list.c 100644
Wyświetl plik

@ -0,0 +1,65 @@
#include "c_types.h"
#include "mem.h"
#include "osapi.h"
#include "user_config.h"
#include "lang.h"
#include "pub_list.h"
typedef struct _pub_entry {
char *topic;
char *data;
uint32_t data_len;
bool local;
struct _pub_entry *next;
} pub_entry;
static pub_entry *pub_list = NULL;
void ICACHE_FLASH_ATTR pub_insert(const char* topic, uint32_t topic_len, const char *data, uint32_t data_len, bool local)
{
pub_entry *pub = (pub_entry *)os_malloc(sizeof(pub_entry));
if (pub == NULL)
return;
pub->topic = (char*)os_malloc(topic_len+1);
if (pub->topic == NULL) {
os_free(pub);
return;
}
pub->data = (char*)os_malloc(data_len+1);
if (pub->data == NULL) {
os_free(pub->topic);
os_free(pub);
return;
}
os_memcpy(pub->topic, topic, topic_len);
pub->topic[topic_len] = '\0';
os_memcpy(pub->data, data, data_len);
pub->data_len = data_len;
pub->data[data_len] = '\0';
pub->local = local;
pub->next = pub_list;
pub_list = pub;
}
void ICACHE_FLASH_ATTR pub_process()
{
pub_entry **pre_last, *last;
while (pub_list != NULL) {
pre_last = &pub_list;
while ((*pre_last)->next != NULL)
pre_last = &((*pre_last)->next);
last = *pre_last;
*pre_last = NULL;
interpreter_topic_received(last->topic, last->data, last->data_len, last->local);
os_free(last->topic);
os_free(last->data);
os_free(last);
}
}

7
user/pub_list.h 100644
Wyświetl plik

@ -0,0 +1,7 @@
#ifndef _PUB_LIST_
#define _PUB_LIST_
void pub_insert(const char* topic, uint32_t topic_len, const char *data, uint32_t data_len, bool local);
void pub_process();
#endif /* _PUB_LIST_ */

101
user/script.sonoff 100644
Wyświetl plik

@ -0,0 +1,101 @@
% Config params, overwrite any previous settings from the commandline
config ap_ssid DerKluge
config ap_password Bonn2016
config ntp_server 1.de.pool.ntp.org
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)
publish local /martinshome/switch/1/status $1 retained
publish remote /martinshome/switch/1/status $1 retained
% $2 is blink flag
setvar $2=0
% local subscriptions once in 'init'
subscribe local /martinshome/switch/1/command
% 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
% Now the events, checked whenever something happens
% Is there a remote command?
on topic remote /martinshome/switch/1/command
do
println "Received remote command: " | $this_data
% republish this locally - this does the action
publish local /martinshome/switch/1/command $this_data
% Is there a local command?
on topic local /martinshome/switch/1/command
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)
endif
if $this_data = "off" then
setvar $1 = 0
setvar $2 = 0
gpio_out 12 $1
gpio_out 13 not ($1)
endif
if $this_data = "toggle" then
setvar $1 = not ($1)
gpio_out 12 $1
gpio_out 13 not ($1)
endif
if $this_data = "blink" then
setvar $2 = 1
settimer 1 500
endif
publish local /martinshome/switch/1/status $1 retained
publish remote /martinshome/switch/1/status $1 retained
% The local pushbotton
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"
endif
% Blinking
on timer 1
do
if $2 = 1 then
publish local /martinshome/switch/1/command "toggle"
settimer 1 500
endif
% Switch on in the evening
on clock 19:30:00
do
publish local /martinshome/switch/1/command "on"
% Switch off at night
on clock 01:00:00
do
publish local /martinshome/switch/1/command "off"

Wyświetl plik

@ -1,3 +1,6 @@
#ifndef _SYS_TIME_
#define _SYS_TIME_
#include "c_types.h"
// returns time until boot in us
@ -9,4 +12,4 @@ uint64_t ICACHE_FLASH_ATTR get_low_systime();
// initializes the timer
void init_long_systime();
#endif /* _SYS_TIME_ */

Wyświetl plik

@ -1,7 +1,7 @@
#ifndef _USER_CONFIG_
#define _USER_CONFIG_
typedef enum {SIG_DO_NOTHING=0, SIG_START_SERVER=1, SIG_UART0, SIG_SCRIPT_LOADED, SIG_CONSOLE_TX_RAW, SIG_CONSOLE_TX, SIG_CONSOLE_RX} USER_SIGNALS;
typedef enum {SIG_DO_NOTHING=0, SIG_START_SERVER=1, SIG_UART0, SIG_TOPIC_RECEIVED, SIG_SCRIPT_LOADED, SIG_CONSOLE_TX_RAW, SIG_CONSOLE_TX, SIG_CONSOLE_RX} USER_SIGNALS;
#define WIFI_SSID "ssid"
#define WIFI_PASSWORD "password"

Wyświetl plik

@ -24,6 +24,7 @@ uint64_t t_ntp_resync = 0;
#ifdef SCRIPTED
#include "lang.h"
#include "pub_list.h"
struct espconn *downloadCon;
struct espconn *scriptcon;
@ -34,7 +35,7 @@ bool timestamps_init;
/* System Task, for signals refer to user_config.h */
#define user_procTaskPrio 0
#define user_procTaskQueueLen 1
#define user_procTaskQueueLen 2
os_event_t user_procTaskQueue[user_procTaskQueueLen];
static void user_procTask(os_event_t * events);
@ -95,18 +96,11 @@ static void ICACHE_FLASH_ATTR mqttPublishedCb(uint32_t * args) {
static void ICACHE_FLASH_ATTR mqttDataCb(uint32_t * args, const char *topic,
uint32_t topic_len, const char *data, uint32_t data_len) {
MQTT_Client *client = (MQTT_Client *) args;
uint8_t buffer[256];
if (topic_len >= sizeof(buffer)) {
os_printf("Topic to long for republication");
return;
}
strncpy(buffer, topic, topic_len);
buffer[topic_len] = 0;
#ifdef SCRIPTED
interpreter_topic_received(buffer, (uint8_t *) data, data_len, false);
MQTT_Client *client = (MQTT_Client *) args;
pub_insert(topic, topic_len, data, data_len, false);
system_os_post(user_procTaskPrio, SIG_TOPIC_RECEIVED, 0);
#endif
}
#endif /* MQTT_CLIENT */
@ -318,7 +312,9 @@ bool ICACHE_FLASH_ATTR printf_retainedtopic(retained_entry * entry, void *user_d
void MQTT_local_DataCallback(uint32_t * args, const char *topic, uint32_t topic_len, const char *data, uint32_t length) {
// os_printf("Received: \"%s\" len: %d\r\n", topic, length);
#ifdef SCRIPTED
interpreter_topic_received(topic, data, length, true);
//interpreter_topic_received(topic, data, length, true);
pub_insert(topic, topic_len, data, length, true);
system_os_post(user_procTaskPrio, SIG_TOPIC_RECEIVED, 0);
#endif
}
@ -1004,6 +1000,12 @@ static void ICACHE_FLASH_ATTR user_procTask(os_event_t * events) {
// Anything else to do here, when the repeater has received its IP?
break;
#ifdef SCRIPTED
case SIG_TOPIC_RECEIVED:
{
pub_process();
}
break;
case SIG_SCRIPT_LOADED:
{
espconn_disconnect(downloadCon);