kopia lustrzana https://github.com/martin-ger/esp_mqtt
queuing of 'on topic' events
rodzic
aef3f73e03
commit
10700b7b94
2
Makefile
2
Makefile
|
@ -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
121
README.md
|
@ -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.
|
@ -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"
|
14
user/lang.c
14
user/lang.c
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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_ */
|
|
@ -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"
|
|
@ -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_ */
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
Ładowanie…
Reference in New Issue