From 1fc6ae24c6c385ccd97dad53d5be786450ef5ac0 Mon Sep 17 00:00:00 2001 From: Martin Ger Date: Fri, 21 Jul 2017 16:35:45 +0200 Subject: [PATCH] added scripting to the master branch --- Makefile | 4 +- README.md | 255 +++++-- easygpio/easygpio.c | 343 +++++++++ easygpio/easygpio.h | 114 +++ firmware/0x00000.bin | Bin 36672 -> 37904 bytes firmware/0x10000.bin | Bin 214468 -> 222516 bytes mqtt/mqtt.c | 1327 ++++++++++++++++----------------- mqtt/mqtt_msg.c | 712 +++++++++--------- mqtt/mqtt_retainedlist.c | 171 +++-- mqtt/mqtt_server.c | 1161 +++++++++++++++-------------- mqtt/mqtt_topiclist.c | 111 +-- mqtt/mqtt_topics.c | 395 +++++----- mqtt/proto.c | 209 +++--- mqtt/queue.c | 26 +- mqtt/ringbuf_mqtt.c | 53 +- mqtt/strtok_r.c | 80 +- mqtt/utils.c | 187 +++-- ntp/ntp.c | 213 +++--- ntp/ntp.h | 3 +- user/config_flash.c | 104 ++- user/config_flash.h | 1 + user/demo_script | 49 ++ user/lang.c | 800 ++++++++++++++++++++ user/lang.h | 29 + user/rfinit.c | 58 +- user/ringbuf.c | 166 ++--- user/sys_time.c | 28 +- user/user_config.h | 18 +- user/user_main.c | 1496 +++++++++++++++++++++----------------- user_basic/user_main.c | 3 +- 30 files changed, 4781 insertions(+), 3335 deletions(-) create mode 100644 easygpio/easygpio.c create mode 100644 easygpio/easygpio.h create mode 100644 user/demo_script create mode 100644 user/lang.c create mode 100644 user/lang.h diff --git a/Makefile b/Makefile index 54f1f6e..f32e5a4 100644 --- a/Makefile +++ b/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 +MODULES = driver user mqtt ntp easygpio #EXTRA_INCDIR = $(BUILD_AREA)/esp-open-sdk/esp-open-lwip/include include EXTRA_INCDIR = include @@ -43,7 +43,7 @@ LIB_MODULES = mqtt LIBS = c gcc hal pp phy net80211 lwip wpa main # compiler flags using during compilation of source files -CFLAGS = -Os -g -O2 -Wpointer-arith -Wundef -Werror -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH -DUSE_OPTIMIZE_PRINTF +CFLAGS = -Os -g -O2 -Wpointer-arith -Wundef -Werror -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH -DUSE_OPTIMIZE_PRINTF -Desp8266 #-DMQTT_DEBUG_ON # linker flags used to generate the main object file diff --git a/README.md b/README.md index ec296f3..2dbea83 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,11 @@ # esp_uMQTT_broker -A basic MQTT Broker on the ESP8266 +A basic MQTT Broker/Client with scripting support on the ESP8266 -Thanks to Tuan PM for sharing his MQTT client library https://github.com/tuanpmt/esp_mqtt as a basis with us. The modified code still contains the complete client functionality from the original esp_mqtt lib, but it has been extended by the basic broker service. - -The broker does support: -- MQTT protocoll versions v3.1 and v3.1.1 simultaniously -- a smaller number of clients (at least 8 have been tested, memory is the issue) -- retained messages -- LWT -- QoS level 0 -- a subset of MQTT (CONNECT, DISCONNECT, SUBSCRIBE, UNSUBSCRIBE, PUBLISH, PING) - -The broker does not yet support: -- username, password authentication -- QoS levels other than 0 -- many TCP(=MQTT) clients -- non-clear sessions -- TLS - -The complete functionality is included in the mqtt directory. The broker is started by simply including: - -```c -#include "mqtt_server.h" - -bool MQTT_server_start(uint16_t portno, uint16_t max_subscriptions, uint16_t max_retained_topics); -``` - -in the user_init() function. - -# Building and Flashing -The code can be used in any project that is compiled using the NONOS_SDK or the esp-open-sdk. Also the sample code in the user directory can be build using the standard SDKs after adapting the variables in the Makefile. - -If you don't need the full demo program, you can find a minimal demo in the directory "user_basic". Rename it to "user", adapt "user_config.h", and do the "make" to build a small demo that just starts an MQTT broker. - -Build the esp_uMQTT_broker firmware with "make". "make flash" flashes it onto an esp8266. - -If you want to use the precompiled binaries of the bigger demo (see below) from the firmware directory you can flash them on an ESP12 with - -```bash -esptool.py --port /dev/ttyUSB0 write_flash -fs 32m 0x00000 firmware/0x00000.bin 0x10000 firmware/0x10000.bin -``` +This program enables the ESP8266 to become the central node in a small distributed IoT system. It implements an MQTT Broker and a simple scripted rule engine with event/action statements that links together the MQTT sensors and actors. It can act as STA, as AP, or as both and it can connect to another MQTT broker (i.e. in the cloud). Here it can act as bridge and forward and rewrite topics in both directions. # Usage -In the user directory there is a demo program that serves as a stand-alone MQTT broker and bridge. The program starts with the following default configuration: +In the user directory there is the main program that serves as a stand-alone MQTT broker, client and bridge. The program starts with the following default configuration: + - ssid: ssid, password: password - ap_ssid: MyAP, ap_password: none, ap_on: 1, ap_open: 1 - network: 192.168.4.0/24 @@ -50,6 +13,7 @@ In the user directory there is a demo program that serves as a stand-alone MQTT This means it connects to the internet via AP ssid,password and offers an open AP with ap_ssid MyAP. This default can be changed in the file user_config.h. The default can be overwritten and persistenly saved to flash by using a console interface. This console is available either via the serial port at 115200 baud or via tcp port 7777 (e.g. "telnet 192.168.4.1 7777" from a connected STA). Use the following commands for an initial setup: + - set ssid your_home_router's_SSID - set password your_home_router's_password - set ap_ssid ESP's_ssid @@ -63,19 +27,19 @@ After reboot it will connect to your home router and itself is ready for station The console understands the following commands: Basic commands (enough to get it working in nearly all environments): + - help: prints a short help message - set [ssid|password] _value_: changes the settings for the uplink AP (WiFi config of your home-router) +- set ap_on [0|1]: selects, whether the soft-AP is disabled (ap_on=0) or enabled (ap_on=1, default) - set [ap_ssid|ap_password] _value_: changes the settings for the soft-AP of the ESP (for your stations) -- show [config|stats]: prints the current config or some status information and statistics -- show mqtt_broker: shows the current status of the uMQTT broker -- save [dhcp]: saves the current config parameters [+ the current DHCP leases] to flash +- 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: locks the current config, changes are not allowed -- unlock _password_: unlocks the config, requires password of the network AP +- 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 - quit: terminates a remote session -Advanced commands: -(Most of the set-commands are effective only after save and reset) +Advanced commands (most of the set-commands are effective only after save and reset): - set network _ip-addr_: sets the IP address of the internal network, network is always /24, router is always x.x.x.1 - set dns _dns-addr_: sets a static DNS address - set dns dhcp: configures use of the dynamic DNS address from DHCP, default @@ -84,17 +48,184 @@ Advanced commands: - set netmask _netmask_: sets a static netmask for the uplink network - set gw _gw-addr_: sets a static gateway address in the uplink network - scan: does a scan for APs -- set ap_on [0|1]: selects, whether the soft-AP is disabled (ap_on=0) or enabled (ap_on=1, default) - set ap_open [0|1]: selects, whether the soft-AP uses WPA2 security (ap_open=0, automatic, if an ap_password is set) or open (ap_open=1) - set speed [80|160]: sets the CPU clock frequency (default 80 Mhz) -- set ntp_server _IP_or_hostname_: sets the name or IP of an NTP server ("none" disables NTP, default) -- set ntp_interval _interval_: sets the NTP sync interval in seconds (default 60) -- set ntp_timezone _tz_: sets the timezone in hours offset +- set config_port _portno_: sets the port number of the console login (default is 7777, 0 disables remote console config) +- script [_portno_|delete]: opens port for upload of scripts or deletes the current script While the user interface looks similar to my esp_wifi_repeater at https://github.com/martin-ger/esp_wifi_repeater this does NO NAT routing. AP and STA network are stricly separated and there is no routing in between. The only possible connection via both networks is the uMQTT broker that listens on both interfaces. # MQTT client/bridging functionality -The broker comes with a "local" and a "remote" client, which means, the broker itself can publish and subscribe topics. The "local" client is a client to the own broker (without the need of an additional TCP connection). This feature is meant to provide the basis for a local rule engine that can react on MQTT events, e.g. to switch GPIOs or send other messages (MQTT, HTTP,...). You can use this at source level with the functions: +The broker comes with a "local" and a "remote" client, which means, the broker itself can publish and subscribe topics. The "local" client is a client to the own broker (without the need of an additional TCP connection). + +By default the "remote" MQTT client is disabled. It can be enabled by setting the config parameter "mqtt_host" to a hostname different from "none". To configure the "remote" MQTT client you can set the following parameters: + +- set mqtt_host _IP_or_hostname_: IP or hostname of the MQTT broker ("none" disables the MQTT client) +- set mqtt_user _username_: Username for authentication ("none" if no authentication is required at the broker) +- set mqtt_user _password_: Password for authentication +- set mqtt_id _clientId_: Id of the client at the broker (default: "ESPRouter_xxxxxx" derived from the MAC address) + +# 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: +``` +% 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 + +% Now the events, checked whenever something happens + +% Here a remote republish, of any local topic starting with "/test/" +on topic local /test/# +do + publish remote $this_topic $this_data + +% When timer 1 expires, do some stuff +on timer 1 +do + % publish a timestamp locally + publish local /t/time $timestamp + + % Let the LED on GPIO 2 blink + gpio_out 2 $1 + setvar $1 not $1 + + % Count occurences in var $2 + setvar $2 $2 add 1 + + % And if we have reached 100, print that to the console + if $2 gte 100 then + print "We have reached " + println $2 + setvar $2 0 + 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" +``` + +In general, scripts have the following BNF: + +``` + ::= on do | + config | + + + ::= init | timer | clock | topic (local|remote) + + ::= publish (local|remote) [retained] | + subscribe (local|remote) | + unsubscribe (local|remote) | + settimer | + setvar $ | + gpio_out | + if then endif | + print | println + + + ::= | not + + := eq | gt | gte | str_ge | str_gte | add | sub | mult | div + + := | | # | $ | $this_item | $this_data | $timestamp + + := "[any ASCII]*" | [any ASCII]* + + := [0-9]* + + := hh:mm:ss +``` + +Scripts with size up to 4KB are uploaded to the esp_uMQTT_broker using a network interface. Start the upload with "script " on the concole of the ESP, e.g.: +``` +CMD>script 2000 +Waiting for script upload on port 2000 +CMD> + +``` +Now the ESP listens on the given port for an incoming connection and stores anything it receives as new script. Upload a file using netcat, e.g.: +```bash +$ netcat 192.168.178.29 2000 < user/demo_script2 +``` +The ESP will store the file and immediatly checks the syntax of the script: +``` +CMD>script 2000 +Waiting for script upload on port 2000 +Script upload completed (451 Bytes) +Syntax okay +CMD> +``` + +You can examine the currently loaded script using the "show script" command. It only displays about 1KB of a script. If you need to see more, use "show script " with a higher starting line. Newly loaded scripts are stored persistently in flash and will be executed after next reset if they contain no syntax errors. "script delete" stops script execution and deleted a script from flash. + +# NTP Support +NTP time is supported and timestamps are only available if the sync with an NTP server is done. By default the NTP client is enabled and set to "1.pool.ntp.org". It can be changed by setting the config parameter "ntp_server" to a hostname or an IP address. An ntp_server of "none" will disable the NTP client. Also you can set the "ntp_timezone" to an offset from GMT in hours. The system time will be synced with the NTP server every "ntp_interval" seconds (default ). Here it uses NOT the full NTP calculation and clock drift compensation. Instead it will just set the local time to the latest received time. + +After NTP sync has been completed successfully once, the local time will be published every second under the topic "$SYS/broker/time" in the format "hh:mm:ss". You can also query the NTP time using the "time" command from the commandline. + +- set ntp_server _IP_or_hostname_: sets the name or IP of an NTP server (default "1.pool.ntp.org", "none" disables NTP) +- set ntp_interval _interval_: sets the NTP sync interval in seconds (default 300) +- set ntp_timezone _tz_: sets the timezone in hours offset (default 0) +- time: prints the current time as hh:mm:ss + +# Building and Flashing +The code can be used in any project that is compiled using the NONOS_SDK or the esp-open-sdk. Also the sample code in the user directory can be build using the standard SDKs after adapting the variables in the Makefile. + +Build the esp_uMQTT_broker firmware with "make". "make flash" flashes it onto an esp8266. + +If you want to use the precompiled binaries from the firmware directory you can flash them directly on an ESP8266, e.g. with + +```bash +$ esptool.py --port /dev/ttyUSB0 write_flash -fs 32m 0x00000 firmware/0x00000.bin 0x10000 firmware/0x10000.bin + +``` +# The MQTT broker library +Thanks to Tuan PM for sharing his MQTT client library https://github.com/tuanpmt/esp_mqtt as a basis with us. The modified code still contains the complete client functionality from the original esp_mqtt lib, but it has been extended by the basic broker service. + +The broker does support: +- MQTT protocoll versions v3.1 and v3.1.1 simultaniously +- a smaller number of clients (at least 8 have been tested, memory is the issue) +- retained messages +- LWT +- QoS level 0 +- a subset of MQTT (CONNECT, DISCONNECT, SUBSCRIBE, UNSUBSCRIBE, PUBLISH, PING) + +The broker does not yet support: +- username, password authentication +- QoS levels other than 0 +- many TCP(=MQTT) clients +- non-clear sessions +- TLS + +# Using the Source Code +The complete functionality is included in the mqtt directory and can be integrated into any NONOS SDK program. The broker is started by simply including: + +```c +#include "mqtt_server.h" + +bool MQTT_server_start(uint16_t portno, uint16_t max_subscriptions, uint16_t max_retained_topics); + +``` +in the user_init() function. Now it is ready for MQTT connections on all activated interfaces (STA and/or AP). You can find a minimal demo in the directory "user_basic". Rename it to "user", adapt "user_config.h", and do the "make" to build a small demo that just starts an MQTT broker. + +Your code can locally interact with the broker using the functions: ```c bool MQTT_local_publish(uint8_t* topic, uint8_t* data, uint16_t data_length, uint8_t qos, uint8_t retain); @@ -103,21 +234,5 @@ bool MQTT_local_unsubscribe(uint8_t* topic); void MQTT_local_onData(MqttDataCallback dataCb); ``` -By default the "remote" MQTT client is disabled. It can be enabled by setting the config parameter "mqtt_host" to a hostname different from "none". To configure the "remote" MQTT client you can set the following parameters: -- set mqtt_host _IP_or_hostname_: IP or hostname of the MQTT broker ("none" disables the MQTT client) -- set mqtt_user _username_: Username for authentication ("none" if no authentication is required at the broker) -- set mqtt_user _password_: Password for authentication -- set mqtt_id _clientId_: Id of the client at the broker (default: "ESPRouter_xxxxxx" derived from the MAC address) +With these functions you can publish and subscribe topics as a local client like you would with a remote MQTT broker. -You can test this with the commands: - -- publish [local|remote] [topic] [data]: this publishes a topic -- subscribe [local|remote] [topic]: subscribes to a topic, received topic will be printed to serial output -- unsubscribe [local|remote] [topic]: unsubscribes from a topic - -Currently the clients republish everything they receive (and they have subscribed) to the other client, thus it can act as something like an MQTT bridge. Up to now, the subscriptions are not persistently saved to config, so they have to be entered manually after each reboot - will work on this... - -# NTP Support -Remote NTP time servers are supported. By default the NTP client is disabled - and the is no immediate need for synced time. Nowever, it can be enabled by setting the config parameter "ntp_server" to a hostname or IP different from "none" ("1.pool.ntp.org" is a good choice). Also you can set the "ntp_timezone" to an offset from GMT. The system time will be synced with the NTP server every "ntp_interval" seconds. Here it uses NOT the full NTP calculation and clock drift compensation. Instead it will just set the local time to the latest received time. - -After NTP sync has been completed successful once, the local time will be published every second under the topic "$SYS/broker/time" in the format "hh:mm:ss". You can also query the NTP time with the "time" command from the commandline. diff --git a/easygpio/easygpio.c b/easygpio/easygpio.c new file mode 100644 index 0000000..0d386b1 --- /dev/null +++ b/easygpio/easygpio.c @@ -0,0 +1,343 @@ +/* +* easygpio.c +* +* Copyright (c) 2015, eadf (https://github.com/eadf) +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of Redis nor the names of its contributors may be used +* to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "easygpio.h" +#include "gpio.h" +#include "osapi.h" +#include "ets_sys.h" + +#define EASYGPIO_USE_GPIO_INPUT_GET + +static void ICACHE_FLASH_ATTR +gpio16_output_conf(void) { + WRITE_PERI_REG(PAD_XPD_DCDC_CONF, + (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbcUL) | 0x1UL); // mux configuration for XPD_DCDC to output rtc_gpio0 + + WRITE_PERI_REG(RTC_GPIO_CONF, + (READ_PERI_REG(RTC_GPIO_CONF) & 0xfffffffeUL) | 0x0UL); //mux configuration for out enable + + WRITE_PERI_REG(RTC_GPIO_ENABLE, + (READ_PERI_REG(RTC_GPIO_ENABLE) & 0xfffffffeUL) | 0x1UL); //out enable +} + +static void ICACHE_FLASH_ATTR +gpio16_input_conf(void) { + WRITE_PERI_REG(PAD_XPD_DCDC_CONF, + (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbcUL) | 0x1UL); // mux configuration for XPD_DCDC and rtc_gpio0 connection + + WRITE_PERI_REG(RTC_GPIO_CONF, + (READ_PERI_REG(RTC_GPIO_CONF) & 0xfffffffeUL) | 0x0UL); //mux configuration for out enable + + WRITE_PERI_REG(RTC_GPIO_ENABLE, + READ_PERI_REG(RTC_GPIO_ENABLE) & 0xfffffffeUL); //out disable +} + +/** + * Returns the number of active pins in the gpioMask. + */ +uint8_t ICACHE_FLASH_ATTR +easygpio_countBits(uint32_t gpioMask) { + + uint8_t i=0; + uint8_t numberOfPins=0; + for (i=0; i<32; i++){ + numberOfPins += (gpioMask & BIT(i))?1:0; + } + return numberOfPins; +} + +/** + * Returns the gpio name and func for a specific pin. + */ +bool ICACHE_FLASH_ATTR +easygpio_getGPIONameFunc(uint8_t gpio_pin, uint32_t *gpio_name, uint8_t *gpio_func) { + + if (gpio_pin == 6 || gpio_pin == 7 || gpio_pin == 8 || gpio_pin == 11 || gpio_pin >= 17) { + os_printf("easygpio_getGPIONameFunc Error: There is no GPIO%d, check your code\n", gpio_pin); + return false; + } + if (gpio_pin == 16) { + os_printf("easygpio_getGPIONameFunc Error: GPIO16 does not have gpio_name and gpio_func\n"); + return false; + } + switch ( gpio_pin ) { + case 0: + *gpio_func = FUNC_GPIO0; + *gpio_name = PERIPHS_IO_MUX_GPIO0_U; + return true; + case 1: + *gpio_func = FUNC_GPIO1; + *gpio_name = PERIPHS_IO_MUX_U0TXD_U; + return true; + case 2: + *gpio_func = FUNC_GPIO2; + *gpio_name = PERIPHS_IO_MUX_GPIO2_U; + return true; + case 3: + *gpio_func = FUNC_GPIO3; + *gpio_name = PERIPHS_IO_MUX_U0RXD_U; + return true; + case 4: + *gpio_func = FUNC_GPIO4; + *gpio_name = PERIPHS_IO_MUX_GPIO4_U; + return true; + case 5: + *gpio_func = FUNC_GPIO5; + *gpio_name = PERIPHS_IO_MUX_GPIO5_U; + return true; + case 9: + *gpio_func = FUNC_GPIO9; + *gpio_name = PERIPHS_IO_MUX_SD_DATA2_U; + return true; + case 10: + *gpio_func = FUNC_GPIO10; + *gpio_name = PERIPHS_IO_MUX_SD_DATA3_U; + return true; + case 12: + *gpio_func = FUNC_GPIO12; + *gpio_name = PERIPHS_IO_MUX_MTDI_U; + return true; + case 13: + *gpio_func = FUNC_GPIO13; + *gpio_name = PERIPHS_IO_MUX_MTCK_U; + return true; + case 14: + *gpio_func = FUNC_GPIO14; + *gpio_name = PERIPHS_IO_MUX_MTMS_U; + return true; + case 15: + *gpio_func = FUNC_GPIO15; + *gpio_name = PERIPHS_IO_MUX_MTDO_U; + return true; + default: + return false; + } + return true; +} + +/** + * Sets the pull up registers for a pin. + */ +static void ICACHE_FLASH_ATTR +easygpio_setupPullsByName(uint32_t gpio_name, EasyGPIO_PullStatus pullStatus) { + + if (EASYGPIO_PULLUP == pullStatus) { + PIN_PULLUP_EN(gpio_name); + } else { + PIN_PULLUP_DIS(gpio_name); + } +} + +/** + * Sets the pull registers for a pin. + */ +bool ICACHE_FLASH_ATTR +easygpio_pullMode(uint8_t gpio_pin, EasyGPIO_PullStatus pullStatus) { + uint32_t gpio_name; + uint8_t gpio_func; + + if (!easygpio_getGPIONameFunc(gpio_pin, &gpio_name, &gpio_func) ) { + return false; + } + + easygpio_setupPullsByName(gpio_name, pullStatus); + return true; +} + +/** + * Sets the 'gpio_pin' pin as a GPIO and sets the pull register for that pin. + * 'pullStatus' has no effect on output pins or GPIO16 + */ +bool ICACHE_FLASH_ATTR +easygpio_pinMode(uint8_t gpio_pin, EasyGPIO_PullStatus pullStatus, EasyGPIO_PinMode pinMode) { + uint32_t gpio_name; + uint8_t gpio_func; + + if (16==gpio_pin) { + // ignoring pull status on GPIO16 for now + if (EASYGPIO_OUTPUT == pinMode) { + gpio16_output_conf(); + } else { + gpio16_input_conf(); + } + return true; + } else if (!easygpio_getGPIONameFunc(gpio_pin, &gpio_name, &gpio_func) ) { + return false; + } + + PIN_FUNC_SELECT(gpio_name, gpio_func); + easygpio_setupPullsByName(gpio_name, pullStatus); + + if (EASYGPIO_OUTPUT != pinMode) { + GPIO_DIS_OUTPUT(GPIO_ID_PIN(gpio_pin)); + } else { + // must enable the pin or else the WRITE_PERI_REG won't work + gpio_output_set(0, 0, BIT(GPIO_ID_PIN(gpio_pin)),0); + } + return true; +} + +/** + * Sets the 'gpio_pin' pin as a GPIO and sets the interrupt to trigger on that pin. + * The 'interruptArg' is the function argument that will be sent to your interruptHandler + */ +bool ICACHE_FLASH_ATTR +easygpio_attachInterrupt(uint8_t gpio_pin, EasyGPIO_PullStatus pullStatus, void (*interruptHandler)(void *arg), void *interruptArg) { + uint32_t gpio_name; + uint8_t gpio_func; + + if (gpio_pin == 16) { + os_printf("easygpio_setupInterrupt Error: GPIO16 does not have interrupts\n"); + return false; + } + if (!easygpio_getGPIONameFunc(gpio_pin, &gpio_name, &gpio_func) ) { + return false; + } + + ETS_GPIO_INTR_ATTACH(interruptHandler, interruptArg); + ETS_GPIO_INTR_DISABLE(); + + PIN_FUNC_SELECT(gpio_name, gpio_func); + + easygpio_setupPullsByName(gpio_name, pullStatus); + + // disable output + GPIO_DIS_OUTPUT(gpio_pin); + + gpio_register_set(GPIO_PIN_ADDR(gpio_pin), GPIO_PIN_INT_TYPE_SET(GPIO_PIN_INTR_DISABLE) + | GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_DISABLE) + | GPIO_PIN_SOURCE_SET(GPIO_AS_PIN_SOURCE)); + + //clear gpio14 status + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(gpio_pin)); + ETS_GPIO_INTR_ENABLE(); + + return true; +} + +/** + * Detach the interrupt handler from the 'gpio_pin' pin. + */ +bool ICACHE_FLASH_ATTR +easygpio_detachInterrupt(uint8_t gpio_pin) { + + if (gpio_pin == 16) { + os_printf("easygpio_setupInterrupt Error: GPIO16 does not have interrupts\n"); + return false; + } + + // Don't know how to detach interrupt, yet. + // Quick and dirty fix - just disable the interrupt + gpio_pin_intr_state_set(GPIO_ID_PIN(gpio_pin), GPIO_PIN_INTR_DISABLE); + return true; +} + +/** + * Uniform way of setting GPIO output value. Handles GPIO 0-16. + * + * You can not rely on that this function will switch the gpio to an output like GPIO_OUTPUT_SET does. + * Use easygpio_outputEnable() to change an input gpio to output mode. + */ +void +easygpio_outputSet(uint8_t gpio_pin, uint8_t value) { + if (16==gpio_pin) { + WRITE_PERI_REG(RTC_GPIO_OUT, + (READ_PERI_REG(RTC_GPIO_OUT) & 0xfffffffeUL) | (0x1UL & value)); + } else { +#ifdef EASYGPIO_USE_GPIO_OUTPUT_SET + GPIO_OUTPUT_SET(GPIO_ID_PIN(gpio_pin), value); +#else + if (value&1){ + WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR, READ_PERI_REG(PERIPHS_GPIO_BASEADDR) | BIT(gpio_pin)); + } else { + WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR, READ_PERI_REG(PERIPHS_GPIO_BASEADDR) & ~BIT(gpio_pin)); + } +#endif + } +} + +/** + * Uniform way of getting GPIO input value. Handles GPIO 0-16. + * The pin must be initiated with easygpio_pinMode() so that the pin mux is setup as a gpio in the first place. + * If you know that you won't be using GPIO16 then you'd better off by just using GPIO_INPUT_GET(). + */ +uint8_t +easygpio_inputGet(uint8_t gpio_pin) { + if (16==gpio_pin) { + return (READ_PERI_REG(RTC_GPIO_IN_DATA) & 1UL); + } else { +#ifdef EASYGPIO_USE_GPIO_INPUT_GET + return GPIO_INPUT_GET(GPIO_ID_PIN(gpio_pin)); +#else + // this does *not* work, maybe GPIO_IN_ADDRESS is the wrong address + return ((GPIO_REG_READ(GPIO_IN_ADDRESS) > gpio_pin) & 1UL); +#endif + } +} + +/** + * Uniform way of turning an output GPIO pin into input mode. Handles GPIO 0-16. + * The pin must be initiated with easygpio_pinMode() so that the pin mux is setup as a gpio in the first place. + * This function does the same thing as GPIO_DIS_OUTPUT, but works on GPIO16 too. + */ +void easygpio_outputDisable(uint8_t gpio_pin) { + if (16==gpio_pin) { + WRITE_PERI_REG(RTC_GPIO_ENABLE, + READ_PERI_REG(RTC_GPIO_ENABLE) & 0xfffffffeUL); //out disable + } else { + GPIO_DIS_OUTPUT(GPIO_ID_PIN(gpio_pin)); + } +} + +/** + * Uniform way of turning an input GPIO pin into output mode. Handles GPIO 0-16. + * The pin must be initiated with easygpio_pinMode() so that the pin mux is setup as a gpio in the first place. + * + * This function: + * - should only be used to convert a input pin into an output pin. + * - is a little bit slower than easygpio_outputSet() so you should use that + * function to just change output value. + * - does the same thing as GPIO_OUTPUT_SET, but works on GPIO16 too. + */ +void easygpio_outputEnable(uint8_t gpio_pin, uint8_t value) { + if (16==gpio_pin) { + // write the value before flipping to output + // - so we don't flash previous value for a few ns. + WRITE_PERI_REG(RTC_GPIO_OUT, + (READ_PERI_REG(RTC_GPIO_OUT) & 0xfffffffeUL) | (0x1UL & value)); + + WRITE_PERI_REG(RTC_GPIO_ENABLE, + (READ_PERI_REG(RTC_GPIO_ENABLE) & 0xfffffffeUL) | 0x1UL); //out enable + + } else { + GPIO_OUTPUT_SET(GPIO_ID_PIN(gpio_pin), value); + } +} diff --git a/easygpio/easygpio.h b/easygpio/easygpio.h new file mode 100644 index 0000000..82c7f26 --- /dev/null +++ b/easygpio/easygpio.h @@ -0,0 +1,114 @@ +/* +* easygpio.h +* +* Copyright (c) 2015, eadf (https://github.com/eadf) +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of Redis nor the names of its contributors may be used +* to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef EASYGPIO_INCLUDE_EASYGPIO_EASYGPIO_H_ +#define EASYGPIO_INCLUDE_EASYGPIO_EASYGPIO_H_ + +#include "c_types.h" + +typedef enum { + EASYGPIO_INPUT=0, + EASYGPIO_OUTPUT=1 +} EasyGPIO_PinMode; + +typedef enum { + EASYGPIO_PULLUP=3, + EASYGPIO_NOPULL=4 +} EasyGPIO_PullStatus; + +/** + * Returns the gpio name and func for a specific pin. + */ +bool easygpio_getGPIONameFunc(uint8_t gpio_pin, uint32_t *gpio_name, uint8_t *gpio_func); + +/** + * Sets the 'gpio_pin' pin as a GPIO and sets the interrupt to trigger on that pin. + * The 'interruptArg' is the function argument that will be sent to your interruptHandler + * (this way you can several interrupts with one interruptHandler) + */ +bool easygpio_attachInterrupt(uint8_t gpio_pin, EasyGPIO_PullStatus pullStatus, void (*interruptHandler)(void *arg), void *interruptArg); + +/** + * Deatach the interrupt handler from the 'gpio_pin' pin. + */ +bool easygpio_detachInterrupt(uint8_t gpio_pin); + +/** + * Returns the number of active pins in the gpioMask. + */ +uint8_t easygpio_countBits(uint32_t gpioMask); + +/** + * Sets the 'gpio_pin' pin as a GPIO and enables/disables the pull-up on that pin. + * 'pullStatus' has no effect on output pins or GPIO16 + */ +bool easygpio_pinMode(uint8_t gpio_pin, EasyGPIO_PullStatus pullStatus, EasyGPIO_PinMode pinMode); + +/** + * Enable or disable the internal pull up for a pin. + */ +bool easygpio_pullMode(uint8_t gpio_pin, EasyGPIO_PullStatus pullStatus); + +/** + * Uniform way of getting GPIO input value. Handles GPIO 0-16. + * The pin must be initiated with easygpio_pinMode() so that the pin mux is setup as a gpio in the first place. + * If you know that you won't be using GPIO16 then you'd better off by just using GPIO_INPUT_GET(). + */ +uint8_t easygpio_inputGet(uint8_t gpio_pin); + +/** + * Uniform way of setting GPIO output value. Handles GPIO 0-16. + * + * You can not rely on that this function will switch the gpio to an output like GPIO_OUTPUT_SET does. + * Use easygpio_outputEnable() to change an input gpio to output mode. + */ +void easygpio_outputSet(uint8_t gpio_pin, uint8_t value); + +/** + * Uniform way of turning an output GPIO pin into input mode. Handles GPIO 0-16. + * The pin must be initiated with easygpio_pinMode() so that the pin mux is setup as a gpio in the first place. + * This function does the same thing as GPIO_DIS_OUTPUT, but works on GPIO16 too. + */ +void easygpio_outputDisable(uint8_t gpio_pin); + +/** + * Uniform way of turning an input GPIO pin into output mode. Handles GPIO 0-16. + * The pin must be initiated with easygpio_pinMode() so that the pin mux is setup as a gpio in the first place. + * + * This function: + * - should only be used to convert a input pin into an output pin. + * - is a little bit slower than easygpio_outputSet() so you should use that + * function to just change output value. + * - does the same thing as GPIO_OUTPUT_SET, but works on GPIO16 too. + */ +void easygpio_outputEnable(uint8_t gpio_pin, uint8_t value); + +#endif /* EASYGPIO_INCLUDE_EASYGPIO_EASYGPIO_H_ */ diff --git a/firmware/0x00000.bin b/firmware/0x00000.bin index d34ea9a9adb7c76440d54a9c70f801bdbc737bdc..3d86efa34f21e832ea34620ba5c2634622c5c0d8 100644 GIT binary patch delta 6998 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 delta 5637 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 z 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 veBeF7;`nzCjT$)OW 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 delta 42865 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

kQA(~BCj<=uE0e$UKxtHFsr_i7Kl~Vn* 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 diff --git a/mqtt/mqtt.c b/mqtt/mqtt.c index 72e9a65..44ab360 100644 --- a/mqtt/mqtt.c +++ b/mqtt/mqtt.c @@ -63,99 +63,89 @@ os_event_t mqtt_procTaskQueue[MQTT_TASK_QUEUE_SIZE]; LOCAL uint8_t zero_len_id[2] = { 0, 0 }; #endif -LOCAL void ICACHE_FLASH_ATTR -mqtt_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) -{ - struct espconn *pConn = (struct espconn *)arg; - MQTT_Client* client = (MQTT_Client *)pConn->reverse; +LOCAL void ICACHE_FLASH_ATTR mqtt_dns_found(const char *name, ip_addr_t * ipaddr, void *arg) { + struct espconn *pConn = (struct espconn *)arg; + MQTT_Client *client = (MQTT_Client *) pConn->reverse; + if (ipaddr == NULL) { + MQTT_INFO("DNS: Found, but got no ip, try to reconnect\r\n"); + client->connState = TCP_RECONNECT_REQ; + return; + } - if (ipaddr == NULL) - { - MQTT_INFO("DNS: Found, but got no ip, try to reconnect\r\n"); - client->connState = TCP_RECONNECT_REQ; - return; - } + MQTT_INFO("DNS: found ip %d.%d.%d.%d\n", + *((uint8 *) & ipaddr->addr), + *((uint8 *) & ipaddr->addr + 1), *((uint8 *) & ipaddr->addr + 2), *((uint8 *) & ipaddr->addr + 3)); - MQTT_INFO("DNS: found ip %d.%d.%d.%d\n", - *((uint8 *) &ipaddr->addr), - *((uint8 *) &ipaddr->addr + 1), - *((uint8 *) &ipaddr->addr + 2), - *((uint8 *) &ipaddr->addr + 3)); + if (client->ip.addr == 0 && ipaddr->addr != 0) { + os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); + if (client->security) { +#ifdef MQTT_SSL_ENABLE + espconn_secure_set_size(ESPCONN_CLIENT, MQTT_SSL_SIZE); + espconn_secure_connect(client->pCon); +#else + MQTT_INFO("TCP: Do not support SSL\r\n"); +#endif + } else { + espconn_connect(client->pCon); + } - if (client->ip.addr == 0 && ipaddr->addr != 0) - { - os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); + client->connState = TCP_CONNECTING; + MQTT_INFO("TCP: connecting...\r\n"); + } + + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); +} + +LOCAL void ICACHE_FLASH_ATTR deliver_publish(MQTT_Client * client, uint8_t * message, int length) { + mqtt_event_data_t event_data; + + event_data.topic_length = length; + event_data.topic = mqtt_get_publish_topic(message, &event_data.topic_length); + event_data.data_length = length; + event_data.data = mqtt_get_publish_data(message, &event_data.data_length); + + if (client->dataCb) + client->dataCb((uint32_t *) client, event_data.topic, event_data.topic_length, event_data.data, + event_data.data_length); + +} + +void ICACHE_FLASH_ATTR mqtt_send_keepalive(MQTT_Client * client) { + MQTT_INFO("\r\nMQTT: Send keepalive packet to %s:%d!\r\n", client->host, client->port); + client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); + client->mqtt_state.pending_msg_type = MQTT_MSG_TYPE_PINGREQ; + client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); + client->mqtt_state.pending_msg_id = + mqtt_get_id(client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); + + client->sendTimeout = MQTT_SEND_TIMOUT; + MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, + client->mqtt_state.pending_msg_id); + err_t result = ESPCONN_OK; if (client->security) { #ifdef MQTT_SSL_ENABLE - espconn_secure_set_size(ESPCONN_CLIENT, MQTT_SSL_SIZE); - espconn_secure_connect(client->pCon); + result = + espconn_secure_send(client->pCon, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length); #else - MQTT_INFO("TCP: Do not support SSL\r\n"); + MQTT_INFO("TCP: Do not support SSL\r\n"); #endif - } - else { - espconn_connect(client->pCon); + } else { + result = + espconn_send(client->pCon, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length); } - client->connState = TCP_CONNECTING; - MQTT_INFO("TCP: connecting...\r\n"); - } - - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); -} - - - -LOCAL void ICACHE_FLASH_ATTR -deliver_publish(MQTT_Client* client, uint8_t* message, int length) -{ - mqtt_event_data_t event_data; - - event_data.topic_length = length; - event_data.topic = mqtt_get_publish_topic(message, &event_data.topic_length); - event_data.data_length = length; - event_data.data = mqtt_get_publish_data(message, &event_data.data_length); - - if (client->dataCb) - client->dataCb((uint32_t*)client, event_data.topic, event_data.topic_length, event_data.data, event_data.data_length); - -} - -void ICACHE_FLASH_ATTR -mqtt_send_keepalive(MQTT_Client *client) -{ - MQTT_INFO("\r\nMQTT: Send keepalive packet to %s:%d!\r\n", client->host, client->port); - client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); - client->mqtt_state.pending_msg_type = MQTT_MSG_TYPE_PINGREQ; - client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); - client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); - - - client->sendTimeout = MQTT_SEND_TIMOUT; - MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, client->mqtt_state.pending_msg_id); - err_t result = ESPCONN_OK; - if (client->security) { -#ifdef MQTT_SSL_ENABLE - result = espconn_secure_send(client->pCon, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } - else { - result = espconn_send(client->pCon, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); - } - - client->mqtt_state.outbound_message = NULL; - if (ESPCONN_OK == result) { - client->keepAliveTick = 0; - client->connState = MQTT_DATA; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); - } - else { - client->connState = TCP_RECONNECT_DISCONNECTING; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); - } + client->mqtt_state.outbound_message = NULL; + if (ESPCONN_OK == result) { + client->keepAliveTick = 0; + client->connState = MQTT_DATA; + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); + } else { + client->connState = TCP_RECONNECT_DISCONNECTING; + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); + } } /** @@ -163,23 +153,21 @@ mqtt_send_keepalive(MQTT_Client *client) * @param mqttClient: The mqtt client which contain TCP client * @retval None */ -void ICACHE_FLASH_ATTR -mqtt_tcpclient_delete(MQTT_Client *mqttClient) -{ - if (mqttClient->pCon != NULL) { - MQTT_INFO("TCP: Free memory\r\n"); - // Force abort connections - espconn_abort(mqttClient->pCon); - // Delete connections - espconn_delete(mqttClient->pCon); +void ICACHE_FLASH_ATTR mqtt_tcpclient_delete(MQTT_Client * mqttClient) { + if (mqttClient->pCon != NULL) { + MQTT_INFO("TCP: Free memory\r\n"); + // Force abort connections + espconn_abort(mqttClient->pCon); + // Delete connections + espconn_delete(mqttClient->pCon); - if (mqttClient->pCon->proto.tcp) { - os_free(mqttClient->pCon->proto.tcp); - mqttClient->pCon->proto.tcp = NULL; + if (mqttClient->pCon->proto.tcp) { + os_free(mqttClient->pCon->proto.tcp); + mqttClient->pCon->proto.tcp = NULL; + } + os_free(mqttClient->pCon); + mqttClient->pCon = NULL; } - os_free(mqttClient->pCon); - mqttClient->pCon = NULL; - } } /** @@ -187,98 +175,93 @@ mqtt_tcpclient_delete(MQTT_Client *mqttClient) * @param mqttClient: The mqtt client * @retval None */ -void ICACHE_FLASH_ATTR -mqtt_client_delete(MQTT_Client *mqttClient) -{ - if (mqttClient == NULL) - return; +void ICACHE_FLASH_ATTR mqtt_client_delete(MQTT_Client * mqttClient) { + if (mqttClient == NULL) + return; - if (mqttClient->pCon != NULL) { - mqtt_tcpclient_delete(mqttClient); - } - - if (mqttClient->host != NULL) { - os_free(mqttClient->host); - mqttClient->host = NULL; - } - - if (mqttClient->user_data != NULL) { - os_free(mqttClient->user_data); - mqttClient->user_data = NULL; - } - - if (mqttClient->mqtt_state.in_buffer != NULL) { - os_free(mqttClient->mqtt_state.in_buffer); - mqttClient->mqtt_state.in_buffer = NULL; - } - - if (mqttClient->mqtt_state.out_buffer != NULL) { - os_free(mqttClient->mqtt_state.out_buffer); - mqttClient->mqtt_state.out_buffer = NULL; - } - - if (mqttClient->mqtt_state.outbound_message != NULL) { - if (mqttClient->mqtt_state.outbound_message->data != NULL) - { - os_free(mqttClient->mqtt_state.outbound_message->data); - mqttClient->mqtt_state.outbound_message->data = NULL; + if (mqttClient->pCon != NULL) { + mqtt_tcpclient_delete(mqttClient); } - } - if (mqttClient->mqtt_state.mqtt_connection.buffer != NULL) { - // Already freed but not NULL - mqttClient->mqtt_state.mqtt_connection.buffer = NULL; - } + if (mqttClient->host != NULL) { + os_free(mqttClient->host); + mqttClient->host = NULL; + } - if (mqttClient->connect_info.client_id != NULL) { + if (mqttClient->user_data != NULL) { + os_free(mqttClient->user_data); + mqttClient->user_data = NULL; + } + + if (mqttClient->mqtt_state.in_buffer != NULL) { + os_free(mqttClient->mqtt_state.in_buffer); + mqttClient->mqtt_state.in_buffer = NULL; + } + + if (mqttClient->mqtt_state.out_buffer != NULL) { + os_free(mqttClient->mqtt_state.out_buffer); + mqttClient->mqtt_state.out_buffer = NULL; + } + + if (mqttClient->mqtt_state.outbound_message != NULL) { + if (mqttClient->mqtt_state.outbound_message->data != NULL) { + os_free(mqttClient->mqtt_state.outbound_message->data); + mqttClient->mqtt_state.outbound_message->data = NULL; + } + } + + if (mqttClient->mqtt_state.mqtt_connection.buffer != NULL) { + // Already freed but not NULL + mqttClient->mqtt_state.mqtt_connection.buffer = NULL; + } + + if (mqttClient->connect_info.client_id != NULL) { #ifdef PROTOCOL_NAMEv311 - /* Don't attempt to free if it's the zero_len array */ - if ( ((uint8_t*)mqttClient->connect_info.client_id) != zero_len_id ) - os_free(mqttClient->connect_info.client_id); + /* Don't attempt to free if it's the zero_len array */ + if (((uint8_t *) mqttClient->connect_info.client_id) != zero_len_id) + os_free(mqttClient->connect_info.client_id); #else - os_free(mqttClient->connect_info.client_id); + os_free(mqttClient->connect_info.client_id); #endif - mqttClient->connect_info.client_id = NULL; - } + mqttClient->connect_info.client_id = NULL; + } - if (mqttClient->connect_info.username != NULL) { - os_free(mqttClient->connect_info.username); - mqttClient->connect_info.username = NULL; - } + if (mqttClient->connect_info.username != NULL) { + os_free(mqttClient->connect_info.username); + mqttClient->connect_info.username = NULL; + } - if (mqttClient->connect_info.password != NULL) { - os_free(mqttClient->connect_info.password); - mqttClient->connect_info.password = NULL; - } + if (mqttClient->connect_info.password != NULL) { + os_free(mqttClient->connect_info.password); + mqttClient->connect_info.password = NULL; + } - if (mqttClient->connect_info.will_topic != NULL) { - os_free(mqttClient->connect_info.will_topic); - mqttClient->connect_info.will_topic = NULL; - } + if (mqttClient->connect_info.will_topic != NULL) { + os_free(mqttClient->connect_info.will_topic); + mqttClient->connect_info.will_topic = NULL; + } - if (mqttClient->connect_info.will_data != NULL) { - os_free(mqttClient->connect_info.will_data); - mqttClient->connect_info.will_data = NULL; - } + if (mqttClient->connect_info.will_data != NULL) { + os_free(mqttClient->connect_info.will_data); + mqttClient->connect_info.will_data = NULL; + } - if (mqttClient->msgQueue.buf != NULL) { - os_free(mqttClient->msgQueue.buf); - mqttClient->msgQueue.buf = NULL; - } + if (mqttClient->msgQueue.buf != NULL) { + os_free(mqttClient->msgQueue.buf); + mqttClient->msgQueue.buf = NULL; + } + // Initialize state + mqttClient->connState = WIFI_INIT; + // Clear callback functions to avoid abnormal callback + mqttClient->connectedCb = NULL; + mqttClient->disconnectedCb = NULL; + mqttClient->publishedCb = NULL; + mqttClient->timeoutCb = NULL; + mqttClient->dataCb = NULL; - // Initialize state - mqttClient->connState = WIFI_INIT; - // Clear callback functions to avoid abnormal callback - mqttClient->connectedCb = NULL; - mqttClient->disconnectedCb = NULL; - mqttClient->publishedCb = NULL; - mqttClient->timeoutCb = NULL; - mqttClient->dataCb = NULL; - - MQTT_INFO("MQTT: client already deleted\r\n"); + MQTT_INFO("MQTT: client already deleted\r\n"); } - /** * @brief Client received callback function. * @param arg: contain the ip link information @@ -286,163 +269,168 @@ mqtt_client_delete(MQTT_Client *mqttClient) * @param len: the lenght of received data * @retval None */ -void ICACHE_FLASH_ATTR -mqtt_tcpclient_recv(void *arg, char *pdata, unsigned short len) -{ - uint8_t msg_type; - uint8_t msg_qos; - uint16_t msg_id; - uint8_t msg_conn_ret; +void ICACHE_FLASH_ATTR mqtt_tcpclient_recv(void *arg, char *pdata, unsigned short len) { + uint8_t msg_type; + uint8_t msg_qos; + uint16_t msg_id; + uint8_t msg_conn_ret; - struct espconn *pCon = (struct espconn*)arg; - MQTT_Client *client = (MQTT_Client *)pCon->reverse; + struct espconn *pCon = (struct espconn *)arg; + MQTT_Client *client = (MQTT_Client *) pCon->reverse; - client->keepAliveTick = 0; -READPACKET: - MQTT_INFO("TCP: data received %d bytes\r\n", len); - // MQTT_INFO("STATE: %d\r\n", client->connState); - if (len < MQTT_BUF_SIZE && len > 0) { - os_memcpy(client->mqtt_state.in_buffer, pdata, len); + client->keepAliveTick = 0; + READPACKET: + MQTT_INFO("TCP: data received %d bytes\r\n", len); + // MQTT_INFO("STATE: %d\r\n", client->connState); + if (len < MQTT_BUF_SIZE && len > 0) { + os_memcpy(client->mqtt_state.in_buffer, pdata, len); - msg_type = mqtt_get_type(client->mqtt_state.in_buffer); - msg_qos = mqtt_get_qos(client->mqtt_state.in_buffer); - msg_id = mqtt_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); - switch (client->connState) { - case MQTT_CONNECT_SENDING: - if (msg_type == MQTT_MSG_TYPE_CONNACK) { - if (client->mqtt_state.pending_msg_type != MQTT_MSG_TYPE_CONNECT) { - MQTT_INFO("MQTT: Invalid packet\r\n"); - if (client->security) { + msg_type = mqtt_get_type(client->mqtt_state.in_buffer); + msg_qos = mqtt_get_qos(client->mqtt_state.in_buffer); + msg_id = mqtt_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); + switch (client->connState) { + case MQTT_CONNECT_SENDING: + if (msg_type == MQTT_MSG_TYPE_CONNACK) { + if (client->mqtt_state.pending_msg_type != MQTT_MSG_TYPE_CONNECT) { + MQTT_INFO("MQTT: Invalid packet\r\n"); + if (client->security) { #ifdef MQTT_SSL_ENABLE - espconn_secure_disconnect(client->pCon); + espconn_secure_disconnect(client->pCon); #else - MQTT_INFO("TCP: Do not support SSL\r\n"); + MQTT_INFO("TCP: Do not support SSL\r\n"); #endif - } - else { - espconn_disconnect(client->pCon); - } - } else { - msg_conn_ret = mqtt_get_connect_return_code(client->mqtt_state.in_buffer); - switch (msg_conn_ret) { - case CONNECTION_ACCEPTED: - MQTT_INFO("MQTT: Connected to %s:%d\r\n", client->host, client->port); - client->connState = MQTT_DATA; - if (client->connectedCb) - client->connectedCb((uint32_t*)client); - break; - case CONNECTION_REFUSE_PROTOCOL: - case CONNECTION_REFUSE_SERVER_UNAVAILABLE: - case CONNECTION_REFUSE_BAD_USERNAME: - case CONNECTION_REFUSE_NOT_AUTHORIZED: - MQTT_INFO("MQTT: Connection refuse, reason code: %d\r\n", msg_conn_ret); - default: - if (client->security) { + } else { + espconn_disconnect(client->pCon); + } + } else { + msg_conn_ret = mqtt_get_connect_return_code(client->mqtt_state.in_buffer); + switch (msg_conn_ret) { + case CONNECTION_ACCEPTED: + MQTT_INFO("MQTT: Connected to %s:%d\r\n", client->host, client->port); + client->connState = MQTT_DATA; + if (client->connectedCb) + client->connectedCb((uint32_t *) client); + break; + case CONNECTION_REFUSE_PROTOCOL: + case CONNECTION_REFUSE_SERVER_UNAVAILABLE: + case CONNECTION_REFUSE_BAD_USERNAME: + case CONNECTION_REFUSE_NOT_AUTHORIZED: + MQTT_INFO("MQTT: Connection refuse, reason code: %d\r\n", msg_conn_ret); + default: + if (client->security) { #ifdef MQTT_SSL_ENABLE - espconn_secure_disconnect(client->pCon); + espconn_secure_disconnect(client->pCon); #else - MQTT_INFO("TCP: Do not support SSL\r\n"); + MQTT_INFO("TCP: Do not support SSL\r\n"); #endif - } - else { - espconn_disconnect(client->pCon); - } + } else { + espconn_disconnect(client->pCon); + } - } + } - } + } - } - break; - case MQTT_DATA: - case MQTT_KEEPALIVE_SEND: - client->mqtt_state.message_length_read = len; - client->mqtt_state.message_length = mqtt_get_total_length(client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); + } + break; + case MQTT_DATA: + case MQTT_KEEPALIVE_SEND: + client->mqtt_state.message_length_read = len; + client->mqtt_state.message_length = + mqtt_get_total_length(client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); + switch (msg_type) { - switch (msg_type) - { + case MQTT_MSG_TYPE_SUBACK: + if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_SUBSCRIBE + && client->mqtt_state.pending_msg_id == msg_id) + MQTT_INFO("MQTT: Subscribe successful\r\n"); + break; + case MQTT_MSG_TYPE_UNSUBACK: + if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE + && client->mqtt_state.pending_msg_id == msg_id) + MQTT_INFO("MQTT: UnSubscribe successful\r\n"); + break; + case MQTT_MSG_TYPE_PUBLISH: + if (msg_qos == 1) + client->mqtt_state.outbound_message = mqtt_msg_puback(&client->mqtt_state.mqtt_connection, msg_id); + else if (msg_qos == 2) + client->mqtt_state.outbound_message = mqtt_msg_pubrec(&client->mqtt_state.mqtt_connection, msg_id); + if (msg_qos == 1 || msg_qos == 2) { + MQTT_INFO("MQTT: Queue response QoS: %d\r\n", msg_qos); + if (QUEUE_Puts + (&client->msgQueue, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length) == -1) { + MQTT_INFO("MQTT: Queue full\r\n"); + } + } - case MQTT_MSG_TYPE_SUBACK: - if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_SUBSCRIBE && client->mqtt_state.pending_msg_id == msg_id) - MQTT_INFO("MQTT: Subscribe successful\r\n"); - break; - case MQTT_MSG_TYPE_UNSUBACK: - if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE && client->mqtt_state.pending_msg_id == msg_id) - MQTT_INFO("MQTT: UnSubscribe successful\r\n"); - break; - case MQTT_MSG_TYPE_PUBLISH: - if (msg_qos == 1) - client->mqtt_state.outbound_message = mqtt_msg_puback(&client->mqtt_state.mqtt_connection, msg_id); - else if (msg_qos == 2) - client->mqtt_state.outbound_message = mqtt_msg_pubrec(&client->mqtt_state.mqtt_connection, msg_id); - if (msg_qos == 1 || msg_qos == 2) { - MQTT_INFO("MQTT: Queue response QoS: %d\r\n", msg_qos); - if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - } - } + deliver_publish(client, client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); + break; + case MQTT_MSG_TYPE_PUBACK: + if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH + && client->mqtt_state.pending_msg_id == msg_id) { + MQTT_INFO("MQTT: received MQTT_MSG_TYPE_PUBACK, finish QoS1 publish\r\n"); + } - deliver_publish(client, client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); - break; - case MQTT_MSG_TYPE_PUBACK: - if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && client->mqtt_state.pending_msg_id == msg_id) { - MQTT_INFO("MQTT: received MQTT_MSG_TYPE_PUBACK, finish QoS1 publish\r\n"); - } + break; + case MQTT_MSG_TYPE_PUBREC: + client->mqtt_state.outbound_message = mqtt_msg_pubrel(&client->mqtt_state.mqtt_connection, msg_id); + if (QUEUE_Puts + (&client->msgQueue, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length) == -1) { + MQTT_INFO("MQTT: Queue full\r\n"); + } + break; + case MQTT_MSG_TYPE_PUBREL: + client->mqtt_state.outbound_message = mqtt_msg_pubcomp(&client->mqtt_state.mqtt_connection, msg_id); + if (QUEUE_Puts + (&client->msgQueue, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length) == -1) { + MQTT_INFO("MQTT: Queue full\r\n"); + } + break; + case MQTT_MSG_TYPE_PUBCOMP: + if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH + && client->mqtt_state.pending_msg_id == msg_id) { + MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_PUBCOMP, finish QoS2 publish\r\n"); + } + break; + case MQTT_MSG_TYPE_PINGREQ: + client->mqtt_state.outbound_message = mqtt_msg_pingresp(&client->mqtt_state.mqtt_connection); + if (QUEUE_Puts + (&client->msgQueue, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length) == -1) { + MQTT_INFO("MQTT: Queue full\r\n"); + } + break; + case MQTT_MSG_TYPE_PINGRESP: + // Ignore + break; + } + // NOTE: this is done down here and not in the switch case above + // because the PSOCK_READBUF_LEN() won't work inside a switch + // statement due to the way protothreads resume. + if (msg_type == MQTT_MSG_TYPE_PUBLISH) { + len = client->mqtt_state.message_length_read; - break; - case MQTT_MSG_TYPE_PUBREC: - client->mqtt_state.outbound_message = mqtt_msg_pubrel(&client->mqtt_state.mqtt_connection, msg_id); - if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - } - break; - case MQTT_MSG_TYPE_PUBREL: - client->mqtt_state.outbound_message = mqtt_msg_pubcomp(&client->mqtt_state.mqtt_connection, msg_id); - if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - } - break; - case MQTT_MSG_TYPE_PUBCOMP: - if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && client->mqtt_state.pending_msg_id == msg_id) { - MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_PUBCOMP, finish QoS2 publish\r\n"); - } - break; - case MQTT_MSG_TYPE_PINGREQ: - client->mqtt_state.outbound_message = mqtt_msg_pingresp(&client->mqtt_state.mqtt_connection); - if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - } - break; - case MQTT_MSG_TYPE_PINGRESP: - // Ignore - break; - } - // NOTE: this is done down here and not in the switch case above - // because the PSOCK_READBUF_LEN() won't work inside a switch - // statement due to the way protothreads resume. - if (msg_type == MQTT_MSG_TYPE_PUBLISH) - { - len = client->mqtt_state.message_length_read; + if (client->mqtt_state.message_length < client->mqtt_state.message_length_read) { + //client->connState = MQTT_PUBLISH_RECV; + //Not Implement yet + len -= client->mqtt_state.message_length; + pdata += client->mqtt_state.message_length; - if (client->mqtt_state.message_length < client->mqtt_state.message_length_read) - { - //client->connState = MQTT_PUBLISH_RECV; - //Not Implement yet - len -= client->mqtt_state.message_length; - pdata += client->mqtt_state.message_length; + MQTT_INFO("Get another published message\r\n"); + goto READPACKET; + } - MQTT_INFO("Get another published message\r\n"); - goto READPACKET; - } - - } - break; + } + break; + } + } else { + MQTT_INFO("ERROR: Message too long\r\n"); } - } else { - MQTT_INFO("ERROR: Message too long\r\n"); - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); } /** @@ -450,109 +438,102 @@ READPACKET: * @param arg: contain the ip link information * @retval None */ -void ICACHE_FLASH_ATTR -mqtt_tcpclient_sent_cb(void *arg) -{ - struct espconn *pCon = (struct espconn *)arg; - MQTT_Client* client = (MQTT_Client *)pCon->reverse; - MQTT_INFO("TCP: Sent\r\n"); - client->sendTimeout = 0; - client->keepAliveTick = 0; +void ICACHE_FLASH_ATTR mqtt_tcpclient_sent_cb(void *arg) { + struct espconn *pCon = (struct espconn *)arg; + MQTT_Client *client = (MQTT_Client *) pCon->reverse; + MQTT_INFO("TCP: Sent\r\n"); + client->sendTimeout = 0; + client->keepAliveTick = 0; - if ((client->connState == MQTT_DATA || client->connState == MQTT_KEEPALIVE_SEND) - && client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH) { - if (client->publishedCb) - client->publishedCb((uint32_t*)client); - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); -} - -void ICACHE_FLASH_ATTR mqtt_timer(void *arg) -{ - MQTT_Client* client = (MQTT_Client*)arg; - if (client->connState == MQTT_DATA) { - client->keepAliveTick ++; - if (client->keepAliveTick > (client->mqtt_state.connect_info->keepalive / 2)) { - client->connState = MQTT_KEEPALIVE_SEND; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); + if ((client->connState == MQTT_DATA || client->connState == MQTT_KEEPALIVE_SEND) + && client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH) { + if (client->publishedCb) + client->publishedCb((uint32_t *) client); } + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); +} - } else if (client->connState == TCP_RECONNECT_REQ) { - client->reconnectTick ++; - if (client->reconnectTick > MQTT_RECONNECT_TIMEOUT) { - client->reconnectTick = 0; - client->connState = TCP_RECONNECT; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); - if (client->timeoutCb) - client->timeoutCb((uint32_t*)client); +void ICACHE_FLASH_ATTR mqtt_timer(void *arg) { + MQTT_Client *client = (MQTT_Client *) arg; + if (client->connState == MQTT_DATA) { + client->keepAliveTick++; + if (client->keepAliveTick > (client->mqtt_state.connect_info->keepalive / 2)) { + client->connState = MQTT_KEEPALIVE_SEND; + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); + } + + } else if (client->connState == TCP_RECONNECT_REQ) { + client->reconnectTick++; + if (client->reconnectTick > MQTT_RECONNECT_TIMEOUT) { + client->reconnectTick = 0; + client->connState = TCP_RECONNECT; + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); + if (client->timeoutCb) + client->timeoutCb((uint32_t *) client); + } } - } - if (client->sendTimeout > 0) - client->sendTimeout --; + if (client->sendTimeout > 0) + client->sendTimeout--; } -void ICACHE_FLASH_ATTR -mqtt_tcpclient_discon_cb(void *arg) -{ +void ICACHE_FLASH_ATTR mqtt_tcpclient_discon_cb(void *arg) { - struct espconn *pespconn = (struct espconn *)arg; - MQTT_Client* client = (MQTT_Client *)pespconn->reverse; - MQTT_INFO("TCP: Disconnected callback\r\n"); - if (TCP_DISCONNECTING == client->connState) { - client->connState = TCP_DISCONNECTED; - } - else if (MQTT_DELETING == client->connState) { - client->connState = MQTT_DELETED; - } - else { - client->connState = TCP_RECONNECT_REQ; - } - if (client->disconnectedCb) - client->disconnectedCb((uint32_t*)client); + struct espconn *pespconn = (struct espconn *)arg; + MQTT_Client *client = (MQTT_Client *) pespconn->reverse; + MQTT_INFO("TCP: Disconnected callback\r\n"); + if (TCP_DISCONNECTING == client->connState) { + client->connState = TCP_DISCONNECTED; + } else if (MQTT_DELETING == client->connState) { + client->connState = MQTT_DELETED; + } else { + client->connState = TCP_RECONNECT_REQ; + } + if (client->disconnectedCb) + client->disconnectedCb((uint32_t *) client); - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); } - - /** * @brief Tcp client connect success callback function. * @param arg: contain the ip link information * @retval None */ -void ICACHE_FLASH_ATTR -mqtt_tcpclient_connect_cb(void *arg) -{ - struct espconn *pCon = (struct espconn *)arg; - MQTT_Client* client = (MQTT_Client *)pCon->reverse; +void ICACHE_FLASH_ATTR mqtt_tcpclient_connect_cb(void *arg) { + struct espconn *pCon = (struct espconn *)arg; + MQTT_Client *client = (MQTT_Client *) pCon->reverse; - espconn_regist_disconcb(client->pCon, mqtt_tcpclient_discon_cb); - espconn_regist_recvcb(client->pCon, mqtt_tcpclient_recv);//////// - espconn_regist_sentcb(client->pCon, mqtt_tcpclient_sent_cb);/////// - MQTT_INFO("MQTT: Connected to broker %s:%d\r\n", client->host, client->port); + espconn_regist_disconcb(client->pCon, mqtt_tcpclient_discon_cb); + espconn_regist_recvcb(client->pCon, mqtt_tcpclient_recv); //////// + espconn_regist_sentcb(client->pCon, mqtt_tcpclient_sent_cb); /////// + MQTT_INFO("MQTT: Connected to broker %s:%d\r\n", client->host, client->port); - mqtt_msg_init(&client->mqtt_state.mqtt_connection, client->mqtt_state.out_buffer, client->mqtt_state.out_buffer_length); - client->mqtt_state.outbound_message = mqtt_msg_connect(&client->mqtt_state.mqtt_connection, client->mqtt_state.connect_info); - client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); - client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); + mqtt_msg_init(&client->mqtt_state.mqtt_connection, client->mqtt_state.out_buffer, + client->mqtt_state.out_buffer_length); + client->mqtt_state.outbound_message = + mqtt_msg_connect(&client->mqtt_state.mqtt_connection, client->mqtt_state.connect_info); + client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); + client->mqtt_state.pending_msg_id = + mqtt_get_id(client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); - - client->sendTimeout = MQTT_SEND_TIMOUT; - MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, client->mqtt_state.pending_msg_id); - if (client->security) { + client->sendTimeout = MQTT_SEND_TIMOUT; + MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, + client->mqtt_state.pending_msg_id); + if (client->security) { #ifdef MQTT_SSL_ENABLE - espconn_secure_send(client->pCon, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); + espconn_secure_send(client->pCon, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length); #else - MQTT_INFO("TCP: Do not support SSL\r\n"); + MQTT_INFO("TCP: Do not support SSL\r\n"); #endif - } - else { - espconn_send(client->pCon, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); - } + } else { + espconn_send(client->pCon, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length); + } - client->mqtt_state.outbound_message = NULL; - client->connState = MQTT_CONNECT_SENDING; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); + client->mqtt_state.outbound_message = NULL; + client->connState = MQTT_CONNECT_SENDING; + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); } /** @@ -560,17 +541,15 @@ mqtt_tcpclient_connect_cb(void *arg) * @param arg: contain the ip link information * @retval None */ -void ICACHE_FLASH_ATTR -mqtt_tcpclient_recon_cb(void *arg, sint8 errType) -{ - struct espconn *pCon = (struct espconn *)arg; - MQTT_Client* client = (MQTT_Client *)pCon->reverse; +void ICACHE_FLASH_ATTR mqtt_tcpclient_recon_cb(void *arg, sint8 errType) { + struct espconn *pCon = (struct espconn *)arg; + MQTT_Client *client = (MQTT_Client *) pCon->reverse; - MQTT_INFO("TCP: Reconnect to %s:%d\r\n", client->host, client->port); + MQTT_INFO("TCP: Reconnect to %s:%d\r\n", client->host, client->port); - client->connState = TCP_RECONNECT_REQ; + client->connState = TCP_RECONNECT_REQ; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); } @@ -585,28 +564,29 @@ mqtt_tcpclient_recon_cb(void *arg, sint8 errType) * @retval TRUE if success queue */ BOOL ICACHE_FLASH_ATTR -MQTT_Publish(MQTT_Client *client, const char* topic, const char* data, int data_length, int qos, int retain) -{ - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - client->mqtt_state.outbound_message = mqtt_msg_publish(&client->mqtt_state.mqtt_connection, - topic, data, data_length, - qos, retain, - &client->mqtt_state.pending_msg_id); - if (client->mqtt_state.outbound_message->length == 0) { - MQTT_INFO("MQTT: Queuing publish failed\r\n"); - return FALSE; - } - MQTT_INFO("MQTT: queuing publish, length: %d, queue size(%d/%d)\r\n", client->mqtt_state.outbound_message->length, client->msgQueue.rb.fill_cnt, client->msgQueue.rb.size); - while (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { - MQTT_INFO("MQTT: Serious buffer error\r\n"); - return FALSE; +MQTT_Publish(MQTT_Client * client, const char *topic, const char *data, int data_length, int qos, int retain) { + uint8_t dataBuffer[MQTT_BUF_SIZE]; + uint16_t dataLen; + client->mqtt_state.outbound_message = mqtt_msg_publish(&client->mqtt_state.mqtt_connection, + topic, data, data_length, + qos, retain, &client->mqtt_state.pending_msg_id); + if (client->mqtt_state.outbound_message->length == 0) { + MQTT_INFO("MQTT: Queuing publish failed\r\n"); + return FALSE; } - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); - return TRUE; + MQTT_INFO("MQTT: queuing publish, length: %d, queue size(%d/%d)\r\n", client->mqtt_state.outbound_message->length, + client->msgQueue.rb.fill_cnt, client->msgQueue.rb.size); + while (QUEUE_Puts + (&client->msgQueue, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length) == -1) { + MQTT_INFO("MQTT: Queue full\r\n"); + if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { + MQTT_INFO("MQTT: Serious buffer error\r\n"); + return FALSE; + } + } + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); + return TRUE; } /** @@ -616,26 +596,25 @@ MQTT_Publish(MQTT_Client *client, const char* topic, const char* data, int data_ * @param qos: qos * @retval TRUE if success queue */ -BOOL ICACHE_FLASH_ATTR -MQTT_Subscribe(MQTT_Client *client, char* topic, uint8_t qos) -{ - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; +BOOL ICACHE_FLASH_ATTR MQTT_Subscribe(MQTT_Client * client, char *topic, uint8_t qos) { + uint8_t dataBuffer[MQTT_BUF_SIZE]; + uint16_t dataLen; - client->mqtt_state.outbound_message = mqtt_msg_subscribe(&client->mqtt_state.mqtt_connection, - topic, qos, - &client->mqtt_state.pending_msg_id); - MQTT_INFO("MQTT: queue subscribe, topic\"%s\", id: %d\r\n", topic, client->mqtt_state.pending_msg_id); - while (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { - MQTT_INFO("MQTT: Serious buffer error\r\n"); - return FALSE; + client->mqtt_state.outbound_message = mqtt_msg_subscribe(&client->mqtt_state.mqtt_connection, + topic, qos, &client->mqtt_state.pending_msg_id); + MQTT_INFO("MQTT: queue subscribe, topic\"%s\", id: %d\r\n", topic, client->mqtt_state.pending_msg_id); + while (QUEUE_Puts + (&client->msgQueue, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length) == -1) { + MQTT_INFO("MQTT: Queue full\r\n"); + if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { + MQTT_INFO("MQTT: Serious buffer error\r\n"); + return FALSE; + } } - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - return TRUE; + return TRUE; } /** @@ -644,24 +623,23 @@ MQTT_Subscribe(MQTT_Client *client, char* topic, uint8_t qos) * @param topic: String topic will un-subscribe * @retval TRUE if success queue */ -BOOL ICACHE_FLASH_ATTR -MQTT_UnSubscribe(MQTT_Client *client, char* topic) -{ - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - client->mqtt_state.outbound_message = mqtt_msg_unsubscribe(&client->mqtt_state.mqtt_connection, - topic, - &client->mqtt_state.pending_msg_id); - MQTT_INFO("MQTT: queue un-subscribe, topic\"%s\", id: %d\r\n", topic, client->mqtt_state.pending_msg_id); - while (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { - MQTT_INFO("MQTT: Serious buffer error\r\n"); - return FALSE; +BOOL ICACHE_FLASH_ATTR MQTT_UnSubscribe(MQTT_Client * client, char *topic) { + uint8_t dataBuffer[MQTT_BUF_SIZE]; + uint16_t dataLen; + client->mqtt_state.outbound_message = mqtt_msg_unsubscribe(&client->mqtt_state.mqtt_connection, + topic, &client->mqtt_state.pending_msg_id); + MQTT_INFO("MQTT: queue un-subscribe, topic\"%s\", id: %d\r\n", topic, client->mqtt_state.pending_msg_id); + while (QUEUE_Puts + (&client->msgQueue, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length) == -1) { + MQTT_INFO("MQTT: Queue full\r\n"); + if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { + MQTT_INFO("MQTT: Serious buffer error\r\n"); + return FALSE; + } } - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); - return TRUE; + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); + return TRUE; } /** @@ -669,99 +647,96 @@ MQTT_UnSubscribe(MQTT_Client *client, char* topic) * @param client: MQTT_Client reference * @retval TRUE if success queue */ -BOOL ICACHE_FLASH_ATTR -MQTT_Ping(MQTT_Client *client) -{ - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); - if (client->mqtt_state.outbound_message->length == 0) { - MQTT_INFO("MQTT: Queuing publish failed\r\n"); - return FALSE; - } - MQTT_INFO("MQTT: queuing publish, length: %d, queue size(%d/%d)\r\n", client->mqtt_state.outbound_message->length, client->msgQueue.rb.fill_cnt, client->msgQueue.rb.size); - while (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { - MQTT_INFO("MQTT: Serious buffer error\r\n"); - return FALSE; +BOOL ICACHE_FLASH_ATTR MQTT_Ping(MQTT_Client * client) { + uint8_t dataBuffer[MQTT_BUF_SIZE]; + uint16_t dataLen; + client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); + if (client->mqtt_state.outbound_message->length == 0) { + MQTT_INFO("MQTT: Queuing publish failed\r\n"); + return FALSE; } - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); - return TRUE; + MQTT_INFO("MQTT: queuing publish, length: %d, queue size(%d/%d)\r\n", client->mqtt_state.outbound_message->length, + client->msgQueue.rb.fill_cnt, client->msgQueue.rb.size); + while (QUEUE_Puts + (&client->msgQueue, client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length) == -1) { + MQTT_INFO("MQTT: Queue full\r\n"); + if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { + MQTT_INFO("MQTT: Serious buffer error\r\n"); + return FALSE; + } + } + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); + return TRUE; } -void ICACHE_FLASH_ATTR -MQTT_Task(os_event_t *e) -{ - MQTT_Client* client = (MQTT_Client*)e->par; - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - if (e->par == 0) - return; - MQTT_INFO("MQTT: Client task activated - state %d\r\n", client->connState); - switch (client->connState) { +void ICACHE_FLASH_ATTR MQTT_Task(os_event_t * e) { + MQTT_Client *client = (MQTT_Client *) e->par; + uint8_t dataBuffer[MQTT_BUF_SIZE]; + uint16_t dataLen; + if (e->par == 0) + return; + MQTT_INFO("MQTT: Client task activated - state %d\r\n", client->connState); + switch (client->connState) { case TCP_RECONNECT_REQ: - break; + break; case TCP_RECONNECT: - mqtt_tcpclient_delete(client); - MQTT_Connect(client); - MQTT_INFO("TCP: Reconnect to: %s:%d\r\n", client->host, client->port); - client->connState = TCP_CONNECTING; - break; + mqtt_tcpclient_delete(client); + MQTT_Connect(client); + MQTT_INFO("TCP: Reconnect to: %s:%d\r\n", client->host, client->port); + client->connState = TCP_CONNECTING; + break; case MQTT_DELETING: case TCP_DISCONNECTING: case TCP_RECONNECT_DISCONNECTING: - if (client->security) { + if (client->security) { #ifdef MQTT_SSL_ENABLE - espconn_secure_disconnect(client->pCon); + espconn_secure_disconnect(client->pCon); #else - MQTT_INFO("TCP: Do not support SSL\r\n"); + MQTT_INFO("TCP: Do not support SSL\r\n"); #endif - } - else { - espconn_disconnect(client->pCon); - } - break; + } else { + espconn_disconnect(client->pCon); + } + break; case TCP_DISCONNECTED: - MQTT_INFO("MQTT: Disconnected\r\n"); - mqtt_tcpclient_delete(client); - break; + MQTT_INFO("MQTT: Disconnected\r\n"); + mqtt_tcpclient_delete(client); + break; case MQTT_DELETED: - MQTT_INFO("MQTT: Deleted client\r\n"); - mqtt_client_delete(client); - break; + MQTT_INFO("MQTT: Deleted client\r\n"); + mqtt_client_delete(client); + break; case MQTT_KEEPALIVE_SEND: - mqtt_send_keepalive(client); - break; + mqtt_send_keepalive(client); + break; case MQTT_DATA: - if (QUEUE_IsEmpty(&client->msgQueue) || client->sendTimeout != 0) { - break; - } - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == 0) { - client->mqtt_state.pending_msg_type = mqtt_get_type(dataBuffer); - client->mqtt_state.pending_msg_id = mqtt_get_id(dataBuffer, dataLen); + if (QUEUE_IsEmpty(&client->msgQueue) || client->sendTimeout != 0) { + break; + } + if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == 0) { + client->mqtt_state.pending_msg_type = mqtt_get_type(dataBuffer); + client->mqtt_state.pending_msg_id = mqtt_get_id(dataBuffer, dataLen); - - client->sendTimeout = MQTT_SEND_TIMOUT; - MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, client->mqtt_state.pending_msg_id); - if (client->security) { + client->sendTimeout = MQTT_SEND_TIMOUT; + MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, + client->mqtt_state.pending_msg_id); + if (client->security) { #ifdef MQTT_SSL_ENABLE - espconn_secure_send(client->pCon, dataBuffer, dataLen); + espconn_secure_send(client->pCon, dataBuffer, dataLen); #else - MQTT_INFO("TCP: Do not support SSL\r\n"); + MQTT_INFO("TCP: Do not support SSL\r\n"); #endif - } - else { - espconn_send(client->pCon, dataBuffer, dataLen); - } + } else { + espconn_send(client->pCon, dataBuffer, dataLen); + } - client->mqtt_state.outbound_message = NULL; - break; - } - break; - } + client->mqtt_state.outbound_message = NULL; + break; + } + break; + } } /** @@ -772,18 +747,16 @@ MQTT_Task(os_event_t *e) * @param security: 1 for ssl, 0 for none * @retval None */ -void ICACHE_FLASH_ATTR -MQTT_InitConnection(MQTT_Client *mqttClient, uint8_t* host, uint32_t port, uint8_t security) -{ - uint32_t temp; - MQTT_INFO("MQTT:InitConnection\r\n"); - os_memset(mqttClient, 0, sizeof(MQTT_Client)); - temp = os_strlen(host); - mqttClient->host = (uint8_t*)os_zalloc(temp + 1); - os_strcpy(mqttClient->host, host); - mqttClient->host[temp] = 0; - mqttClient->port = port; - mqttClient->security = security; +void ICACHE_FLASH_ATTR MQTT_InitConnection(MQTT_Client * mqttClient, uint8_t * host, uint32_t port, uint8_t security) { + uint32_t temp; + MQTT_INFO("MQTT:InitConnection\r\n"); + os_memset(mqttClient, 0, sizeof(MQTT_Client)); + temp = os_strlen(host); + mqttClient->host = (uint8_t *) os_zalloc(temp + 1); + os_strcpy(mqttClient->host, host); + mqttClient->host[temp] = 0; + mqttClient->port = port; + mqttClient->security = security; } @@ -797,199 +770,173 @@ MQTT_InitConnection(MQTT_Client *mqttClient, uint8_t* host, uint32_t port, uint8 * @retval None */ BOOL ICACHE_FLASH_ATTR -MQTT_InitClient(MQTT_Client *mqttClient, uint8_t* client_id, uint8_t* client_user, uint8_t* client_pass, uint32_t keepAliveTime, uint8_t cleanSession) -{ - uint32_t temp; - MQTT_INFO("MQTT:InitClient\r\n"); +MQTT_InitClient(MQTT_Client * mqttClient, uint8_t * client_id, uint8_t * client_user, uint8_t * client_pass, + uint32_t keepAliveTime, uint8_t cleanSession) { + uint32_t temp; + MQTT_INFO("MQTT:InitClient\r\n"); - os_memset(&mqttClient->connect_info, 0, sizeof(mqtt_connect_info_t)); + os_memset(&mqttClient->connect_info, 0, sizeof(mqtt_connect_info_t)); - if ( !client_id ) - { - /* Should be allowed by broker, but clean session flag must be set. */ - #ifdef PROTOCOL_NAMEv311 - if (cleanSession) - { - mqttClient->connect_info.client_id = zero_len_id; - } else { - MQTT_INFO("cleanSession must be set to use 0 length client_id\r\n"); - return false; + if (!client_id) { + /* Should be allowed by broker, but clean session flag must be set. */ +#ifdef PROTOCOL_NAMEv311 + if (cleanSession) { + mqttClient->connect_info.client_id = zero_len_id; + } else { + MQTT_INFO("cleanSession must be set to use 0 length client_id\r\n"); + return false; + } + /* Not supported. Return. */ +#else + MQTT_INFO("Client ID required for MQTT < 3.1.1!\r\n"); + return false; +#endif } - /* Not supported. Return. */ - #else - MQTT_INFO("Client ID required for MQTT < 3.1.1!\r\n"); - return false; - #endif - } - /* If connect_info's client_id is still NULL and we get here, we can * - * assume the passed client_id is non-NULL. */ - if ( !(mqttClient->connect_info.client_id) ) - { - temp = os_strlen(client_id); - mqttClient->connect_info.client_id = (uint8_t*)os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.client_id, client_id); - mqttClient->connect_info.client_id[temp] = 0; - } + /* If connect_info's client_id is still NULL and we get here, we can * + * assume the passed client_id is non-NULL. */ + if (!(mqttClient->connect_info.client_id)) { + temp = os_strlen(client_id); + mqttClient->connect_info.client_id = (uint8_t *) os_zalloc(temp + 1); + os_strcpy(mqttClient->connect_info.client_id, client_id); + mqttClient->connect_info.client_id[temp] = 0; + } - if (client_user) - { - temp = os_strlen(client_user); - mqttClient->connect_info.username = (uint8_t*)os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.username, client_user); - mqttClient->connect_info.username[temp] = 0; - } + if (client_user) { + temp = os_strlen(client_user); + mqttClient->connect_info.username = (uint8_t *) os_zalloc(temp + 1); + os_strcpy(mqttClient->connect_info.username, client_user); + mqttClient->connect_info.username[temp] = 0; + } - if (client_pass) - { - temp = os_strlen(client_pass); - mqttClient->connect_info.password = (uint8_t*)os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.password, client_pass); - mqttClient->connect_info.password[temp] = 0; - } + if (client_pass) { + temp = os_strlen(client_pass); + mqttClient->connect_info.password = (uint8_t *) os_zalloc(temp + 1); + os_strcpy(mqttClient->connect_info.password, client_pass); + mqttClient->connect_info.password[temp] = 0; + } + mqttClient->connect_info.keepalive = keepAliveTime; + mqttClient->connect_info.clean_session = cleanSession; - mqttClient->connect_info.keepalive = keepAliveTime; - mqttClient->connect_info.clean_session = cleanSession; + mqttClient->mqtt_state.in_buffer = (uint8_t *) os_zalloc(MQTT_BUF_SIZE); + mqttClient->mqtt_state.in_buffer_length = MQTT_BUF_SIZE; + mqttClient->mqtt_state.out_buffer = (uint8_t *) os_zalloc(MQTT_BUF_SIZE); + mqttClient->mqtt_state.out_buffer_length = MQTT_BUF_SIZE; + mqttClient->mqtt_state.connect_info = &mqttClient->connect_info; - mqttClient->mqtt_state.in_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE); - mqttClient->mqtt_state.in_buffer_length = MQTT_BUF_SIZE; - mqttClient->mqtt_state.out_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE); - mqttClient->mqtt_state.out_buffer_length = MQTT_BUF_SIZE; - mqttClient->mqtt_state.connect_info = &mqttClient->connect_info; + mqtt_msg_init(&mqttClient->mqtt_state.mqtt_connection, mqttClient->mqtt_state.out_buffer, + mqttClient->mqtt_state.out_buffer_length); - mqtt_msg_init(&mqttClient->mqtt_state.mqtt_connection, mqttClient->mqtt_state.out_buffer, mqttClient->mqtt_state.out_buffer_length); + QUEUE_Init(&mqttClient->msgQueue, QUEUE_BUFFER_SIZE); - QUEUE_Init(&mqttClient->msgQueue, QUEUE_BUFFER_SIZE); - - system_os_task(MQTT_Task, MQTT_TASK_PRIO, mqtt_procTaskQueue, MQTT_TASK_QUEUE_SIZE); - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient); - return true; + system_os_task(MQTT_Task, MQTT_TASK_PRIO, mqtt_procTaskQueue, MQTT_TASK_QUEUE_SIZE); + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) mqttClient); + return true; } void ICACHE_FLASH_ATTR -MQTT_InitLWT(MQTT_Client *mqttClient, uint8_t* will_topic, uint8_t* will_msg, uint8_t will_qos, uint8_t will_retain) +MQTT_InitLWT(MQTT_Client * mqttClient, uint8_t * will_topic, uint8_t * will_msg, uint8_t will_qos, uint8_t will_retain) { - uint32_t temp; - temp = os_strlen(will_topic); - mqttClient->connect_info.will_topic = (uint8_t*)os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.will_topic, will_topic); - mqttClient->connect_info.will_topic[temp] = 0; + uint32_t temp; + temp = os_strlen(will_topic); + mqttClient->connect_info.will_topic = (uint8_t *) os_zalloc(temp + 1); + os_strcpy(mqttClient->connect_info.will_topic, will_topic); + mqttClient->connect_info.will_topic[temp] = 0; - temp = os_strlen(will_msg); - mqttClient->connect_info.will_data = (uint8_t*)os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.will_data, will_msg); - mqttClient->connect_info.will_data[temp] = 0; + temp = os_strlen(will_msg); + mqttClient->connect_info.will_data = (uint8_t *) os_zalloc(temp + 1); + os_strcpy(mqttClient->connect_info.will_data, will_msg); + mqttClient->connect_info.will_data[temp] = 0; - - mqttClient->connect_info.will_qos = will_qos; - mqttClient->connect_info.will_retain = will_retain; + mqttClient->connect_info.will_qos = will_qos; + mqttClient->connect_info.will_retain = will_retain; } + /** * @brief Begin connect to MQTT broker * @param client: MQTT_Client reference * @retval None */ -void ICACHE_FLASH_ATTR -MQTT_Connect(MQTT_Client *mqttClient) -{ - if (mqttClient->pCon) { - // Clean up the old connection forcefully - using MQTT_Disconnect - // does not actually release the old connection until the - // disconnection callback is invoked. - mqtt_tcpclient_delete(mqttClient); - } - mqttClient->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); - mqttClient->pCon->type = ESPCONN_TCP; - mqttClient->pCon->state = ESPCONN_NONE; - mqttClient->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); - mqttClient->pCon->proto.tcp->local_port = espconn_port(); - mqttClient->pCon->proto.tcp->remote_port = mqttClient->port; - mqttClient->pCon->reverse = mqttClient; - espconn_regist_connectcb(mqttClient->pCon, mqtt_tcpclient_connect_cb); - espconn_regist_reconcb(mqttClient->pCon, mqtt_tcpclient_recon_cb); +void ICACHE_FLASH_ATTR MQTT_Connect(MQTT_Client * mqttClient) { + if (mqttClient->pCon) { + // Clean up the old connection forcefully - using MQTT_Disconnect + // does not actually release the old connection until the + // disconnection callback is invoked. + mqtt_tcpclient_delete(mqttClient); + } + mqttClient->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + mqttClient->pCon->type = ESPCONN_TCP; + mqttClient->pCon->state = ESPCONN_NONE; + mqttClient->pCon->proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); + mqttClient->pCon->proto.tcp->local_port = espconn_port(); + mqttClient->pCon->proto.tcp->remote_port = mqttClient->port; + mqttClient->pCon->reverse = mqttClient; + espconn_regist_connectcb(mqttClient->pCon, mqtt_tcpclient_connect_cb); + espconn_regist_reconcb(mqttClient->pCon, mqtt_tcpclient_recon_cb); - mqttClient->keepAliveTick = 0; - mqttClient->reconnectTick = 0; + mqttClient->keepAliveTick = 0; + mqttClient->reconnectTick = 0; + os_timer_disarm(&mqttClient->mqttTimer); + os_timer_setfn(&mqttClient->mqttTimer, (os_timer_func_t *) mqtt_timer, mqttClient); + os_timer_arm(&mqttClient->mqttTimer, 1000, 1); - os_timer_disarm(&mqttClient->mqttTimer); - os_timer_setfn(&mqttClient->mqttTimer, (os_timer_func_t *)mqtt_timer, mqttClient); - os_timer_arm(&mqttClient->mqttTimer, 1000, 1); - - if (UTILS_StrToIP(mqttClient->host, &mqttClient->pCon->proto.tcp->remote_ip)) { - MQTT_INFO("TCP: Connect to ip %s:%d\r\n", mqttClient->host, mqttClient->port); - if (mqttClient->security) - { + if (UTILS_StrToIP(mqttClient->host, &mqttClient->pCon->proto.tcp->remote_ip)) { + MQTT_INFO("TCP: Connect to ip %s:%d\r\n", mqttClient->host, mqttClient->port); + if (mqttClient->security) { #ifdef MQTT_SSL_ENABLE - espconn_secure_set_size(ESPCONN_CLIENT, MQTT_SSL_SIZE); - espconn_secure_connect(mqttClient->pCon); + espconn_secure_set_size(ESPCONN_CLIENT, MQTT_SSL_SIZE); + espconn_secure_connect(mqttClient->pCon); #else - MQTT_INFO("TCP: Do not support SSL\r\n"); + MQTT_INFO("TCP: Do not support SSL\r\n"); #endif + } else { + espconn_connect(mqttClient->pCon); + } + } else { + MQTT_INFO("TCP: Connect to domain %s:%d\r\n", mqttClient->host, mqttClient->port); + espconn_gethostbyname(mqttClient->pCon, mqttClient->host, &mqttClient->ip, mqtt_dns_found); } - else - { - espconn_connect(mqttClient->pCon); - } - } - else { - MQTT_INFO("TCP: Connect to domain %s:%d\r\n", mqttClient->host, mqttClient->port); - espconn_gethostbyname(mqttClient->pCon, mqttClient->host, &mqttClient->ip, mqtt_dns_found); - } - mqttClient->connState = TCP_CONNECTING; + mqttClient->connState = TCP_CONNECTING; } -void ICACHE_FLASH_ATTR -MQTT_Disconnect(MQTT_Client *mqttClient) -{ - mqttClient->connState = TCP_DISCONNECTING; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient); - os_timer_disarm(&mqttClient->mqttTimer); +void ICACHE_FLASH_ATTR MQTT_Disconnect(MQTT_Client * mqttClient) { + mqttClient->connState = TCP_DISCONNECTING; + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) mqttClient); + os_timer_disarm(&mqttClient->mqttTimer); } -void ICACHE_FLASH_ATTR -MQTT_DeleteClient(MQTT_Client *mqttClient) -{ - if (NULL == mqttClient) - return; +void ICACHE_FLASH_ATTR MQTT_DeleteClient(MQTT_Client * mqttClient) { + if (NULL == mqttClient) + return; - mqttClient->connState = MQTT_DELETED; - // if(TCP_DISCONNECTED == mqttClient->connState) { - // mqttClient->connState = MQTT_DELETED; - // } else if(MQTT_DELETED != mqttClient->connState) { - // mqttClient->connState = MQTT_DELETING; - // } + mqttClient->connState = MQTT_DELETED; + // if(TCP_DISCONNECTED == mqttClient->connState) { + // mqttClient->connState = MQTT_DELETED; + // } else if(MQTT_DELETED != mqttClient->connState) { + // mqttClient->connState = MQTT_DELETING; + // } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient); - os_timer_disarm(&mqttClient->mqttTimer); + system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) mqttClient); + os_timer_disarm(&mqttClient->mqttTimer); } -void ICACHE_FLASH_ATTR -MQTT_OnConnected(MQTT_Client *mqttClient, MqttCallback connectedCb) -{ - mqttClient->connectedCb = connectedCb; +void ICACHE_FLASH_ATTR MQTT_OnConnected(MQTT_Client * mqttClient, MqttCallback connectedCb) { + mqttClient->connectedCb = connectedCb; } -void ICACHE_FLASH_ATTR -MQTT_OnDisconnected(MQTT_Client *mqttClient, MqttCallback disconnectedCb) -{ - mqttClient->disconnectedCb = disconnectedCb; +void ICACHE_FLASH_ATTR MQTT_OnDisconnected(MQTT_Client * mqttClient, MqttCallback disconnectedCb) { + mqttClient->disconnectedCb = disconnectedCb; } -void ICACHE_FLASH_ATTR -MQTT_OnData(MQTT_Client *mqttClient, MqttDataCallback dataCb) -{ - mqttClient->dataCb = dataCb; +void ICACHE_FLASH_ATTR MQTT_OnData(MQTT_Client * mqttClient, MqttDataCallback dataCb) { + mqttClient->dataCb = dataCb; } -void ICACHE_FLASH_ATTR -MQTT_OnPublished(MQTT_Client *mqttClient, MqttCallback publishedCb) -{ - mqttClient->publishedCb = publishedCb; +void ICACHE_FLASH_ATTR MQTT_OnPublished(MQTT_Client * mqttClient, MqttCallback publishedCb) { + mqttClient->publishedCb = publishedCb; } -void ICACHE_FLASH_ATTR -MQTT_OnTimeout(MQTT_Client *mqttClient, MqttCallback timeoutCb) -{ - mqttClient->timeoutCb = timeoutCb; +void ICACHE_FLASH_ATTR MQTT_OnTimeout(MQTT_Client * mqttClient, MqttCallback timeoutCb) { + mqttClient->timeoutCb = timeoutCb; } diff --git a/mqtt/mqtt_msg.c b/mqtt/mqtt_msg.c index 5c7f312..a3b5201 100644 --- a/mqtt/mqtt_msg.c +++ b/mqtt/mqtt_msg.c @@ -37,231 +37,206 @@ #include "mqtt_msg.h" #include "user_config.h" -static int ICACHE_FLASH_ATTR append_string(mqtt_connection_t* connection, const char* string, int len) -{ - if (connection->message.length + len + 2 > connection->buffer_length) - return -1; +static int ICACHE_FLASH_ATTR append_string(mqtt_connection_t * connection, const char *string, int len) { + if (connection->message.length + len + 2 > connection->buffer_length) + return -1; - connection->buffer[connection->message.length++] = len >> 8; - connection->buffer[connection->message.length++] = len & 0xff; - os_memcpy(connection->buffer + connection->message.length, string, len); - connection->message.length += len; + connection->buffer[connection->message.length++] = len >> 8; + connection->buffer[connection->message.length++] = len & 0xff; + os_memcpy(connection->buffer + connection->message.length, string, len); + connection->message.length += len; - return len + 2; + return len + 2; } -static uint16_t ICACHE_FLASH_ATTR append_message_id(mqtt_connection_t* connection, uint16_t message_id) -{ - // If message_id is zero then we should assign one, otherwise - // we'll use the one supplied by the caller - while (message_id == 0) - message_id = ++connection->message_id; +static uint16_t ICACHE_FLASH_ATTR append_message_id(mqtt_connection_t * connection, uint16_t message_id) { + // If message_id is zero then we should assign one, otherwise + // we'll use the one supplied by the caller + while (message_id == 0) + message_id = ++connection->message_id; - if (connection->message.length + 2 > connection->buffer_length) - return 0; + if (connection->message.length + 2 > connection->buffer_length) + return 0; - connection->buffer[connection->message.length++] = message_id >> 8; - connection->buffer[connection->message.length++] = message_id & 0xff; + connection->buffer[connection->message.length++] = message_id >> 8; + connection->buffer[connection->message.length++] = message_id & 0xff; - return message_id; + return message_id; } -static int ICACHE_FLASH_ATTR init_message(mqtt_connection_t* connection) -{ - connection->message.length = MQTT_MAX_FIXED_HEADER_SIZE; - return MQTT_MAX_FIXED_HEADER_SIZE; +static int ICACHE_FLASH_ATTR init_message(mqtt_connection_t * connection) { + connection->message.length = MQTT_MAX_FIXED_HEADER_SIZE; + return MQTT_MAX_FIXED_HEADER_SIZE; } -static mqtt_message_t* ICACHE_FLASH_ATTR fail_message(mqtt_connection_t* connection) -{ - connection->message.data = connection->buffer; - connection->message.length = 0; - return &connection->message; -} - -static mqtt_message_t* ICACHE_FLASH_ATTR fini_message(mqtt_connection_t* connection, int type, int dup, int qos, int retain) -{ - int remaining_length = connection->message.length - MQTT_MAX_FIXED_HEADER_SIZE; - - if (remaining_length > 127) - { - connection->buffer[0] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); - connection->buffer[1] = 0x80 | (remaining_length % 128); - connection->buffer[2] = remaining_length / 128; - connection->message.length = remaining_length + 3; +static mqtt_message_t *ICACHE_FLASH_ATTR fail_message(mqtt_connection_t * connection) { connection->message.data = connection->buffer; - } - else - { - connection->buffer[1] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); - connection->buffer[2] = remaining_length; - connection->message.length = remaining_length + 2; - connection->message.data = connection->buffer + 1; - } - - return &connection->message; + connection->message.length = 0; + return &connection->message; } -void ICACHE_FLASH_ATTR mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length) -{ - os_memset(connection, 0, sizeof(mqtt_connection_t)); - connection->buffer = buffer; - connection->buffer_length = buffer_length; -} +static mqtt_message_t *ICACHE_FLASH_ATTR fini_message(mqtt_connection_t * connection, int type, int dup, int qos, + int retain) { + int remaining_length = connection->message.length - MQTT_MAX_FIXED_HEADER_SIZE; -int ICACHE_FLASH_ATTR mqtt_get_total_length(uint8_t* buffer, uint16_t length) -{ - int i; - int totlen = 0; - - for (i = 1; i < length; ++i) - { - totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); - if ((buffer[i] & 0x80) == 0) - { - ++i; - break; + if (remaining_length > 127) { + connection->buffer[0] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + connection->buffer[1] = 0x80 | (remaining_length % 128); + connection->buffer[2] = remaining_length / 128; + connection->message.length = remaining_length + 3; + connection->message.data = connection->buffer; + } else { + connection->buffer[1] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + connection->buffer[2] = remaining_length; + connection->message.length = remaining_length + 2; + connection->message.data = connection->buffer + 1; } - } - totlen += i; - return totlen; + return &connection->message; } - -char* ICACHE_FLASH_ATTR mqtt_get_str(uint8_t* buffer, uint16_t* length) -{ - int i = 0; - int topiclen; - - if (i + 2 >= *length) - return NULL; - topiclen = buffer[i++] << 8; - topiclen |= buffer[i++]; - - if (i + topiclen > *length) - return NULL; - - *length = topiclen; - return buffer + i; +void ICACHE_FLASH_ATTR mqtt_msg_init(mqtt_connection_t * connection, uint8_t * buffer, uint16_t buffer_length) { + os_memset(connection, 0, sizeof(mqtt_connection_t)); + connection->buffer = buffer; + connection->buffer_length = buffer_length; } -const char* ICACHE_FLASH_ATTR mqtt_get_publish_topic(uint8_t* buffer, uint16_t* length) -{ - int i; - int totlen = 0; - int topiclen; +int ICACHE_FLASH_ATTR mqtt_get_total_length(uint8_t * buffer, uint16_t length) { + int i; + int totlen = 0; - for (i = 1; i < *length; ++i) - { - totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); - if ((buffer[i] & 0x80) == 0) - { - ++i; - break; + for (i = 1; i < length; ++i) { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } } - } - totlen += i; + totlen += i; - if (i + 2 >= *length) - return NULL; - topiclen = buffer[i++] << 8; - topiclen |= buffer[i++]; - - if (i + topiclen > *length) - return NULL; - - *length = topiclen; - return (const char*)(buffer + i); + return totlen; } -const char* ICACHE_FLASH_ATTR mqtt_get_publish_data(uint8_t* buffer, uint16_t* length) -{ - int i; - int totlen = 0; - int topiclen; - int blength = *length; - *length = 0; +char *ICACHE_FLASH_ATTR mqtt_get_str(uint8_t * buffer, uint16_t * length) { + int i = 0; + int topiclen; - for (i = 1; i < blength; ++i) - { - totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); - if ((buffer[i] & 0x80) == 0) - { - ++i; - break; + if (i + 2 >= *length) + return NULL; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if (i + topiclen > *length) + return NULL; + + *length = topiclen; + return buffer + i; +} + +const char *ICACHE_FLASH_ATTR mqtt_get_publish_topic(uint8_t * buffer, uint16_t * length) { + int i; + int totlen = 0; + int topiclen; + + for (i = 1; i < *length; ++i) { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } } - } - totlen += i; + totlen += i; - if (i + 2 >= blength) - return NULL; - topiclen = buffer[i++] << 8; - topiclen |= buffer[i++]; + if (i + 2 >= *length) + return NULL; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; - if (i + topiclen >= blength) - return NULL; + if (i + topiclen > *length) + return NULL; - i += topiclen; + *length = topiclen; + return (const char *)(buffer + i); +} + +const char *ICACHE_FLASH_ATTR mqtt_get_publish_data(uint8_t * buffer, uint16_t * length) { + int i; + int totlen = 0; + int topiclen; + int blength = *length; + *length = 0; + + for (i = 1; i < blength; ++i) { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + totlen += i; - if (mqtt_get_qos(buffer) > 0) - { if (i + 2 >= blength) - return NULL; - i += 2; - } + return NULL; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; - if (totlen < i) - return NULL; + if (i + topiclen >= blength) + return NULL; - if (totlen <= blength) - *length = totlen - i; - else - *length = blength - i; - return (const char*)(buffer + i); + i += topiclen; + + if (mqtt_get_qos(buffer) > 0) { + if (i + 2 >= blength) + return NULL; + i += 2; + } + + if (totlen < i) + return NULL; + + if (totlen <= blength) + *length = totlen - i; + else + *length = blength - i; + return (const char *)(buffer + i); } -uint16_t ICACHE_FLASH_ATTR mqtt_get_id(uint8_t* buffer, uint16_t length) -{ - if (length < 1) - return 0; +uint16_t ICACHE_FLASH_ATTR mqtt_get_id(uint8_t * buffer, uint16_t length) { + if (length < 1) + return 0; - switch (mqtt_get_type(buffer)) - { + switch (mqtt_get_type(buffer)) { case MQTT_MSG_TYPE_PUBLISH: - { - int i; - int topiclen; + { + int i; + int topiclen; - for (i = 1; i < length; ++i) - { - if ((buffer[i] & 0x80) == 0) - { - ++i; - break; - } - } + for (i = 1; i < length; ++i) { + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } - if (i + 2 >= length) - return 0; - topiclen = buffer[i++] << 8; - topiclen |= buffer[i++]; + if (i + 2 >= length) + return 0; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; - if (i + topiclen >= length) - return 0; - i += topiclen; + if (i + topiclen >= length) + return 0; + i += topiclen; - if (mqtt_get_qos(buffer) > 0) - { - if (i + 2 >= length) - return 0; - //i += 2; - } else { - return 0; - } + if (mqtt_get_qos(buffer) > 0) { + if (i + 2 >= length) + return 0; + //i += 2; + } else { + return 0; + } - return (buffer[i] << 8) | buffer[i + 1]; - } + return (buffer[i] << 8) | buffer[i + 1]; + } case MQTT_MSG_TYPE_PUBACK: case MQTT_MSG_TYPE_PUBREC: case MQTT_MSG_TYPE_PUBREL: @@ -270,249 +245,230 @@ uint16_t ICACHE_FLASH_ATTR mqtt_get_id(uint8_t* buffer, uint16_t length) case MQTT_MSG_TYPE_UNSUBACK: case MQTT_MSG_TYPE_SUBSCRIBE: case MQTT_MSG_TYPE_UNSUBSCRIBE: - { - // This requires the remaining length to be encoded in 1 byte, - // which it should be. - if (length >= 4 && (buffer[1] & 0x80) == 0) - return (buffer[2] << 8) | buffer[3]; - else - return 0; - } + { + // This requires the remaining length to be encoded in 1 byte, + // which it should be. + if (length >= 4 && (buffer[1] & 0x80) == 0) + return (buffer[2] << 8) | buffer[3]; + else + return 0; + } default: - return 0; - } + return 0; + } } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info) -{ - struct mqtt_connect_variable_header* variable_header; +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_connect(mqtt_connection_t * connection, mqtt_connect_info_t * info) { + struct mqtt_connect_variable_header *variable_header; - init_message(connection); + init_message(connection); - if (connection->message.length + sizeof(*variable_header) > connection->buffer_length) - return fail_message(connection); - variable_header = (void*)(connection->buffer + connection->message.length); - connection->message.length += sizeof(*variable_header); + if (connection->message.length + sizeof(*variable_header) > connection->buffer_length) + return fail_message(connection); + variable_header = (void *)(connection->buffer + connection->message.length); + connection->message.length += sizeof(*variable_header); - variable_header->lengthMsb = 0; + variable_header->lengthMsb = 0; #if defined(PROTOCOL_NAMEv31) - variable_header->lengthLsb = 6; - os_memcpy(variable_header->magic, "MQIsdp", 6); - variable_header->version = 3; + variable_header->lengthLsb = 6; + os_memcpy(variable_header->magic, "MQIsdp", 6); + variable_header->version = 3; #elif defined(PROTOCOL_NAMEv311) - variable_header->lengthLsb = 4; - os_memcpy(variable_header->magic, "MQTT", 4); - variable_header->version = 4; + variable_header->lengthLsb = 4; + os_memcpy(variable_header->magic, "MQTT", 4); + variable_header->version = 4; #else #error "Please define protocol name" #endif - variable_header->flags = 0; - variable_header->keepaliveMsb = info->keepalive >> 8; - variable_header->keepaliveLsb = info->keepalive & 0xff; + variable_header->flags = 0; + variable_header->keepaliveMsb = info->keepalive >> 8; + variable_header->keepaliveLsb = info->keepalive & 0xff; - if (info->clean_session) - variable_header->flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION; + if (info->clean_session) + variable_header->flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION; - if (info->client_id == NULL) - { - /* Never allowed */ - return fail_message(connection); - } - else if (info->client_id[0] == '\0') - { + if (info->client_id == NULL) { + /* Never allowed */ + return fail_message(connection); + } else if (info->client_id[0] == '\0') { #ifdef PROTOCOL_NAMEv311 - /* Allowed. Format 0 Length ID */ - append_string(connection, info->client_id, 2) ; + /* Allowed. Format 0 Length ID */ + append_string(connection, info->client_id, 2); #else - /* 0 Length not allowed */ - return fail_message(connection); + /* 0 Length not allowed */ + return fail_message(connection); #endif - } - else - { - /* No 0 data and at least 1 long. Good to go. */ - if(append_string(connection, info->client_id, os_strlen(info->client_id)) < 0) - return fail_message(connection); - } + } else { + /* No 0 data and at least 1 long. Good to go. */ + if (append_string(connection, info->client_id, os_strlen(info->client_id)) < 0) + return fail_message(connection); + } - if (info->will_topic != NULL && info->will_topic[0] != '\0') - { - if (append_string(connection, info->will_topic, os_strlen(info->will_topic)) < 0) - return fail_message(connection); + if (info->will_topic != NULL && info->will_topic[0] != '\0') { + if (append_string(connection, info->will_topic, os_strlen(info->will_topic)) < 0) + return fail_message(connection); - if (append_string(connection, info->will_data, os_strlen(info->will_data)) < 0) - return fail_message(connection); + if (append_string(connection, info->will_data, os_strlen(info->will_data)) < 0) + return fail_message(connection); - variable_header->flags |= MQTT_CONNECT_FLAG_WILL; - if (info->will_retain) - variable_header->flags |= MQTT_CONNECT_FLAG_WILL_RETAIN; - variable_header->flags |= (info->will_qos & 3) << 3; - } + variable_header->flags |= MQTT_CONNECT_FLAG_WILL; + if (info->will_retain) + variable_header->flags |= MQTT_CONNECT_FLAG_WILL_RETAIN; + variable_header->flags |= (info->will_qos & 3) << 3; + } - if (info->username != NULL && info->username[0] != '\0') - { - if (append_string(connection, info->username, os_strlen(info->username)) < 0) - return fail_message(connection); + if (info->username != NULL && info->username[0] != '\0') { + if (append_string(connection, info->username, os_strlen(info->username)) < 0) + return fail_message(connection); - variable_header->flags |= MQTT_CONNECT_FLAG_USERNAME; - } + variable_header->flags |= MQTT_CONNECT_FLAG_USERNAME; + } - if (info->password != NULL && info->password[0] != '\0') - { - if (append_string(connection, info->password, os_strlen(info->password)) < 0) - return fail_message(connection); + if (info->password != NULL && info->password[0] != '\0') { + if (append_string(connection, info->password, os_strlen(info->password)) < 0) + return fail_message(connection); - variable_header->flags |= MQTT_CONNECT_FLAG_PASSWORD; - } + variable_header->flags |= MQTT_CONNECT_FLAG_PASSWORD; + } - return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); + return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_connack(mqtt_connection_t* connection, enum mqtt_connect_return_code retcode) -{ - init_message(connection); - connection->buffer[connection->message.length++] = 0; // Connect Acknowledge Flags - connection->buffer[connection->message.length++] = retcode; // Connect Return code - return fini_message(connection, MQTT_MSG_TYPE_CONNACK, 0, 0, 0); +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_connack(mqtt_connection_t * connection, + enum mqtt_connect_return_code retcode) { + init_message(connection); + connection->buffer[connection->message.length++] = 0; // Connect Acknowledge Flags + connection->buffer[connection->message.length++] = retcode; // Connect Return code + return fini_message(connection, MQTT_MSG_TYPE_CONNACK, 0, 0, 0); } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id) -{ - init_message(connection); +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_publish(mqtt_connection_t * connection, const char *topic, const char *data, + int data_length, int qos, int retain, uint16_t * message_id) { + init_message(connection); - if (topic == NULL || topic[0] == '\0') - return fail_message(connection); + if (topic == NULL || topic[0] == '\0') + return fail_message(connection); - if (append_string(connection, topic, os_strlen(topic)) < 0) - return fail_message(connection); + if (append_string(connection, topic, os_strlen(topic)) < 0) + return fail_message(connection); + + if (qos > 0) { + if ((*message_id = append_message_id(connection, 0)) == 0) + return fail_message(connection); + } else + *message_id = 0; + + if (connection->message.length + data_length > connection->buffer_length) + return fail_message(connection); + os_memcpy(connection->buffer + connection->message.length, data, data_length); + connection->message.length += data_length; + + return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); +} + +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_puback(mqtt_connection_t * connection, uint16_t message_id) { + init_message(connection); + if (append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); +} + +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pubrec(mqtt_connection_t * connection, uint16_t message_id) { + init_message(connection); + if (append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); +} + +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pubrel(mqtt_connection_t * connection, uint16_t message_id) { + init_message(connection); + if (append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); +} + +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pubcomp(mqtt_connection_t * connection, uint16_t message_id) { + init_message(connection); + if (append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); +} + +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_subscribe(mqtt_connection_t * connection, const char *topic, int qos, + uint16_t * message_id) { + init_message(connection); + + if (topic == NULL || topic[0] == '\0') + return fail_message(connection); - if (qos > 0) - { if ((*message_id = append_message_id(connection, 0)) == 0) - return fail_message(connection); - } - else - *message_id = 0; + return fail_message(connection); - if (connection->message.length + data_length > connection->buffer_length) - return fail_message(connection); - os_memcpy(connection->buffer + connection->message.length, data, data_length); - connection->message.length += data_length; + if (append_string(connection, topic, os_strlen(topic)) < 0) + return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); + if (connection->message.length + 1 > connection->buffer_length) + return fail_message(connection); + connection->buffer[connection->message.length++] = qos; + + return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id) -{ - init_message(connection); - if (append_message_id(connection, message_id) == 0) - return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_suback(mqtt_connection_t * connection, uint8_t * ret_codes, + uint8_t ret_codes_len, uint16_t message_id) { + uint8_t i; + + init_message(connection); + + if ((append_message_id(connection, message_id)) == 0) + return fail_message(connection); + + for (i = 0; i < ret_codes_len; i++) + connection->buffer[connection->message.length++] = ret_codes[i]; + + return fini_message(connection, MQTT_MSG_TYPE_SUBACK, 0, 0, 0); } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id) -{ - init_message(connection); - if (append_message_id(connection, message_id) == 0) - return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_unsubscribe(mqtt_connection_t * connection, const char *topic, + uint16_t * message_id) { + init_message(connection); + + if (topic == NULL || topic[0] == '\0') + return fail_message(connection); + + if ((*message_id = append_message_id(connection, 0)) == 0) + return fail_message(connection); + + if (append_string(connection, topic, os_strlen(topic)) < 0) + return fail_message(connection); + + return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id) -{ - init_message(connection); - if (append_message_id(connection, message_id) == 0) - return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_unsuback(mqtt_connection_t * connection, uint16_t message_id) { + uint8_t i; + + init_message(connection); + + if ((append_message_id(connection, message_id)) == 0) + return fail_message(connection); + + return fini_message(connection, MQTT_MSG_TYPE_UNSUBACK, 0, 0, 0); } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id) -{ - init_message(connection); - if (append_message_id(connection, message_id) == 0) - return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pingreq(mqtt_connection_t * connection) { + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0); } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id) -{ - init_message(connection); - - if (topic == NULL || topic[0] == '\0') - return fail_message(connection); - - if ((*message_id = append_message_id(connection, 0)) == 0) - return fail_message(connection); - - if (append_string(connection, topic, os_strlen(topic)) < 0) - return fail_message(connection); - - if (connection->message.length + 1 > connection->buffer_length) - return fail_message(connection); - connection->buffer[connection->message.length++] = qos; - - return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pingresp(mqtt_connection_t * connection) { + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0); } -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_suback(mqtt_connection_t* connection, uint8_t *ret_codes, uint8_t ret_codes_len, uint16_t message_id) -{ -uint8_t i; - - init_message(connection); - - if ((append_message_id(connection, message_id)) == 0) - return fail_message(connection); - - for (i = 0; i < ret_codes_len; i++) - connection->buffer[connection->message.length++] = ret_codes[i]; - - return fini_message(connection, MQTT_MSG_TYPE_SUBACK, 0, 0, 0); -} - -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id) -{ - init_message(connection); - - if (topic == NULL || topic[0] == '\0') - return fail_message(connection); - - if ((*message_id = append_message_id(connection, 0)) == 0) - return fail_message(connection); - - if (append_string(connection, topic, os_strlen(topic)) < 0) - return fail_message(connection); - - return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); -} - -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_unsuback(mqtt_connection_t* connection, uint16_t message_id) -{ -uint8_t i; - - init_message(connection); - - if ((append_message_id(connection, message_id)) == 0) - return fail_message(connection); - - return fini_message(connection, MQTT_MSG_TYPE_UNSUBACK, 0, 0, 0); -} - -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pingreq(mqtt_connection_t* connection) -{ - init_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0); -} - -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pingresp(mqtt_connection_t* connection) -{ - init_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0); -} - -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_disconnect(mqtt_connection_t* connection) -{ - init_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); +mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_disconnect(mqtt_connection_t * connection) { + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); } diff --git a/mqtt/mqtt_retainedlist.c b/mqtt/mqtt_retainedlist.c index fe47eed..017ddbd 100644 --- a/mqtt/mqtt_retainedlist.c +++ b/mqtt/mqtt_retainedlist.c @@ -12,111 +12,110 @@ static retained_entry *retained_list = NULL; static uint16_t max_entry; -bool ICACHE_FLASH_ATTR create_retainedlist(uint16_t num_entires) -{ - max_entry = num_entires; - retained_list = (retained_entry *)os_zalloc(num_entires * sizeof(retained_entry)); - return retained_list != NULL; +bool ICACHE_FLASH_ATTR create_retainedlist(uint16_t num_entires) { + max_entry = num_entires; + retained_list = (retained_entry *) os_zalloc(num_entires * sizeof(retained_entry)); + return retained_list != NULL; } -bool update_retainedtopic(uint8_t *topic, uint8_t *data, uint16_t data_len, uint8_t qos) -{ -uint16_t i; +bool update_retainedtopic(uint8_t * topic, uint8_t * data, uint16_t data_len, uint8_t qos) { + uint16_t i; - if (retained_list == NULL) return false; + if (retained_list == NULL) + return false; - // look for topic in list - for (i=0; i=max_entry) { - - // if empty new data - no entry required - if (data_len == 0) return true; - - // find free - for (i=0; i=max_entry) { - // list full - return false; + + // not yet in list + if (i >= max_entry) { + + // if empty new data - no entry required + if (data_len == 0) + return true; + + // find free + for (i = 0; i < max_entry; i++) { + if (retained_list[i].topic == NULL) + break; + } + if (i >= max_entry) { + // list full + return false; + } + retained_list[i].topic = (uint8_t *) os_malloc(os_strlen(topic) + 1); + if (retained_list[i].topic == NULL) { + // out of mem + return false; + } + os_strcpy(retained_list[i].topic, topic); } - retained_list[i].topic = (uint8_t *)os_malloc(os_strlen(topic)+1); - if (retained_list[i].topic == NULL) { - // out of mem - return false; + // if empty new data - delete + if (data_len == 0) { + os_free(retained_list[i].topic); + retained_list[i].topic = NULL; + os_free(retained_list[i].data); + retained_list[i].data = NULL; + retained_list[i].data_len = 0; + return true; } - os_strcpy(retained_list[i].topic, topic); - } - // if empty new data - delete - if (data_len == 0) { - os_free(retained_list[i].topic); - retained_list[i].topic = NULL; - os_free(retained_list[i].data); - retained_list[i].data = NULL; - retained_list[i].data_len = 0; - return true; - } - - if (retained_list[i].data == NULL) { - // no data till now, new memory allocation - retained_list[i].data = (uint8_t *)os_malloc(data_len); - } else { - if (data_len != retained_list[i].data_len) { - // not same size as before, new memory allocation - os_free(retained_list[i].data); - retained_list[i].data = (uint8_t *)os_malloc(data_len); + if (retained_list[i].data == NULL) { + // no data till now, new memory allocation + retained_list[i].data = (uint8_t *) os_malloc(data_len); + } else { + if (data_len != retained_list[i].data_len) { + // not same size as before, new memory allocation + os_free(retained_list[i].data); + retained_list[i].data = (uint8_t *) os_malloc(data_len); + } + } + if (retained_list[i].data == NULL) { + // out of mem + os_free(retained_list[i].topic); + retained_list[i].topic = NULL; + retained_list[i].data_len = 0; + return false; } - } - if (retained_list[i].data == NULL) { - // out of mem - os_free(retained_list[i].topic); - retained_list[i].topic = NULL; - retained_list[i].data_len = 0; - return false; - } - os_memcpy(retained_list[i].data, data, data_len); - retained_list[i].data_len = data_len; - retained_list[i].qos = qos; + os_memcpy(retained_list[i].data, data, data_len); + retained_list[i].data_len = data_len; + retained_list[i].qos = qos; - return true; + return true; } -bool ICACHE_FLASH_ATTR find_retainedtopic(uint8_t *topic, find_retainedtopic_cb cb, MQTT_ClientCon *clientcon) -{ -uint16_t i; -bool retval = false; +bool ICACHE_FLASH_ATTR find_retainedtopic(uint8_t * topic, find_retainedtopic_cb cb, MQTT_ClientCon * clientcon) { + uint16_t i; + bool retval = false; - if (retained_list == NULL) return false; + if (retained_list == NULL) + return false; - for (i=0; iclientcon == LOCAL_MQTT_CLIENT) { - MQTT_INFO("MQTT: Client: LOCAL Topic: \"%s\" QoS: %d\r\n", topic->topic, topic->qos); - } else { - MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", topic->clientcon->connect_info.client_id, topic->topic, topic->qos); - } - return false; +bool ICACHE_FLASH_ATTR print_topic(topic_entry * topic, void *user_data) { + if (topic->clientcon == LOCAL_MQTT_CLIENT) { + MQTT_INFO("MQTT: Client: LOCAL Topic: \"%s\" QoS: %d\r\n", topic->topic, topic->qos); + } else { + MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", topic->clientcon->connect_info.client_id, topic->topic, + topic->qos); + } + return false; } +bool ICACHE_FLASH_ATTR publish_topic(topic_entry * topic_e, uint8_t * topic, uint8_t * data, uint16_t data_len) { + MQTT_ClientCon *clientcon = topic_e->clientcon; + uint16_t message_id = 0; -bool ICACHE_FLASH_ATTR publish_topic(topic_entry *topic_e, uint8_t *topic, uint8_t *data, uint16_t data_len) -{ -MQTT_ClientCon *clientcon = topic_e->clientcon; -uint16_t message_id = 0; + if (topic_e->clientcon == LOCAL_MQTT_CLIENT) { + MQTT_INFO("MQTT: Client: LOCAL Topic: \"%s\" QoS: %d\r\n", topic_e->topic, topic_e->qos); + if (local_data_cb != NULL) + local_data_cb(NULL, topic, os_strlen(topic), data, data_len); + return true; + } - if (topic_e->clientcon == LOCAL_MQTT_CLIENT) { - MQTT_INFO("MQTT: Client: LOCAL Topic: \"%s\" QoS: %d\r\n", topic_e->topic, topic_e->qos); - if (local_data_cb != NULL) - local_data_cb(NULL, topic, os_strlen(topic), data, data_len); + MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", clientcon->connect_info.client_id, topic_e->topic, + topic_e->qos); + + clientcon->mqtt_state.outbound_message = + mqtt_msg_publish(&clientcon->mqtt_state.mqtt_connection, topic, data, data_len, topic_e->qos, 0, &message_id); + if (QUEUE_Puts + (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, + clientcon->mqtt_state.outbound_message->length) == -1) { + MQTT_ERROR("MQTT: Queue full\r\n"); + return false; + } return true; - } - - MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", clientcon->connect_info.client_id, topic_e->topic, topic_e->qos); - - clientcon->mqtt_state.outbound_message = - mqtt_msg_publish(&clientcon->mqtt_state.mqtt_connection, topic, data, data_len, topic_e->qos, 0, &message_id); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - return false; - } - return true; } +bool ICACHE_FLASH_ATTR publish_retainedtopic(retained_entry * entry, MQTT_ClientCon * clientcon) { + uint16_t message_id = 0; -bool ICACHE_FLASH_ATTR publish_retainedtopic(retained_entry *entry, MQTT_ClientCon *clientcon) -{ -uint16_t message_id = 0; + MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", clientcon->connect_info.client_id, entry->topic, + entry->qos); - MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", clientcon->connect_info.client_id, entry->topic, entry->qos); - - clientcon->mqtt_state.outbound_message = - mqtt_msg_publish(&clientcon->mqtt_state.mqtt_connection, entry->topic, entry->data, entry->data_len, entry->qos, 1, &message_id); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - return false; - } - return true; -} - - -bool ICACHE_FLASH_ATTR activate_next_client() -{ -MQTT_ClientCon *clientcon = clientcon_list; - - for (clientcon = clientcon_list; clientcon != NULL; clientcon = clientcon->next) { - if (!QUEUE_IsEmpty(&clientcon->msgQueue)) { - MQTT_INFO("MQTT: Next message to client: %s\r\n", clientcon->connect_info.client_id); - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t)clientcon); - return true; - } - } - return true; -} - - -bool ICACHE_FLASH_ATTR -MQTT_InitClientCon(MQTT_ClientCon *mqttClientCon) -{ - uint32_t temp; - MQTT_INFO("MQTT:InitClientCon\r\n"); - - mqttClientCon->connState = TCP_CONNECTED; - - os_memset(&mqttClientCon->connect_info, 0, sizeof(mqtt_connect_info_t)); - - mqttClientCon->connect_info.client_id = zero_len_id; - mqttClientCon->protocolVersion = 0; - - mqttClientCon->mqtt_state.in_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE); - mqttClientCon->mqtt_state.in_buffer_length = MQTT_BUF_SIZE; - mqttClientCon->mqtt_state.out_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE); - mqttClientCon->mqtt_state.out_buffer_length = MQTT_BUF_SIZE; - mqttClientCon->mqtt_state.connect_info = &mqttClientCon->connect_info; - - mqtt_msg_init(&mqttClientCon->mqtt_state.mqtt_connection, mqttClientCon->mqtt_state.out_buffer, mqttClientCon->mqtt_state.out_buffer_length); - - QUEUE_Init(&mqttClientCon->msgQueue, QUEUE_BUFFER_SIZE); - - mqttClientCon->next = clientcon_list; - clientcon_list = mqttClientCon; - - return true; -} - - -bool ICACHE_FLASH_ATTR -MQTT_DeleteClientCon(MQTT_ClientCon *mqttClientCon) -{ - MQTT_INFO("MQTT:DeleteClientCon\r\n"); - - if (mqttClientCon == NULL) - return; - - os_timer_disarm(&mqttClientCon->mqttTimer); - - if (mqttClientCon->pCon != NULL) { - espconn_delete(mqttClientCon->pCon); - } - - MQTT_ClientCon **p = &clientcon_list; - while (*p != mqttClientCon && *p != NULL) { - p=&((*p)->next); - } - if (*p == mqttClientCon) - *p = (*p)->next; - - if (mqttClientCon->user_data != NULL) { - os_free(mqttClientCon->user_data); - mqttClientCon->user_data = NULL; - } - - if (mqttClientCon->mqtt_state.in_buffer != NULL) { - os_free(mqttClientCon->mqtt_state.in_buffer); - mqttClientCon->mqtt_state.in_buffer = NULL; - } - - if (mqttClientCon->mqtt_state.out_buffer != NULL) { - os_free(mqttClientCon->mqtt_state.out_buffer); - mqttClientCon->mqtt_state.out_buffer = NULL; - } - - if (mqttClientCon->mqtt_state.outbound_message != NULL) { - if (mqttClientCon->mqtt_state.outbound_message->data != NULL) - { - os_free(mqttClientCon->mqtt_state.outbound_message->data); - mqttClientCon->mqtt_state.outbound_message->data = NULL; + clientcon->mqtt_state.outbound_message = + mqtt_msg_publish(&clientcon->mqtt_state.mqtt_connection, entry->topic, entry->data, entry->data_len, entry->qos, + 1, &message_id); + if (QUEUE_Puts + (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, + clientcon->mqtt_state.outbound_message->length) == -1) { + MQTT_ERROR("MQTT: Queue full\r\n"); + return false; } - } + return true; +} - if (mqttClientCon->mqtt_state.mqtt_connection.buffer != NULL) { - // Already freed but not NULL - mqttClientCon->mqtt_state.mqtt_connection.buffer = NULL; - } +bool ICACHE_FLASH_ATTR activate_next_client() { + MQTT_ClientCon *clientcon = clientcon_list; - if (mqttClientCon->connect_info.client_id != NULL) { - /* Don't attempt to free if it's the zero_len array */ - if ( ((uint8_t*)mqttClientCon->connect_info.client_id) != zero_len_id ) - os_free(mqttClientCon->connect_info.client_id); - mqttClientCon->connect_info.client_id = NULL; - } + for (clientcon = clientcon_list; clientcon != NULL; clientcon = clientcon->next) { + if (!QUEUE_IsEmpty(&clientcon->msgQueue)) { + MQTT_INFO("MQTT: Next message to client: %s\r\n", clientcon->connect_info.client_id); + system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) clientcon); + return true; + } + } + return true; +} - if (mqttClientCon->connect_info.username != NULL) { - os_free(mqttClientCon->connect_info.username); - mqttClientCon->connect_info.username = NULL; - } +bool ICACHE_FLASH_ATTR MQTT_InitClientCon(MQTT_ClientCon * mqttClientCon) { + uint32_t temp; + MQTT_INFO("MQTT:InitClientCon\r\n"); - if (mqttClientCon->connect_info.password != NULL) { - os_free(mqttClientCon->connect_info.password); - mqttClientCon->connect_info.password = NULL; - } + mqttClientCon->connState = TCP_CONNECTED; - if (mqttClientCon->connect_info.will_topic != NULL) { - // Publish the LWT - find_topic(mqttClientCon->connect_info.will_topic, publish_topic, - mqttClientCon->connect_info.will_data, mqttClientCon->connect_info.will_data_len); - activate_next_client(); + os_memset(&mqttClientCon->connect_info, 0, sizeof(mqtt_connect_info_t)); - if (mqttClientCon->connect_info.will_retain) { - update_retainedtopic(mqttClientCon->connect_info.will_topic, mqttClientCon->connect_info.will_data, - mqttClientCon->connect_info.will_data_len, mqttClientCon->connect_info.will_qos); + mqttClientCon->connect_info.client_id = zero_len_id; + mqttClientCon->protocolVersion = 0; + + mqttClientCon->mqtt_state.in_buffer = (uint8_t *) os_zalloc(MQTT_BUF_SIZE); + mqttClientCon->mqtt_state.in_buffer_length = MQTT_BUF_SIZE; + mqttClientCon->mqtt_state.out_buffer = (uint8_t *) os_zalloc(MQTT_BUF_SIZE); + mqttClientCon->mqtt_state.out_buffer_length = MQTT_BUF_SIZE; + mqttClientCon->mqtt_state.connect_info = &mqttClientCon->connect_info; + + mqtt_msg_init(&mqttClientCon->mqtt_state.mqtt_connection, mqttClientCon->mqtt_state.out_buffer, + mqttClientCon->mqtt_state.out_buffer_length); + + QUEUE_Init(&mqttClientCon->msgQueue, QUEUE_BUFFER_SIZE); + + mqttClientCon->next = clientcon_list; + clientcon_list = mqttClientCon; + + return true; +} + +bool ICACHE_FLASH_ATTR MQTT_DeleteClientCon(MQTT_ClientCon * mqttClientCon) { + MQTT_INFO("MQTT:DeleteClientCon\r\n"); + + if (mqttClientCon == NULL) + return; + + os_timer_disarm(&mqttClientCon->mqttTimer); + + if (mqttClientCon->pCon != NULL) { + espconn_delete(mqttClientCon->pCon); } - os_free(mqttClientCon->connect_info.will_topic); - mqttClientCon->connect_info.will_topic = NULL; - } + MQTT_ClientCon **p = &clientcon_list; + while (*p != mqttClientCon && *p != NULL) { + p = &((*p)->next); + } + if (*p == mqttClientCon) + *p = (*p)->next; - if (mqttClientCon->connect_info.will_data != NULL) { - os_free(mqttClientCon->connect_info.will_data); - mqttClientCon->connect_info.will_data = NULL; - } + if (mqttClientCon->user_data != NULL) { + os_free(mqttClientCon->user_data); + mqttClientCon->user_data = NULL; + } - if (mqttClientCon->msgQueue.buf != NULL) { - os_free(mqttClientCon->msgQueue.buf); - mqttClientCon->msgQueue.buf = NULL; - } + if (mqttClientCon->mqtt_state.in_buffer != NULL) { + os_free(mqttClientCon->mqtt_state.in_buffer); + mqttClientCon->mqtt_state.in_buffer = NULL; + } - delete_topic(mqttClientCon, 0); + if (mqttClientCon->mqtt_state.out_buffer != NULL) { + os_free(mqttClientCon->mqtt_state.out_buffer); + mqttClientCon->mqtt_state.out_buffer = NULL; + } - os_free(mqttClientCon); + if (mqttClientCon->mqtt_state.outbound_message != NULL) { + if (mqttClientCon->mqtt_state.outbound_message->data != NULL) { + os_free(mqttClientCon->mqtt_state.outbound_message->data); + mqttClientCon->mqtt_state.outbound_message->data = NULL; + } + } - return true; + if (mqttClientCon->mqtt_state.mqtt_connection.buffer != NULL) { + // Already freed but not NULL + mqttClientCon->mqtt_state.mqtt_connection.buffer = NULL; + } + + if (mqttClientCon->connect_info.client_id != NULL) { + /* Don't attempt to free if it's the zero_len array */ + if (((uint8_t *) mqttClientCon->connect_info.client_id) != zero_len_id) + os_free(mqttClientCon->connect_info.client_id); + mqttClientCon->connect_info.client_id = NULL; + } + + if (mqttClientCon->connect_info.username != NULL) { + os_free(mqttClientCon->connect_info.username); + mqttClientCon->connect_info.username = NULL; + } + + if (mqttClientCon->connect_info.password != NULL) { + os_free(mqttClientCon->connect_info.password); + mqttClientCon->connect_info.password = NULL; + } + + if (mqttClientCon->connect_info.will_topic != NULL) { + // Publish the LWT + find_topic(mqttClientCon->connect_info.will_topic, publish_topic, + mqttClientCon->connect_info.will_data, mqttClientCon->connect_info.will_data_len); + activate_next_client(); + + if (mqttClientCon->connect_info.will_retain) { + update_retainedtopic(mqttClientCon->connect_info.will_topic, mqttClientCon->connect_info.will_data, + mqttClientCon->connect_info.will_data_len, mqttClientCon->connect_info.will_qos); + } + + os_free(mqttClientCon->connect_info.will_topic); + mqttClientCon->connect_info.will_topic = NULL; + } + + if (mqttClientCon->connect_info.will_data != NULL) { + os_free(mqttClientCon->connect_info.will_data); + mqttClientCon->connect_info.will_data = NULL; + } + + if (mqttClientCon->msgQueue.buf != NULL) { + os_free(mqttClientCon->msgQueue.buf); + mqttClientCon->msgQueue.buf = NULL; + } + + delete_topic(mqttClientCon, 0); + + os_free(mqttClientCon); + + return true; } +void ICACHE_FLASH_ATTR MQTT_ServerDisconnect(MQTT_ClientCon * mqttClientCon) { + MQTT_INFO("MQTT:ServerDisconnect\r\n"); -void ICACHE_FLASH_ATTR -MQTT_ServerDisconnect(MQTT_ClientCon *mqttClientCon) -{ - MQTT_INFO("MQTT:ServerDisconnect\r\n"); - - mqttClientCon->mqtt_state.message_length_read = 0; - mqttClientCon->connState = TCP_DISCONNECTED; - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t)mqttClientCon); - os_timer_disarm(&mqttClientCon->mqttTimer); + mqttClientCon->mqtt_state.message_length_read = 0; + mqttClientCon->connState = TCP_DISCONNECTED; + system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) mqttClientCon); + os_timer_disarm(&mqttClientCon->mqttTimer); } +void ICACHE_FLASH_ATTR mqtt_server_timer(void *arg) { + MQTT_ClientCon *clientcon = (MQTT_ClientCon *) arg; -void ICACHE_FLASH_ATTR mqtt_server_timer(void *arg) -{ - MQTT_ClientCon *clientcon = (MQTT_ClientCon*)arg; - - if (clientcon->sendTimeout > 0) - clientcon->sendTimeout--; + if (clientcon->sendTimeout > 0) + clientcon->sendTimeout--; } +static void ICACHE_FLASH_ATTR MQTT_ClientCon_recv_cb(void *arg, char *pdata, unsigned short len) { + uint8_t msg_type; + uint8_t msg_qos; + uint16_t msg_id; + enum mqtt_connect_flag msg_conn_ret; + uint16_t topic_index; + uint16_t topic_len; + uint8_t *topic_str; + uint8_t topic_buffer[MQTT_BUF_SIZE]; + uint16_t data_len; + uint8_t *data; -static void ICACHE_FLASH_ATTR MQTT_ClientCon_recv_cb(void *arg, char *pdata, unsigned short len) -{ - uint8_t msg_type; - uint8_t msg_qos; - uint16_t msg_id; - enum mqtt_connect_flag msg_conn_ret; - uint16_t topic_index; - uint16_t topic_len; - uint8_t *topic_str; - uint8_t topic_buffer[MQTT_BUF_SIZE]; - uint16_t data_len; - uint8_t *data; + struct espconn *pCon = (struct espconn *)arg; - struct espconn *pCon = (struct espconn *)arg; + MQTT_INFO("MQTT_ClientCon_recv_cb(): %d bytes of data received\n", len); - MQTT_INFO("MQTT_ClientCon_recv_cb(): %d bytes of data received\n", len); + MQTT_ClientCon *clientcon = (MQTT_ClientCon *) pCon->reverse; + if (clientcon == NULL) { + MQTT_ERROR("ERROR: No client status\r\n"); + return; + } - MQTT_ClientCon *clientcon = (MQTT_ClientCon *)pCon->reverse; - if (clientcon == NULL) { - MQTT_ERROR("ERROR: No client status\r\n"); - return; - } + MQTT_INFO("MQTT: TCP: data received %d bytes (State: %d)\r\n", len, clientcon->connState); - MQTT_INFO("MQTT: TCP: data received %d bytes (State: %d)\r\n", len, clientcon->connState); - - // Expect minimum the full fixed size header - if (len+clientcon->mqtt_state.message_length_read > MQTT_BUF_SIZE || len < 2) { - MQTT_ERROR("MQTT: Message too short/long\r\n"); - clientcon->mqtt_state.message_length_read = 0; - return; - } -READPACKET: - os_memcpy(&clientcon->mqtt_state.in_buffer[clientcon->mqtt_state.message_length_read], pdata, len); - clientcon->mqtt_state.message_length_read += len; + // Expect minimum the full fixed size header + if (len + clientcon->mqtt_state.message_length_read > MQTT_BUF_SIZE || len < 2) { + MQTT_ERROR("MQTT: Message too short/long\r\n"); + clientcon->mqtt_state.message_length_read = 0; + return; + } + READPACKET: + os_memcpy(&clientcon->mqtt_state.in_buffer[clientcon->mqtt_state.message_length_read], pdata, len); + clientcon->mqtt_state.message_length_read += len; - clientcon->mqtt_state.message_length = - mqtt_get_total_length(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.message_length_read); - MQTT_INFO("MQTT: total_len: %d\r\n", clientcon->mqtt_state.message_length); - if (clientcon->mqtt_state.message_length > clientcon->mqtt_state.message_length_read) { - MQTT_WARNING("MQTT: Partial message received\r\n"); - return; - } + clientcon->mqtt_state.message_length = + mqtt_get_total_length(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.message_length_read); + MQTT_INFO("MQTT: total_len: %d\r\n", clientcon->mqtt_state.message_length); + if (clientcon->mqtt_state.message_length > clientcon->mqtt_state.message_length_read) { + MQTT_WARNING("MQTT: Partial message received\r\n"); + return; + } - msg_type = mqtt_get_type(clientcon->mqtt_state.in_buffer); - MQTT_INFO("MQTT: message_type: %d\r\n", msg_type); - //msg_qos = mqtt_get_qos(clientcon->mqtt_state.in_buffer); - switch (clientcon->connState) { + msg_type = mqtt_get_type(clientcon->mqtt_state.in_buffer); + MQTT_INFO("MQTT: message_type: %d\r\n", msg_type); + //msg_qos = mqtt_get_qos(clientcon->mqtt_state.in_buffer); + switch (clientcon->connState) { case TCP_CONNECTED: - switch (msg_type) - { - case MQTT_MSG_TYPE_CONNECT: + switch (msg_type) { + case MQTT_MSG_TYPE_CONNECT: - MQTT_INFO("MQTT: Connect received, message_len: %d\r\n", clientcon->mqtt_state.message_length); + MQTT_INFO("MQTT: Connect received, message_len: %d\r\n", clientcon->mqtt_state.message_length); - if (clientcon->mqtt_state.message_length < sizeof(struct mqtt_connect_variable_header)+3) { - MQTT_ERROR("MQTT: Too short Connect message\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } + if (clientcon->mqtt_state.message_length < sizeof(struct mqtt_connect_variable_header) + 3) { + MQTT_ERROR("MQTT: Too short Connect message\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } - struct mqtt_connect_variable_header4* variable_header = - (struct mqtt_connect_variable_header4*)&clientcon->mqtt_state.in_buffer[2]; - uint16_t var_header_len = sizeof(struct mqtt_connect_variable_header4); + struct mqtt_connect_variable_header4 *variable_header = + (struct mqtt_connect_variable_header4 *)&clientcon->mqtt_state.in_buffer[2]; + uint16_t var_header_len = sizeof(struct mqtt_connect_variable_header4); - // We check MQTT v3.11 (version 4) - if ((variable_header->lengthMsb<<8) + variable_header->lengthLsb == 4 && - variable_header->version == 4 && - os_strncmp(variable_header->magic, "MQTT", 4) == 0) { - clientcon->protocolVersion = 4; - } else { - struct mqtt_connect_variable_header3* variable_header3 = - (struct mqtt_connect_variable_header3*)&clientcon->mqtt_state.in_buffer[2]; - var_header_len = sizeof(struct mqtt_connect_variable_header3); + // We check MQTT v3.11 (version 4) + if ((variable_header->lengthMsb << 8) + variable_header->lengthLsb == 4 && + variable_header->version == 4 && os_strncmp(variable_header->magic, "MQTT", 4) == 0) { + clientcon->protocolVersion = 4; + } else { + struct mqtt_connect_variable_header3 *variable_header3 = + (struct mqtt_connect_variable_header3 *)&clientcon->mqtt_state.in_buffer[2]; + var_header_len = sizeof(struct mqtt_connect_variable_header3); - // We check MQTT v3.1 (version 3) - if ((variable_header3->lengthMsb<<8) + variable_header3->lengthLsb == 6 && - variable_header3->version == 3 && - os_strncmp(variable_header3->magic, "MQIsdp", 6) == 0) { - clientcon->protocolVersion = 3; - // adapt the remaining header fields (dirty as we overlay the two structs of different length) - variable_header->version = variable_header3->version; - variable_header->flags = variable_header3->flags; - variable_header->keepaliveMsb = variable_header3->keepaliveMsb; - variable_header->keepaliveLsb = variable_header3->keepaliveLsb; - } else { - // Neither found - MQTT_WARNING("MQTT: Wrong protocoll version\r\n"); - msg_conn_ret = CONNECTION_REFUSE_PROTOCOL; - clientcon->connState = TCP_DISCONNECTING; - break; - } - } + // We check MQTT v3.1 (version 3) + if ((variable_header3->lengthMsb << 8) + variable_header3->lengthLsb == 6 && + variable_header3->version == 3 && os_strncmp(variable_header3->magic, "MQIsdp", 6) == 0) { + clientcon->protocolVersion = 3; + // adapt the remaining header fields (dirty as we overlay the two structs of different length) + variable_header->version = variable_header3->version; + variable_header->flags = variable_header3->flags; + variable_header->keepaliveMsb = variable_header3->keepaliveMsb; + variable_header->keepaliveLsb = variable_header3->keepaliveLsb; + } else { + // Neither found + MQTT_WARNING("MQTT: Wrong protocoll version\r\n"); + msg_conn_ret = CONNECTION_REFUSE_PROTOCOL; + clientcon->connState = TCP_DISCONNECTING; + break; + } + } - MQTT_INFO("MQTT: Connect flags %x\r\n", variable_header->flags); - clientcon->connect_info.clean_session = (variable_header->flags & MQTT_CONNECT_FLAG_CLEAN_SESSION) != 0; - if ((variable_header->flags & MQTT_CONNECT_FLAG_USERNAME) != 0 || - (variable_header->flags & MQTT_CONNECT_FLAG_PASSWORD) != 0) { - MQTT_WARNING("MQTT: Connect option currently not supported\r\n"); - msg_conn_ret = CONNECTION_REFUSE_NOT_AUTHORIZED; - clientcon->connState = TCP_DISCONNECTING; - break; - } - clientcon->connect_info.keepalive = (variable_header->keepaliveMsb<<8) + variable_header->keepaliveLsb; - espconn_regist_time(clientcon->pCon, 2*clientcon->connect_info.keepalive, 1); - MQTT_INFO("MQTT: Keepalive %d\r\n", clientcon->connect_info.keepalive); + MQTT_INFO("MQTT: Connect flags %x\r\n", variable_header->flags); + clientcon->connect_info.clean_session = (variable_header->flags & MQTT_CONNECT_FLAG_CLEAN_SESSION) != 0; + if ((variable_header->flags & MQTT_CONNECT_FLAG_USERNAME) != 0 || + (variable_header->flags & MQTT_CONNECT_FLAG_PASSWORD) != 0) { + MQTT_WARNING("MQTT: Connect option currently not supported\r\n"); + msg_conn_ret = CONNECTION_REFUSE_NOT_AUTHORIZED; + clientcon->connState = TCP_DISCONNECTING; + break; + } + clientcon->connect_info.keepalive = (variable_header->keepaliveMsb << 8) + variable_header->keepaliveLsb; + espconn_regist_time(clientcon->pCon, 2 * clientcon->connect_info.keepalive, 1); + MQTT_INFO("MQTT: Keepalive %d\r\n", clientcon->connect_info.keepalive); - // Get the client id - uint16_t id_len = clientcon->mqtt_state.message_length - (2+var_header_len); - const char *client_id = mqtt_get_str(&clientcon->mqtt_state.in_buffer[2+var_header_len], &id_len); - if (client_id == NULL || id_len > 80) { - MQTT_WARNING("MQTT: Client Id invalid\r\n"); - msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; - clientcon->connState = TCP_DISCONNECTING; - break; - } - if (id_len == 0) { - if (clientcon->protocolVersion == 3) { - MQTT_WARNING("MQTT: Empty client Id in MQTT 3.1\r\n"); - msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; - clientcon->connState = TCP_DISCONNECTING; - break; - } - if (!clientcon->connect_info.clean_session) { - MQTT_WARNING("MQTT: Null client Id and NOT cleansession\r\n"); - msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; - clientcon->connState = TCP_DISCONNECTING; - break; - } - clientcon->connect_info.client_id = zero_len_id; - } else { - clientcon->connect_info.client_id = (char *) os_zalloc(id_len+1); - if (clientcon->connect_info.client_id != NULL) { - os_memcpy(clientcon->connect_info.client_id, client_id, id_len); - clientcon->connect_info.client_id[id_len]=0; - MQTT_INFO("MQTT: Client id %s\r\n", clientcon->connect_info.client_id); - } else { - MQTT_ERROR("MQTT: Out of mem\r\n"); - msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; - clientcon->connState = TCP_DISCONNECTING; - break; - } - } + // Get the client id + uint16_t id_len = clientcon->mqtt_state.message_length - (2 + var_header_len); + const char *client_id = mqtt_get_str(&clientcon->mqtt_state.in_buffer[2 + var_header_len], &id_len); + if (client_id == NULL || id_len > 80) { + MQTT_WARNING("MQTT: Client Id invalid\r\n"); + msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; + clientcon->connState = TCP_DISCONNECTING; + break; + } + if (id_len == 0) { + if (clientcon->protocolVersion == 3) { + MQTT_WARNING("MQTT: Empty client Id in MQTT 3.1\r\n"); + msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; + clientcon->connState = TCP_DISCONNECTING; + break; + } + if (!clientcon->connect_info.clean_session) { + MQTT_WARNING("MQTT: Null client Id and NOT cleansession\r\n"); + msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; + clientcon->connState = TCP_DISCONNECTING; + break; + } + clientcon->connect_info.client_id = zero_len_id; + } else { + clientcon->connect_info.client_id = (char *)os_zalloc(id_len + 1); + if (clientcon->connect_info.client_id != NULL) { + os_memcpy(clientcon->connect_info.client_id, client_id, id_len); + clientcon->connect_info.client_id[id_len] = 0; + MQTT_INFO("MQTT: Client id %s\r\n", clientcon->connect_info.client_id); + } else { + MQTT_ERROR("MQTT: Out of mem\r\n"); + msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; + clientcon->connState = TCP_DISCONNECTING; + break; + } + } - // Get the LWT - clientcon->connect_info.will_retain = (variable_header->flags & MQTT_CONNECT_FLAG_WILL_RETAIN) != 0; - clientcon->connect_info.will_qos = (variable_header->flags & 0x18)>>3; - if (!(variable_header->flags & MQTT_CONNECT_FLAG_WILL)) { - // Must be all 0 if no lwt is given - if (clientcon->connect_info.will_retain || clientcon->connect_info.will_qos) { - MQTT_WARNING("MQTT: Last Will flags invalid\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } - } else { - uint16_t lw_topic_len = clientcon->mqtt_state.message_length - (4+var_header_len+id_len); - const char *lw_topic = mqtt_get_str(&clientcon->mqtt_state.in_buffer[4+var_header_len+id_len], &lw_topic_len); + // Get the LWT + clientcon->connect_info.will_retain = (variable_header->flags & MQTT_CONNECT_FLAG_WILL_RETAIN) != 0; + clientcon->connect_info.will_qos = (variable_header->flags & 0x18) >> 3; + if (!(variable_header->flags & MQTT_CONNECT_FLAG_WILL)) { + // Must be all 0 if no lwt is given + if (clientcon->connect_info.will_retain || clientcon->connect_info.will_qos) { + MQTT_WARNING("MQTT: Last Will flags invalid\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } + } else { + uint16_t lw_topic_len = clientcon->mqtt_state.message_length - (4 + var_header_len + id_len); + const char *lw_topic = + mqtt_get_str(&clientcon->mqtt_state.in_buffer[4 + var_header_len + id_len], &lw_topic_len); - if (lw_topic == NULL) { - MQTT_WARNING("MQTT: Last Will topic invalid\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } + if (lw_topic == NULL) { + MQTT_WARNING("MQTT: Last Will topic invalid\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } - clientcon->connect_info.will_topic = (char *) os_zalloc(lw_topic_len+1); - if (clientcon->connect_info.will_topic != NULL) { - os_memcpy(clientcon->connect_info.will_topic, lw_topic, lw_topic_len); - clientcon->connect_info.will_topic[lw_topic_len]=0; - if (Topics_hasWildcards(clientcon->connect_info.will_topic)) { - MQTT_WARNING("MQTT: Last Will topic has wildcards\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } - MQTT_INFO("MQTT: LWT topic %s\r\n", clientcon->connect_info.will_topic); - } else { - MQTT_ERROR("MQTT: Out of mem\r\n"); - msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; - clientcon->connState = TCP_DISCONNECTING; - break; - } + clientcon->connect_info.will_topic = (char *)os_zalloc(lw_topic_len + 1); + if (clientcon->connect_info.will_topic != NULL) { + os_memcpy(clientcon->connect_info.will_topic, lw_topic, lw_topic_len); + clientcon->connect_info.will_topic[lw_topic_len] = 0; + if (Topics_hasWildcards(clientcon->connect_info.will_topic)) { + MQTT_WARNING("MQTT: Last Will topic has wildcards\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } + MQTT_INFO("MQTT: LWT topic %s\r\n", clientcon->connect_info.will_topic); + } else { + MQTT_ERROR("MQTT: Out of mem\r\n"); + msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; + clientcon->connState = TCP_DISCONNECTING; + break; + } - uint16_t lw_data_len = clientcon->mqtt_state.message_length - (6+var_header_len+id_len+lw_topic_len); - const char *lw_data = mqtt_get_str(&clientcon->mqtt_state.in_buffer[6+var_header_len+id_len+lw_topic_len], &lw_data_len); + uint16_t lw_data_len = + clientcon->mqtt_state.message_length - (6 + var_header_len + id_len + lw_topic_len); + const char *lw_data = + mqtt_get_str(&clientcon->mqtt_state.in_buffer[6 + var_header_len + id_len + lw_topic_len], + &lw_data_len); - if (lw_data == NULL) { - MQTT_WARNING("MQTT: Last Will data invalid\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } + if (lw_data == NULL) { + MQTT_WARNING("MQTT: Last Will data invalid\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } - clientcon->connect_info.will_data = (char *) os_zalloc(lw_data_len); - clientcon->connect_info.will_data_len = lw_data_len; - if (clientcon->connect_info.will_data != NULL) { - os_memcpy(clientcon->connect_info.will_data, lw_data, lw_data_len); - MQTT_INFO("MQTT: %d bytes of LWT data\r\n", clientcon->connect_info.will_data_len); - } else { - MQTT_ERROR("MQTT: Out of mem\r\n"); - msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; - clientcon->connState = TCP_DISCONNECTING; - break; - } - } + clientcon->connect_info.will_data = (char *)os_zalloc(lw_data_len); + clientcon->connect_info.will_data_len = lw_data_len; + if (clientcon->connect_info.will_data != NULL) { + os_memcpy(clientcon->connect_info.will_data, lw_data, lw_data_len); + MQTT_INFO("MQTT: %d bytes of LWT data\r\n", clientcon->connect_info.will_data_len); + } else { + MQTT_ERROR("MQTT: Out of mem\r\n"); + msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; + clientcon->connState = TCP_DISCONNECTING; + break; + } + } + msg_conn_ret = CONNECTION_ACCEPTED; + clientcon->connState = MQTT_DATA; + break; - msg_conn_ret = CONNECTION_ACCEPTED; - clientcon->connState = MQTT_DATA; - break; + default: + MQTT_WARNING("MQTT: Invalid message\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } + clientcon->mqtt_state.outbound_message = mqtt_msg_connack(&clientcon->mqtt_state.mqtt_connection, msg_conn_ret); + if (QUEUE_Puts + (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, + clientcon->mqtt_state.outbound_message->length) == -1) { + MQTT_ERROR("MQTT: Queue full\r\n"); + } - default: - MQTT_WARNING("MQTT: Invalid message\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } - clientcon->mqtt_state.outbound_message = mqtt_msg_connack(&clientcon->mqtt_state.mqtt_connection, msg_conn_ret); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - - break; + break; case MQTT_DATA: - switch (msg_type) - { - uint8_t ret_codes[MAX_SUBS_PER_REQ]; - uint8_t num_subs; - - case MQTT_MSG_TYPE_SUBSCRIBE: - MQTT_INFO("MQTT: Subscribe received, message_len: %d\r\n", clientcon->mqtt_state.message_length); - // 2B fixed header + 2B variable header + 2 len + 1 char + 1 QoS - if (clientcon->mqtt_state.message_length < 8) { - MQTT_ERROR("MQTT: Too short Subscribe message\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } - msg_id = mqtt_get_id(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.in_buffer_length); - MQTT_INFO("MQTT: Message id %d\r\n", msg_id); - topic_index = 4; - num_subs = 0; - while (topic_index < clientcon->mqtt_state.message_length && num_subs < MAX_SUBS_PER_REQ) { - topic_len = clientcon->mqtt_state.message_length - topic_index; - topic_str = mqtt_get_str(&clientcon->mqtt_state.in_buffer[topic_index], &topic_len); - if (topic_str == NULL) { - MQTT_WARNING("MQTT: Subscribe topic invalid\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } - topic_index += 2 + topic_len; + switch (msg_type) { + uint8_t ret_codes[MAX_SUBS_PER_REQ]; + uint8_t num_subs; - if (topic_index >= clientcon->mqtt_state.message_length) { - MQTT_WARNING("MQTT: Subscribe QoS missing\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } - uint8_t topic_QoS = clientcon->mqtt_state.in_buffer[topic_index++]; + case MQTT_MSG_TYPE_SUBSCRIBE: + MQTT_INFO("MQTT: Subscribe received, message_len: %d\r\n", clientcon->mqtt_state.message_length); + // 2B fixed header + 2B variable header + 2 len + 1 char + 1 QoS + if (clientcon->mqtt_state.message_length < 8) { + MQTT_ERROR("MQTT: Too short Subscribe message\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } + msg_id = mqtt_get_id(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.in_buffer_length); + MQTT_INFO("MQTT: Message id %d\r\n", msg_id); + topic_index = 4; + num_subs = 0; + while (topic_index < clientcon->mqtt_state.message_length && num_subs < MAX_SUBS_PER_REQ) { + topic_len = clientcon->mqtt_state.message_length - topic_index; + topic_str = mqtt_get_str(&clientcon->mqtt_state.in_buffer[topic_index], &topic_len); + if (topic_str == NULL) { + MQTT_WARNING("MQTT: Subscribe topic invalid\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } + topic_index += 2 + topic_len; - os_memcpy(topic_buffer, topic_str, topic_len); - topic_buffer[topic_len] = 0; - MQTT_INFO("MQTT: Subscribed topic %s QoS %d\r\n", topic_buffer, topic_QoS); + if (topic_index >= clientcon->mqtt_state.message_length) { + MQTT_WARNING("MQTT: Subscribe QoS missing\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } + uint8_t topic_QoS = clientcon->mqtt_state.in_buffer[topic_index++]; - // the return codes, one per topic - // For now we always give back error or QoS = 0 !! - ret_codes[num_subs++] = add_topic(clientcon, topic_buffer, 0)?0:0x80; - //iterate_topics(print_topic, 0); - } - MQTT_INFO("MQTT: Subscribe successful\r\n"); + os_memcpy(topic_buffer, topic_str, topic_len); + topic_buffer[topic_len] = 0; + MQTT_INFO("MQTT: Subscribed topic %s QoS %d\r\n", topic_buffer, topic_QoS); - clientcon->mqtt_state.outbound_message = mqtt_msg_suback(&clientcon->mqtt_state.mqtt_connection, ret_codes, num_subs, msg_id); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } + // the return codes, one per topic + // For now we always give back error or QoS = 0 !! + ret_codes[num_subs++] = add_topic(clientcon, topic_buffer, 0) ? 0 : 0x80; + //iterate_topics(print_topic, 0); + } + MQTT_INFO("MQTT: Subscribe successful\r\n"); - find_retainedtopic(topic_buffer, publish_retainedtopic, clientcon); + clientcon->mqtt_state.outbound_message = + mqtt_msg_suback(&clientcon->mqtt_state.mqtt_connection, ret_codes, num_subs, msg_id); + if (QUEUE_Puts + (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, + clientcon->mqtt_state.outbound_message->length) == -1) { + MQTT_ERROR("MQTT: Queue full\r\n"); + } - break; + find_retainedtopic(topic_buffer, publish_retainedtopic, clientcon); - case MQTT_MSG_TYPE_UNSUBSCRIBE: - MQTT_INFO("MQTT: Unsubscribe received, message_len: %d\r\n", clientcon->mqtt_state.message_length); - // 2B fixed header + 2B variable header + 2 len + 1 char - if (clientcon->mqtt_state.message_length < 7) { - MQTT_ERROR("MQTT: Too short Unsubscribe message\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } - msg_id = mqtt_get_id(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.in_buffer_length); - MQTT_INFO("MQTT: Message id %d\r\n", msg_id); - topic_index = 4; - while (topic_index < clientcon->mqtt_state.message_length) { - uint16_t topic_len = clientcon->mqtt_state.message_length - topic_index; - char *topic_str = mqtt_get_str(&clientcon->mqtt_state.in_buffer[topic_index], &topic_len); - if (topic_str == NULL) { - MQTT_WARNING("MQTT: Subscribe topic invalid\r\n"); - MQTT_ServerDisconnect(clientcon); - return; - } - topic_index += 2 + topic_len; + break; - os_memcpy(topic_buffer, topic_str, topic_len); - topic_buffer[topic_len] = 0; - MQTT_INFO("MQTT: Unsubscribed topic %s\r\n", topic_buffer); + case MQTT_MSG_TYPE_UNSUBSCRIBE: + MQTT_INFO("MQTT: Unsubscribe received, message_len: %d\r\n", clientcon->mqtt_state.message_length); + // 2B fixed header + 2B variable header + 2 len + 1 char + if (clientcon->mqtt_state.message_length < 7) { + MQTT_ERROR("MQTT: Too short Unsubscribe message\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } + msg_id = mqtt_get_id(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.in_buffer_length); + MQTT_INFO("MQTT: Message id %d\r\n", msg_id); + topic_index = 4; + while (topic_index < clientcon->mqtt_state.message_length) { + uint16_t topic_len = clientcon->mqtt_state.message_length - topic_index; + char *topic_str = mqtt_get_str(&clientcon->mqtt_state.in_buffer[topic_index], &topic_len); + if (topic_str == NULL) { + MQTT_WARNING("MQTT: Subscribe topic invalid\r\n"); + MQTT_ServerDisconnect(clientcon); + return; + } + topic_index += 2 + topic_len; - delete_topic(clientcon, topic_buffer); - //iterate_topics(print_topic, 0); - } - MQTT_INFO("MQTT: Unubscribe successful\r\n"); + os_memcpy(topic_buffer, topic_str, topic_len); + topic_buffer[topic_len] = 0; + MQTT_INFO("MQTT: Unsubscribed topic %s\r\n", topic_buffer); - clientcon->mqtt_state.outbound_message = mqtt_msg_unsuback(&clientcon->mqtt_state.mqtt_connection, msg_id); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - break; + delete_topic(clientcon, topic_buffer); + //iterate_topics(print_topic, 0); + } + MQTT_INFO("MQTT: Unubscribe successful\r\n"); - case MQTT_MSG_TYPE_PUBLISH: - MQTT_INFO("MQTT: Publish received, message_len: %d\r\n", clientcon->mqtt_state.message_length); + clientcon->mqtt_state.outbound_message = mqtt_msg_unsuback(&clientcon->mqtt_state.mqtt_connection, msg_id); + if (QUEUE_Puts + (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, + clientcon->mqtt_state.outbound_message->length) == -1) { + MQTT_ERROR("MQTT: Queue full\r\n"); + } + break; + + case MQTT_MSG_TYPE_PUBLISH: + MQTT_INFO("MQTT: Publish received, message_len: %d\r\n", clientcon->mqtt_state.message_length); /* if (msg_qos == 1) clientcon->mqtt_state.outbound_message = mqtt_msg_puback(&clientcon->mqtt_state.mqtt_connection, msg_id); @@ -582,43 +575,45 @@ READPACKET: } } */ - topic_len = clientcon->mqtt_state.in_buffer_length; - topic_str = (uint8_t*)mqtt_get_publish_topic(clientcon->mqtt_state.in_buffer, &topic_len); - os_memcpy(topic_buffer, topic_str, topic_len); - topic_buffer[topic_len] = 0; - data_len = clientcon->mqtt_state.in_buffer_length; - data = (uint8_t*)mqtt_get_publish_data(clientcon->mqtt_state.in_buffer, &data_len); + topic_len = clientcon->mqtt_state.in_buffer_length; + topic_str = (uint8_t *) mqtt_get_publish_topic(clientcon->mqtt_state.in_buffer, &topic_len); + os_memcpy(topic_buffer, topic_str, topic_len); + topic_buffer[topic_len] = 0; + data_len = clientcon->mqtt_state.in_buffer_length; + data = (uint8_t *) mqtt_get_publish_data(clientcon->mqtt_state.in_buffer, &data_len); - MQTT_INFO("MQTT: Published topic \"%s\"\r\n", topic_buffer); - MQTT_INFO("MQTT: Matches to:\r\n"); + MQTT_INFO("MQTT: Published topic \"%s\"\r\n", topic_buffer); + MQTT_INFO("MQTT: Matches to:\r\n"); - // Now find, if anything matches and enque publish message - find_topic(topic_buffer, publish_topic, data, data_len); + // Now find, if anything matches and enque publish message + find_topic(topic_buffer, publish_topic, data, data_len); - if (mqtt_get_retain(clientcon->mqtt_state.in_buffer)) { - update_retainedtopic(topic_buffer, data, data_len, mqtt_get_qos(clientcon->mqtt_state.in_buffer)); - } - - break; + if (mqtt_get_retain(clientcon->mqtt_state.in_buffer)) { + update_retainedtopic(topic_buffer, data, data_len, mqtt_get_qos(clientcon->mqtt_state.in_buffer)); + } - case MQTT_MSG_TYPE_PINGREQ: - MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_PINGREQ\r\n"); - clientcon->mqtt_state.outbound_message = mqtt_msg_pingresp(&clientcon->mqtt_state.mqtt_connection); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - break; + break; - case MQTT_MSG_TYPE_DISCONNECT: - MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_DISCONNECT\r\n"); + case MQTT_MSG_TYPE_PINGREQ: + MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_PINGREQ\r\n"); + clientcon->mqtt_state.outbound_message = mqtt_msg_pingresp(&clientcon->mqtt_state.mqtt_connection); + if (QUEUE_Puts + (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, + clientcon->mqtt_state.outbound_message->length) == -1) { + MQTT_ERROR("MQTT: Queue full\r\n"); + } + break; - // Clean session close: no LWT - if (clientcon->connect_info.will_topic != NULL) { - os_free(clientcon->connect_info.will_topic); - clientcon->connect_info.will_topic = NULL; - } - MQTT_ServerDisconnect(clientcon); - return; + case MQTT_MSG_TYPE_DISCONNECT: + MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_DISCONNECT\r\n"); + + // Clean session close: no LWT + if (clientcon->connect_info.will_topic != NULL) { + os_free(clientcon->connect_info.will_topic); + clientcon->connect_info.will_topic = NULL; + } + MQTT_ServerDisconnect(clientcon); + return; /* case MQTT_MSG_TYPE_PUBACK: @@ -649,76 +644,70 @@ READPACKET: break; */ - default: - // Ignore - break; - } - break; - } + default: + // Ignore + break; + } + break; + } - // More than one MQTT command in the packet? - len = clientcon->mqtt_state.message_length_read; - if (clientcon->mqtt_state.message_length < len) { - len -= clientcon->mqtt_state.message_length; - pdata += clientcon->mqtt_state.message_length; + // More than one MQTT command in the packet? + len = clientcon->mqtt_state.message_length_read; + if (clientcon->mqtt_state.message_length < len) { + len -= clientcon->mqtt_state.message_length; + pdata += clientcon->mqtt_state.message_length; + clientcon->mqtt_state.message_length_read = 0; + + MQTT_INFO("MQTT: Get another received message\r\n"); + goto READPACKET; + } clientcon->mqtt_state.message_length_read = 0; - MQTT_INFO("MQTT: Get another received message\r\n"); - goto READPACKET; - } - clientcon->mqtt_state.message_length_read = 0; - - if (msg_type != MQTT_MSG_TYPE_PUBLISH) { - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t)clientcon); - } else { - activate_next_client(); - } + if (msg_type != MQTT_MSG_TYPE_PUBLISH) { + system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) clientcon); + } else { + activate_next_client(); + } } - /* Called when a client has disconnected from the MQTT server */ -static void ICACHE_FLASH_ATTR MQTT_ClientCon_discon_cb(void *arg) -{ +static void ICACHE_FLASH_ATTR MQTT_ClientCon_discon_cb(void *arg) { struct espconn *pCon = (struct espconn *)arg; - MQTT_ClientCon *clientcon = (MQTT_ClientCon *)pCon->reverse; + MQTT_ClientCon *clientcon = (MQTT_ClientCon *) pCon->reverse; MQTT_INFO("MQTT_ClientCon_discon_cb(): client disconnected\n"); MQTT_DeleteClientCon(clientcon); } - -static void ICACHE_FLASH_ATTR MQTT_ClientCon_sent_cb(void *arg) -{ +static void ICACHE_FLASH_ATTR MQTT_ClientCon_sent_cb(void *arg) { struct espconn *pCon = (struct espconn *)arg; - MQTT_ClientCon *clientcon = (MQTT_ClientCon *)pCon->reverse; + MQTT_ClientCon *clientcon = (MQTT_ClientCon *) pCon->reverse; MQTT_INFO("MQTT_ClientCon_sent_cb(): Data sent\n"); clientcon->sendTimeout = 0; if (clientcon->connState == TCP_DISCONNECTING) { - clientcon->connState = TCP_DISCONNECTED; - espconn_disconnect(clientcon->pCon); + clientcon->connState = TCP_DISCONNECTED; + espconn_disconnect(clientcon->pCon); } activate_next_client(); } - /* Called when a client connects to the MQTT server */ -static void ICACHE_FLASH_ATTR MQTT_ClientCon_connected_cb(void *arg) -{ +static void ICACHE_FLASH_ATTR MQTT_ClientCon_connected_cb(void *arg) { struct espconn *pespconn = (struct espconn *)arg; MQTT_ClientCon *mqttClientCon; MQTT_INFO("MQTT_ClientCon_connected_cb(): Client connected\r\n"); - espconn_regist_sentcb(pespconn, MQTT_ClientCon_sent_cb); - espconn_regist_disconcb(pespconn, MQTT_ClientCon_discon_cb); - espconn_regist_recvcb(pespconn, MQTT_ClientCon_recv_cb); - espconn_regist_time(pespconn, 30, 1); + espconn_regist_sentcb(pespconn, MQTT_ClientCon_sent_cb); + espconn_regist_disconcb(pespconn, MQTT_ClientCon_discon_cb); + espconn_regist_recvcb(pespconn, MQTT_ClientCon_recv_cb); + espconn_regist_time(pespconn, 30, 1); - mqttClientCon = (MQTT_ClientCon *)os_zalloc(sizeof(MQTT_ClientCon)); + mqttClientCon = (MQTT_ClientCon *) os_zalloc(sizeof(MQTT_ClientCon)); pespconn->reverse = mqttClientCon; if (mqttClientCon == NULL) { MQTT_ERROR("ERROR: Cannot allocate client status\r\n"); @@ -728,70 +717,68 @@ static void ICACHE_FLASH_ATTR MQTT_ClientCon_connected_cb(void *arg) MQTT_InitClientCon(mqttClientCon); mqttClientCon->pCon = pespconn; - os_timer_setfn(&mqttClientCon->mqttTimer, (os_timer_func_t *)mqtt_server_timer, mqttClientCon); + os_timer_setfn(&mqttClientCon->mqttTimer, (os_timer_func_t *) mqtt_server_timer, mqttClientCon); os_timer_arm(&mqttClientCon->mqttTimer, 1000, 1); } +void ICACHE_FLASH_ATTR MQTT_ServerTask(os_event_t * e) { + MQTT_ClientCon *clientcon = (MQTT_ClientCon *) e->par; + uint8_t dataBuffer[MQTT_BUF_SIZE]; + uint16_t dataLen; + if (e->par == 0) + return; -void ICACHE_FLASH_ATTR -MQTT_ServerTask(os_event_t *e) -{ - MQTT_ClientCon *clientcon = (MQTT_ClientCon *)e->par; - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - if (e->par == 0) - return; + MQTT_INFO("MQTT: Server task activated - state %d\r\n", clientcon->connState); - MQTT_INFO("MQTT: Server task activated - state %d\r\n", clientcon->connState); - - switch (clientcon->connState) { + switch (clientcon->connState) { case TCP_DISCONNECTED: - MQTT_INFO("MQTT: Disconnect\r\n"); - espconn_disconnect(clientcon->pCon); - break; + MQTT_INFO("MQTT: Disconnect\r\n"); + espconn_disconnect(clientcon->pCon); + break; case TCP_DISCONNECTING: case MQTT_DATA: - if (!QUEUE_IsEmpty(&clientcon->msgQueue) && clientcon->sendTimeout == 0 && - QUEUE_Gets(&clientcon->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == 0) { + if (!QUEUE_IsEmpty(&clientcon->msgQueue) && clientcon->sendTimeout == 0 && + QUEUE_Gets(&clientcon->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == 0) { - clientcon->mqtt_state.pending_msg_type = mqtt_get_type(dataBuffer); - clientcon->mqtt_state.pending_msg_id = mqtt_get_id(dataBuffer, dataLen); + clientcon->mqtt_state.pending_msg_type = mqtt_get_type(dataBuffer); + clientcon->mqtt_state.pending_msg_id = mqtt_get_id(dataBuffer, dataLen); - clientcon->sendTimeout = MQTT_SEND_TIMOUT; - MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", clientcon->mqtt_state.pending_msg_type, clientcon->mqtt_state.pending_msg_id); - espconn_send(clientcon->pCon, dataBuffer, dataLen); + clientcon->sendTimeout = MQTT_SEND_TIMOUT; + MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", clientcon->mqtt_state.pending_msg_type, + clientcon->mqtt_state.pending_msg_id); + espconn_send(clientcon->pCon, dataBuffer, dataLen); - clientcon->mqtt_state.outbound_message = NULL; - break; - } - if (clientcon->connState == TCP_DISCONNECTING) { - MQTT_ServerDisconnect(clientcon); - } - break; - } + clientcon->mqtt_state.outbound_message = NULL; + break; + } + if (clientcon->connState == TCP_DISCONNECTING) { + MQTT_ServerDisconnect(clientcon); + } + break; + } } - -bool ICACHE_FLASH_ATTR MQTT_server_start(uint16_t portno, uint16_t max_subscriptions, uint16_t max_retained_topics) -{ +bool ICACHE_FLASH_ATTR MQTT_server_start(uint16_t portno, uint16_t max_subscriptions, uint16_t max_retained_topics) { MQTT_INFO("Starting MQTT server on port %d\r\n", portno); - if (!create_topiclist(max_subscriptions)) return false; - if (!create_retainedlist(max_retained_topics)) return false; + if (!create_topiclist(max_subscriptions)) + return false; + if (!create_retainedlist(max_retained_topics)) + return false; clientcon_list = NULL; struct espconn *pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); if (pCon == NULL) - return false; + return false; /* Equivalent to bind */ - pCon->type = ESPCONN_TCP; + pCon->type = ESPCONN_TCP; pCon->state = ESPCONN_NONE; - pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); + pCon->proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); if (pCon->proto.tcp == NULL) { - os_free(pCon); - return false; + os_free(pCon); + return false; } pCon->proto.tcp->local_port = portno; @@ -805,29 +792,23 @@ bool ICACHE_FLASH_ATTR MQTT_server_start(uint16_t portno, uint16_t max_subscript return true; } - -bool ICACHE_FLASH_ATTR MQTT_local_publish(uint8_t* topic, uint8_t* data, uint16_t data_length, uint8_t qos, uint8_t retain) -{ - find_topic(topic, publish_topic, data, data_length); - if (retain) - update_retainedtopic(topic, data, data_length, qos); - activate_next_client(); - return true; +bool ICACHE_FLASH_ATTR MQTT_local_publish(uint8_t * topic, uint8_t * data, uint16_t data_length, uint8_t qos, + uint8_t retain) { + find_topic(topic, publish_topic, data, data_length); + if (retain) + update_retainedtopic(topic, data, data_length, qos); + activate_next_client(); + return true; } - -bool ICACHE_FLASH_ATTR MQTT_local_subscribe(uint8_t* topic, uint8_t qos) -{ - return add_topic(LOCAL_MQTT_CLIENT, topic, 0); +bool ICACHE_FLASH_ATTR MQTT_local_subscribe(uint8_t * topic, uint8_t qos) { + return add_topic(LOCAL_MQTT_CLIENT, topic, 0); } -bool ICACHE_FLASH_ATTR MQTT_local_unsubscribe(uint8_t* topic) -{ - return delete_topic(LOCAL_MQTT_CLIENT, topic); +bool ICACHE_FLASH_ATTR MQTT_local_unsubscribe(uint8_t * topic) { + return delete_topic(LOCAL_MQTT_CLIENT, topic); } -void ICACHE_FLASH_ATTR MQTT_local_onData(MqttDataCallback dataCb) -{ - local_data_cb = dataCb; +void ICACHE_FLASH_ATTR MQTT_local_onData(MqttDataCallback dataCb) { + local_data_cb = dataCb; } - diff --git a/mqtt/mqtt_topiclist.c b/mqtt/mqtt_topiclist.c index 4d2964e..9721f00 100644 --- a/mqtt/mqtt_topiclist.c +++ b/mqtt/mqtt_topiclist.c @@ -12,79 +12,80 @@ static topic_entry *topic_list = NULL; static uint16_t max_entry; -bool ICACHE_FLASH_ATTR create_topiclist(uint16_t num_entires) -{ - max_entry = num_entires; - topic_list = (topic_entry *)os_zalloc(num_entires * sizeof(topic_entry)); - return topic_list != NULL; +bool ICACHE_FLASH_ATTR create_topiclist(uint16_t num_entires) { + max_entry = num_entires; + topic_list = (topic_entry *) os_zalloc(num_entires * sizeof(topic_entry)); + return topic_list != NULL; } -bool ICACHE_FLASH_ATTR add_topic(MQTT_ClientCon *clientcon, uint8_t *topic, uint8_t qos) -{ -uint16_t i; +bool ICACHE_FLASH_ATTR add_topic(MQTT_ClientCon * clientcon, uint8_t * topic, uint8_t qos) { + uint16_t i; - if (topic_list == NULL) return false; - if (!Topics_isValidName(topic)) return false; + if (topic_list == NULL) + return false; + if (!Topics_isValidName(topic)) + return false; - for (i=0; i aName) { /* check previous char is '/' */ + if (*(pos - 1) != '/') + rc = false; + } + if (*(pos + 1) != '\0') { /* check that subsequent char is '/' */ + if (*(pos + 1) != '/') + rc = false; + } + pos = _strchr(pos + 1, *c); } + } - /* '#' or '+' only next to a slash separator or end of name */ - for (c = "#+"; *c != '\0'; ++c) - { - char* pos = _strchr(aName, *c); - while (pos != NULL) - { - if (pos > aName) /* check previous char is '/'*/ - { - if (*(pos - 1) != '/') - rc = false; - } - if (*(pos + 1) != '\0') /* check that subsequent char is '/'*/ - { - if (*(pos + 1) != '/') - rc = false; - } - pos = _strchr(pos + 1, *c); - } - } - - return rc; + return rc; } - /** * Reverse a string. * Linux utility function for Linux to enable Windows/Linux portability * @param astr the character string to reverse * @return pointer to the reversed string which was reversed in place */ -char* _strrev(char* astr) -{ - char* forwards = astr; - int len = os_strlen(astr); - if (len > 1) - { - char* backwards = astr + len - 1; - while (forwards < backwards) - { - char temp = *forwards; - *forwards++ = *backwards; - *backwards-- = temp; - } +char *_strrev(char *astr) { + char *forwards = astr; + int len = os_strlen(astr); + if (len > 1) { + char *backwards = astr + len - 1; + while (forwards < backwards) { + char temp = *forwards; + *forwards++ = *backwards; + *backwards-- = temp; } - return astr; + } + return astr; } - /** * Does a topic string contain wildcards? * @param topic the topic name string * @return boolean value indicating whether the topic contains a wildcard or not */ -int Topics_hasWildcards(char* topic) -{ - return (_strchr(topic, '+') != NULL) || (_strchr(topic, '#') != NULL); +int Topics_hasWildcards(char *topic) { + return (_strchr(topic, '+') != NULL) || (_strchr(topic, '#') != NULL); } - /** * Tests whether one topic string matches another where one can contain wildcards. * @param wildTopic a topic name string that can contain wildcards * @param topic a topic name string that must not contain wildcards * @return boolean value indicating whether topic matches wildTopic */ -int Topics_matches(char* wildTopic, int wildcards, char* topic) -{ - int rc = false; - char *last1 = NULL, *last2 = NULL; - char *pwild = NULL, *pmatch = NULL; +int Topics_matches(char *wildTopic, int wildcards, char *topic) { + int rc = false; + char *last1 = NULL, *last2 = NULL; + char *pwild = NULL, *pmatch = NULL; - if (!wildcards) - { - rc = (os_strcmp(wildTopic, topic) == 0); - goto exit; + if (!wildcards) { + rc = (os_strcmp(wildTopic, topic) == 0); + goto exit; + } + + if (Topics_hasWildcards(topic)) { + //os_printf("Topics_matches: should not be wildcard in topic %s", topic); + goto exit; + } + if (!Topics_isValidName(wildTopic)) { + //os_printf("Topics_matches: invalid topic name %s", wildTopic); + goto exit; + } + if (!Topics_isValidName(topic)) { + //os_printf("Topics_matches: invalid topic name %s", topic); + goto exit; + } + + if (strcmp(wildTopic, MULTI_LEVEL_WILDCARD) == 0 || /* Hash matches anything... */ + strcmp(wildTopic, topic) == 0) { + rc = true; + goto exit; + } + + if (strcmp(wildTopic, "/#") == 0) { /* Special case for /# matches anything starting with / */ + rc = (topic[0] == '/') ? true : false; + goto exit; + } + + /* because strtok will return bill when matching /bill/ or bill in a topic name for the first time, + * we have to check whether the first character is / explicitly. + */ + if ((wildTopic[0] == TOPIC_LEVEL_SEPARATOR[0]) && (topic[0] != TOPIC_LEVEL_SEPARATOR[0])) + goto exit; + + if ((wildTopic[0] == SINGLE_LEVEL_WILDCARD[0]) && (topic[0] == TOPIC_LEVEL_SEPARATOR[0])) + goto exit; + + /* We only match hash-first topics in reverse, for speed */ + if (wildTopic[0] == MULTI_LEVEL_WILDCARD[0]) { + wildTopic = (char *)_strrev(_strdup(wildTopic)); + topic = (char *)_strrev(_strdup(topic)); + } else { + wildTopic = (char *)_strdup(wildTopic); + topic = (char *)_strdup(topic); + } + + pwild = _strtok_r(wildTopic, TOPIC_LEVEL_SEPARATOR, &last1); + pmatch = _strtok_r(topic, TOPIC_LEVEL_SEPARATOR, &last2); + + /* Step through the subscription, level by level */ + while (pwild != NULL) { + /* Have we got # - if so, it matches anything. */ + if (strcmp(pwild, MULTI_LEVEL_WILDCARD) == 0) { + rc = true; + break; } + /* Nope - check for matches... */ + if (pmatch != NULL) { + if (strcmp(pwild, SINGLE_LEVEL_WILDCARD) != 0 && strcmp(pwild, pmatch) != 0) + /* The two levels simply don't match... */ + break; + } else + break; /* No more tokens to match against further tokens in the wildcard stream... */ + pwild = _strtok_r(NULL, TOPIC_LEVEL_SEPARATOR, &last1); + pmatch = _strtok_r(NULL, TOPIC_LEVEL_SEPARATOR, &last2); + } - if (Topics_hasWildcards(topic)) - { - //os_printf("Topics_matches: should not be wildcard in topic %s", topic); - goto exit; - } - if (!Topics_isValidName(wildTopic)) - { - //os_printf("Topics_matches: invalid topic name %s", wildTopic); - goto exit; - } - if (!Topics_isValidName(topic)) - { - //os_printf("Topics_matches: invalid topic name %s", topic); - goto exit; - } + /* All tokens up to here matched, and we didn't end in #. If there + are any topic tokens remaining, the match is bad, otherwise it was + a good match. */ + if (pmatch == NULL && pwild == NULL) + rc = true; - if (strcmp(wildTopic, MULTI_LEVEL_WILDCARD) == 0 || /* Hash matches anything... */ - strcmp(wildTopic, topic) == 0) - { - rc = true; - goto exit; - } - - if (strcmp(wildTopic, "/#") == 0) /* Special case for /# matches anything starting with / */ - { - rc = (topic[0] == '/') ? true : false; - goto exit; - } - - /* because strtok will return bill when matching /bill/ or bill in a topic name for the first time, - * we have to check whether the first character is / explicitly. - */ - if ((wildTopic[0] == TOPIC_LEVEL_SEPARATOR[0]) && (topic[0] != TOPIC_LEVEL_SEPARATOR[0])) - goto exit; - - if ((wildTopic[0] == SINGLE_LEVEL_WILDCARD[0]) && (topic[0] == TOPIC_LEVEL_SEPARATOR[0])) - goto exit; - - /* We only match hash-first topics in reverse, for speed */ - if (wildTopic[0] == MULTI_LEVEL_WILDCARD[0]) - { - wildTopic = (char*)_strrev(_strdup(wildTopic)); - topic = (char*)_strrev(_strdup(topic)); - } - else - { - wildTopic = (char*)_strdup(wildTopic); - topic = (char*)_strdup(topic); - } - - pwild = _strtok_r(wildTopic, TOPIC_LEVEL_SEPARATOR, &last1); - pmatch = _strtok_r(topic, TOPIC_LEVEL_SEPARATOR, &last2); - - /* Step through the subscription, level by level */ - while (pwild != NULL) - { - /* Have we got # - if so, it matches anything. */ - if (strcmp(pwild, MULTI_LEVEL_WILDCARD) == 0) - { - rc = true; - break; - } - /* Nope - check for matches... */ - if (pmatch != NULL) - { - if (strcmp(pwild, SINGLE_LEVEL_WILDCARD) != 0 && strcmp(pwild, pmatch) != 0) - /* The two levels simply don't match... */ - break; - } - else - break; /* No more tokens to match against further tokens in the wildcard stream... */ - pwild = _strtok_r(NULL, TOPIC_LEVEL_SEPARATOR, &last1); - pmatch = _strtok_r(NULL, TOPIC_LEVEL_SEPARATOR, &last2); - } - - /* All tokens up to here matched, and we didn't end in #. If there - are any topic tokens remaining, the match is bad, otherwise it was - a good match. */ - if (pmatch == NULL && pwild == NULL) - rc = true; - - /* Now free the memory allocated in strdup() */ - os_free(wildTopic); - os_free(topic); -exit: - return rc; -} /* end matches*/ + /* Now free the memory allocated in strdup() */ + os_free(wildTopic); + os_free(topic); + exit: + return rc; +} /* end matches */ #ifdef UNIT_TEST #if !defined(ARRAY_SIZE) @@ -246,73 +216,64 @@ exit: #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #endif -int test() -{ - int i; +int test() { + int i; - struct - { - char* str; - } tests0[] = { - "#", "jj", - "+/a", "adkj/a", - "+/a", "adsjk/adakjd/a", "a/+", "a/#", "#/a" - }; + struct { + char *str; + } tests0[] = { + "#", "jj", "+/a", "adkj/a", "+/a", "adsjk/adakjd/a", "a/+", "a/#", "#/a"}; - for (i = 0; i < sizeof(tests0)/sizeof(char*); ++i) - { - os_printf("topic %s, isValidName %d\n", tests0[i].str, Topics_isValidName(tests0[i].str)); - //assert(Topics_isValidName(tests0[i].str) == 1); - } + for (i = 0; i < sizeof(tests0) / sizeof(char *); ++i) { + os_printf("topic %s, isValidName %d\n", tests0[i].str, Topics_isValidName(tests0[i].str)); + //assert(Topics_isValidName(tests0[i].str) == 1); + } - struct + struct { + char *wild; + char *topic; + int result; + } tests1[] = { { - char* wild; - char* topic; - int result; - } tests1[] = { - { "#", "jj" , 1}, - { "+/a", "adkj/a", 1}, - { "+/a", "adsjk/adakjd/a", 0}, - { "+/+/a", "adsjk/adakjd/a", 1}, - { "#/a", "adsjk/adakjd/a", 1}, - { "test/#", "test/1", 1}, - { "test/+", "test/1", 1}, - { "+", "test1", 1}, - { "+", "test1/k", 0}, - { "+", "/test1/k", 0}, - { "/+", "test1/k", 0}, - { "+", "/jkj", 0}, - { "/+", "/test1", 1}, - { "+/+", "/test1", 0}, - { "+/+", "test1/k", 1}, - { "/#", "/test1/k", 1}, - { "/#", "test1/k", 0}, - }; + "#", "jj", 1}, { + "+/a", "adkj/a", 1}, { + "+/a", "adsjk/adakjd/a", 0}, { + "+/+/a", "adsjk/adakjd/a", 1}, { + "#/a", "adsjk/adakjd/a", 1}, { + "test/#", "test/1", 1}, { + "test/+", "test/1", 1}, { + "+", "test1", 1}, { + "+", "test1/k", 0}, { + "+", "/test1/k", 0}, { + "/+", "test1/k", 0}, { + "+", "/jkj", 0}, { + "/+", "/test1", 1}, { + "+/+", "/test1", 0}, { + "+/+", "test1/k", 1}, { + "/#", "/test1/k", 1}, { + "/#", "test1/k", 0},}; - for (i = 0; i < ARRAY_SIZE(tests1); ++i) - { - os_printf("wild: %s, topic %s, result %d %d (should)\n", tests1[i].wild, tests1[i].topic, - Topics_matches(_strdup(tests1[i].wild), 1, _strdup(tests1[i].topic)), tests1[i].result); - //assert(Topics_matches(_strdup(tests1[i].wild), _strdup(tests1[i].topic)) == tests1[i].result); - } + for (i = 0; i < ARRAY_SIZE(tests1); ++i) { + os_printf("wild: %s, topic %s, result %d %d (should)\n", tests1[i].wild, tests1[i].topic, + Topics_matches(_strdup(tests1[i].wild), 1, _strdup(tests1[i].topic)), tests1[i].result); + //assert(Topics_matches(_strdup(tests1[i].wild), _strdup(tests1[i].topic)) == tests1[i].result); + } - struct + struct { + char *str; + char *result; + } tests2[] = { { - char* str; - char* result; - } tests2[] = { - { "#", "#" }, - { "ab", "ba" }, - { "abc", "cba" }, - { "abcd", "dcba" }, - { "abcde", "edcba" } - }; - for (i = 0; i < 5; ++i) - { - os_printf("str: %s, _strrev %s\n", tests2[i].str, _strrev(_strdup(tests2[i].str))); - //assert(strcmp(tests2[i].result, _strrev(_strdup(tests2[i].str))) == 0); - } + "#", "#"}, { + "ab", "ba"}, { + "abc", "cba"}, { + "abcd", "dcba"}, { + "abcde", "edcba"} + }; + for (i = 0; i < 5; ++i) { + os_printf("str: %s, _strrev %s\n", tests2[i].str, _strrev(_strdup(tests2[i].str))); + //assert(strcmp(tests2[i].result, _strrev(_strdup(tests2[i].str))) == 0); + } } #endif diff --git a/mqtt/proto.c b/mqtt/proto.c index 8968a1a..45bbe95 100644 --- a/mqtt/proto.c +++ b/mqtt/proto.c @@ -1,129 +1,132 @@ #include "proto.h" #include "ringbuf_mqtt.h" -I8 ICACHE_FLASH_ATTR PROTO_Init(PROTO_PARSER *parser, PROTO_PARSE_CALLBACK *completeCallback, U8 *buf, U16 bufSize) -{ - parser->buf = buf; - parser->bufSize = bufSize; - parser->dataLen = 0; - parser->callback = completeCallback; - parser->isEsc = 0; - return 0; +I8 ICACHE_FLASH_ATTR PROTO_Init(PROTO_PARSER * parser, PROTO_PARSE_CALLBACK * completeCallback, U8 * buf, U16 bufSize) { + parser->buf = buf; + parser->bufSize = bufSize; + parser->dataLen = 0; + parser->callback = completeCallback; + parser->isEsc = 0; + return 0; } -I8 ICACHE_FLASH_ATTR PROTO_ParseByte(PROTO_PARSER *parser, U8 value) -{ - switch (value) { +I8 ICACHE_FLASH_ATTR PROTO_ParseByte(PROTO_PARSER * parser, U8 value) { + switch (value) { case 0x7D: - parser->isEsc = 1; - break; + parser->isEsc = 1; + break; case 0x7E: - parser->dataLen = 0; - parser->isEsc = 0; - parser->isBegin = 1; - break; + parser->dataLen = 0; + parser->isEsc = 0; + parser->isBegin = 1; + break; case 0x7F: - if (parser->callback != NULL) - parser->callback(); - parser->isBegin = 0; - return 0; - break; + if (parser->callback != NULL) + parser->callback(); + parser->isBegin = 0; + return 0; + break; default: - if (parser->isBegin == 0) break; + if (parser->isBegin == 0) + break; - if (parser->isEsc) { - value ^= 0x20; - parser->isEsc = 0; - } + if (parser->isEsc) { + value ^= 0x20; + parser->isEsc = 0; + } - if (parser->dataLen < parser->bufSize) - parser->buf[parser->dataLen++] = value; + if (parser->dataLen < parser->bufSize) + parser->buf[parser->dataLen++] = value; - break; - } - return -1; -} - -I8 ICACHE_FLASH_ATTR PROTO_Parse(PROTO_PARSER *parser, U8 *buf, U16 len) -{ - while (len--) - PROTO_ParseByte(parser, *buf++); - - return 0; -} -I16 ICACHE_FLASH_ATTR PROTO_ParseRb(RINGBUF* rb, U8 *bufOut, U16* len, U16 maxBufLen) -{ - U8 c; - - PROTO_PARSER proto; - PROTO_Init(&proto, NULL, bufOut, maxBufLen); - while (RINGBUF_Get(rb, &c) == 0) { - if (PROTO_ParseByte(&proto, c) == 0) { - *len = proto.dataLen; - return 0; + break; } - } - return -1; + return -1; } -I16 ICACHE_FLASH_ATTR PROTO_Add(U8 *buf, const U8 *packet, I16 bufSize) -{ - U16 i = 2; - U16 len = *(U16*) packet; - if (bufSize < 1) return -1; +I8 ICACHE_FLASH_ATTR PROTO_Parse(PROTO_PARSER * parser, U8 * buf, U16 len) { + while (len--) + PROTO_ParseByte(parser, *buf++); - *buf++ = 0x7E; - bufSize--; + return 0; +} +I16 ICACHE_FLASH_ATTR PROTO_ParseRb(RINGBUF * rb, U8 * bufOut, U16 * len, U16 maxBufLen) { + U8 c; - while (len--) { - switch (*packet) { - case 0x7D: - case 0x7E: - case 0x7F: - if (bufSize < 2) return -1; - *buf++ = 0x7D; - *buf++ = *packet++ ^ 0x20; - i += 2; - bufSize -= 2; - break; - default: - if (bufSize < 1) return -1; - *buf++ = *packet++; - i++; - bufSize--; - break; + PROTO_PARSER proto; + PROTO_Init(&proto, NULL, bufOut, maxBufLen); + while (RINGBUF_Get(rb, &c) == 0) { + if (PROTO_ParseByte(&proto, c) == 0) { + *len = proto.dataLen; + return 0; + } } - } - - if (bufSize < 1) return -1; - *buf++ = 0x7F; - - return i; + return -1; } +I16 ICACHE_FLASH_ATTR PROTO_Add(U8 * buf, const U8 * packet, I16 bufSize) { + U16 i = 2; + U16 len = *(U16 *) packet; -I16 ICACHE_FLASH_ATTR PROTO_AddRb(RINGBUF *rb, const U8 *packet, I16 len) -{ - U16 i = 2; - if (RINGBUF_Put(rb, 0x7E) == -1) return -1; - while (len--) { - switch (*packet) { - case 0x7D: - case 0x7E: - case 0x7F: - if (RINGBUF_Put(rb, 0x7D) == -1) return -1; - if (RINGBUF_Put(rb, *packet++ ^ 0x20) == -1) return -1; - i += 2; - break; - default: - if (RINGBUF_Put(rb, *packet++) == -1) return -1; - i++; - break; + if (bufSize < 1) + return -1; + + *buf++ = 0x7E; + bufSize--; + + while (len--) { + switch (*packet) { + case 0x7D: + case 0x7E: + case 0x7F: + if (bufSize < 2) + return -1; + *buf++ = 0x7D; + *buf++ = *packet++ ^ 0x20; + i += 2; + bufSize -= 2; + break; + default: + if (bufSize < 1) + return -1; + *buf++ = *packet++; + i++; + bufSize--; + break; + } } - } - if (RINGBUF_Put(rb, 0x7F) == -1) return -1; - return i; + if (bufSize < 1) + return -1; + *buf++ = 0x7F; + + return i; } +I16 ICACHE_FLASH_ATTR PROTO_AddRb(RINGBUF * rb, const U8 * packet, I16 len) { + U16 i = 2; + if (RINGBUF_Put(rb, 0x7E) == -1) + return -1; + while (len--) { + switch (*packet) { + case 0x7D: + case 0x7E: + case 0x7F: + if (RINGBUF_Put(rb, 0x7D) == -1) + return -1; + if (RINGBUF_Put(rb, *packet++ ^ 0x20) == -1) + return -1; + i += 2; + break; + default: + if (RINGBUF_Put(rb, *packet++) == -1) + return -1; + i++; + break; + } + } + if (RINGBUF_Put(rb, 0x7F) == -1) + return -1; + + return i; +} diff --git a/mqtt/queue.c b/mqtt/queue.c index 7fa50aa..804dbb6 100644 --- a/mqtt/queue.c +++ b/mqtt/queue.c @@ -34,24 +34,20 @@ #include "os_type.h" #include "mem.h" #include "proto.h" -void ICACHE_FLASH_ATTR QUEUE_Init(QUEUE *queue, int bufferSize) -{ - queue->buf = (uint8_t*)os_zalloc(bufferSize); - RINGBUF_Init(&queue->rb, queue->buf, bufferSize); +void ICACHE_FLASH_ATTR QUEUE_Init(QUEUE * queue, int bufferSize) { + queue->buf = (uint8_t *) os_zalloc(bufferSize); + RINGBUF_Init(&queue->rb, queue->buf, bufferSize); } -int32_t ICACHE_FLASH_ATTR QUEUE_Puts(QUEUE *queue, uint8_t* buffer, uint16_t len) -{ - return PROTO_AddRb(&queue->rb, buffer, len); +int32_t ICACHE_FLASH_ATTR QUEUE_Puts(QUEUE * queue, uint8_t * buffer, uint16_t len) { + return PROTO_AddRb(&queue->rb, buffer, len); } -int32_t ICACHE_FLASH_ATTR QUEUE_Gets(QUEUE *queue, uint8_t* buffer, uint16_t* len, uint16_t maxLen) -{ +int32_t ICACHE_FLASH_ATTR QUEUE_Gets(QUEUE * queue, uint8_t * buffer, uint16_t * len, uint16_t maxLen) { - return PROTO_ParseRb(&queue->rb, buffer, len, maxLen); + return PROTO_ParseRb(&queue->rb, buffer, len, maxLen); } -BOOL ICACHE_FLASH_ATTR QUEUE_IsEmpty(QUEUE *queue) -{ - if (queue->rb.fill_cnt <= 0) - return TRUE; - return FALSE; +BOOL ICACHE_FLASH_ATTR QUEUE_IsEmpty(QUEUE * queue) { + if (queue->rb.fill_cnt <= 0) + return TRUE; + return FALSE; } diff --git a/mqtt/ringbuf_mqtt.c b/mqtt/ringbuf_mqtt.c index fb38ed7..dd2f8e0 100644 --- a/mqtt/ringbuf_mqtt.c +++ b/mqtt/ringbuf_mqtt.c @@ -5,7 +5,6 @@ #include "ringbuf_mqtt.h" - /** * \brief init a RINGBUF object * \param r pointer to a RINGBUF object @@ -13,55 +12,53 @@ * \param size size of buf * \return 0 if successfull, otherwise failed */ -I16 ICACHE_FLASH_ATTR RINGBUF_Init(RINGBUF *r, U8* buf, I32 size) -{ - if (r == NULL || buf == NULL || size < 2) return -1; +I16 ICACHE_FLASH_ATTR RINGBUF_Init(RINGBUF * r, U8 * buf, I32 size) { + if (r == NULL || buf == NULL || size < 2) + return -1; - r->p_o = r->p_r = r->p_w = buf; - r->fill_cnt = 0; - r->size = size; + r->p_o = r->p_r = r->p_w = buf; + r->fill_cnt = 0; + r->size = size; - return 0; + return 0; } + /** * \brief put a character into ring buffer * \param r pointer to a ringbuf object * \param c character to be put * \return 0 if successfull, otherwise failed */ -I16 ICACHE_FLASH_ATTR RINGBUF_Put(RINGBUF *r, U8 c) -{ - if (r->fill_cnt >= r->size)return -1; // ring buffer is full, this should be atomic operation +I16 ICACHE_FLASH_ATTR RINGBUF_Put(RINGBUF * r, U8 c) { + if (r->fill_cnt >= r->size) + return -1; // ring buffer is full, this should be atomic operation + r->fill_cnt++; // increase filled slots count, this should be atomic operation - r->fill_cnt++; // increase filled slots count, this should be atomic operation + *r->p_w++ = c; // put character into buffer + if (r->p_w >= r->p_o + r->size) // rollback if write pointer go pass + r->p_w = r->p_o; // the physical boundary - *r->p_w++ = c; // put character into buffer - - if (r->p_w >= r->p_o + r->size) // rollback if write pointer go pass - r->p_w = r->p_o; // the physical boundary - - return 0; + return 0; } + /** * \brief get a character from ring buffer * \param r pointer to a ringbuf object * \param c read character * \return 0 if successfull, otherwise failed */ -I16 ICACHE_FLASH_ATTR RINGBUF_Get(RINGBUF *r, U8* c) -{ - if (r->fill_cnt <= 0)return -1; // ring buffer is empty, this should be atomic operation +I16 ICACHE_FLASH_ATTR RINGBUF_Get(RINGBUF * r, U8 * c) { + if (r->fill_cnt <= 0) + return -1; // ring buffer is empty, this should be atomic operation + r->fill_cnt--; // decrease filled slots count - r->fill_cnt--; // decrease filled slots count + *c = *r->p_r++; // get the character out + if (r->p_r >= r->p_o + r->size) // rollback if write pointer go pass + r->p_r = r->p_o; // the physical boundary - *c = *r->p_r++; // get the character out - - if (r->p_r >= r->p_o + r->size) // rollback if write pointer go pass - r->p_r = r->p_o; // the physical boundary - - return 0; + return 0; } diff --git a/mqtt/strtok_r.c b/mqtt/strtok_r.c index f25c9c7..98ca8bd 100644 --- a/mqtt/strtok_r.c +++ b/mqtt/strtok_r.c @@ -40,48 +40,46 @@ #include "osapi.h" #include "mem.h" -char * -_strtok_r(char *s, const char *delim, char **last) -{ - char *spanp, *tok; - int c, sc; +char *_strtok_r(char *s, const char *delim, char **last) { + char *spanp, *tok; + int c, sc; - if (s == NULL && (s = *last) == NULL) - return (NULL); + if (s == NULL && (s = *last) == NULL) + return (NULL); - /* - * Skip (span) leading delimiters (s += strspn(s, delim), sort of). - */ -cont: + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ + cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0;) { + if (c == sc) + goto cont; + } + + if (c == 0) { /* no non-delimiter characters */ + *last = NULL; + return (NULL); + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) { c = *s++; - for (spanp = (char *)delim; (sc = *spanp++) != 0;) { - if (c == sc) - goto cont; - } - - if (c == 0) { /* no non-delimiter characters */ - *last = NULL; - return (NULL); - } - tok = s - 1; - - /* - * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). - * Note that delim must have one NUL; we stop if we see that, too. - */ - for (;;) { - c = *s++; - spanp = (char *)delim; - do { - if ((sc = *spanp++) == c) { - if (c == 0) - s = NULL; - else - s[-1] = '\0'; - *last = s; - return (tok); - } - } while (sc != 0); - } - /* NOTREACHED */ + spanp = (char *)delim; + do { + if ((sc = *spanp++) == c) { + if (c == 0) + s = NULL; + else + s[-1] = '\0'; + *last = s; + return (tok); + } + } while (sc != 0); + } + /* NOTREACHED */ } diff --git a/mqtt/utils.c b/mqtt/utils.c index ac4c927..d3ef700 100644 --- a/mqtt/utils.c +++ b/mqtt/utils.c @@ -37,113 +37,108 @@ #include #include "utils.h" +uint8_t ICACHE_FLASH_ATTR UTILS_IsIPV4(int8_t * str) { + uint8_t segs = 0; /* Segment count. */ + uint8_t chcnt = 0; /* Character count within segment. */ + uint8_t accum = 0; /* Accumulator for segment. */ + /* Catch NULL pointer. */ + if (str == 0) + return 0; + /* Process every character in string. */ -uint8_t ICACHE_FLASH_ATTR UTILS_IsIPV4 (int8_t *str) -{ - uint8_t segs = 0; /* Segment count. */ - uint8_t chcnt = 0; /* Character count within segment. */ - uint8_t accum = 0; /* Accumulator for segment. */ - /* Catch NULL pointer. */ - if (str == 0) - return 0; - /* Process every character in string. */ + while (*str != '\0') { + /* Segment changeover. */ - while (*str != '\0') { - /* Segment changeover. */ + if (*str == '.') { + /* Must have some digits in segment. */ + if (chcnt == 0) + return 0; + /* Limit number of segments. */ + if (++segs == 4) + return 0; + /* Reset segment values and restart loop. */ + chcnt = accum = 0; + str++; + continue; + } - if (*str == '.') { - /* Must have some digits in segment. */ - if (chcnt == 0) - return 0; - /* Limit number of segments. */ - if (++segs == 4) - return 0; - /* Reset segment values and restart loop. */ - chcnt = accum = 0; - str++; - continue; + /* Check numeric. */ + if ((*str < '0') || (*str > '9')) + return 0; + + /* Accumulate and check segment. */ + + if ((accum = accum * 10 + *str - '0') > 255) + return 0; + /* Advance other segment specific stuff and continue loop. */ + + chcnt++; + str++; } - /* Check numeric. */ - if ((*str < '0') || (*str > '9')) - return 0; + /* Check enough segments and enough characters in last segment. */ - /* Accumulate and check segment. */ + if (segs != 3) + return 0; + if (chcnt == 0) + return 0; + /* Address okay. */ - if ((accum = accum * 10 + *str - '0') > 255) - return 0; - /* Advance other segment specific stuff and continue loop. */ - - chcnt++; - str++; - } - - /* Check enough segments and enough characters in last segment. */ - - if (segs != 3) - return 0; - if (chcnt == 0) - return 0; - /* Address okay. */ - - return 1; + return 1; } -uint8_t ICACHE_FLASH_ATTR UTILS_StrToIP(const int8_t* str, void *ip) -{ +uint8_t ICACHE_FLASH_ATTR UTILS_StrToIP(const int8_t * str, void *ip) { - /* The count of the number of bytes processed. */ - int i; - /* A pointer to the next digit to process. */ - const char * start; + /* The count of the number of bytes processed. */ + int i; + /* A pointer to the next digit to process. */ + const char *start; - start = str; - for (i = 0; i < 4; i++) { - /* The digit being processed. */ - char c; - /* The value of this byte. */ - int n = 0; - while (1) { - c = * start; - start++; - if (c >= '0' && c <= '9') { - n *= 10; - n += c - '0'; - } - /* We insist on stopping at "." if we are still parsing - the first, second, or third numbers. If we have reached - the end of the numbers, we will allow any character. */ - else if ((i < 3 && c == '.') || i == 3) { - break; - } - else { - return 0; - } + start = str; + for (i = 0; i < 4; i++) { + /* The digit being processed. */ + char c; + /* The value of this byte. */ + int n = 0; + while (1) { + c = *start; + start++; + if (c >= '0' && c <= '9') { + n *= 10; + n += c - '0'; + } + /* We insist on stopping at "." if we are still parsing + the first, second, or third numbers. If we have reached + the end of the numbers, we will allow any character. */ + else if ((i < 3 && c == '.') || i == 3) { + break; + } else { + return 0; + } + } + if (n >= 256) { + return 0; + } + ((uint8_t *) ip)[i] = n; } - if (n >= 256) { - return 0; + return 1; + +} +uint32_t ICACHE_FLASH_ATTR UTILS_Atoh(const int8_t * s) { + uint32_t value = 0, digit; + int8_t c; + + while ((c = *s++)) { + if ('0' <= c && c <= '9') + digit = c - '0'; + else if ('A' <= c && c <= 'F') + digit = c - 'A' + 10; + else if ('a' <= c && c <= 'f') + digit = c - 'a' + 10; + else + break; + + value = (value << 4) | digit; } - ((uint8_t*)ip)[i] = n; - } - return 1; + return value; } -uint32_t ICACHE_FLASH_ATTR UTILS_Atoh(const int8_t *s) -{ - uint32_t value = 0, digit; - int8_t c; - - while ((c = *s++)) { - if ('0' <= c && c <= '9') - digit = c - '0'; - else if ('A' <= c && c <= 'F') - digit = c - 'A' + 10; - else if ('a' <= c && c <= 'f') - digit = c - 'a' + 10; - else break; - - value = (value << 4) | digit; - } - - return value; -} - diff --git a/ntp/ntp.c b/ntp/ntp.c index 6e15faf..baaa110 100644 --- a/ntp/ntp.c +++ b/ntp/ntp.c @@ -14,162 +14,161 @@ #define OFFSET 2208988800ULL -static ip_addr_t ntp_server_ip = {0}; +static ip_addr_t ntp_server_ip = { 0 }; + static os_timer_t ntp_timeout; static struct espconn *pCon, pConDNS; -static struct timeval t_tv = {0,0}; +static struct timeval t_tv = { 0, 0 }; + static uint64_t t_offset = 0; +static int16_t ntp_timezone = 0; -void ICACHE_FLASH_ATTR get_cur_time(struct timeval *tv) -{ - uint64_t t_curr = get_long_systime() - t_offset + t_tv.tv_usec; - tv->tv_sec = t_tv.tv_sec + t_curr/1000000; - tv->tv_usec = t_curr%1000000; +void ICACHE_FLASH_ATTR get_cur_time(struct timeval *tv) { + uint64_t t_curr = get_long_systime() - t_offset + t_tv.tv_usec; + tv->tv_sec = t_tv.tv_sec + t_curr / 1000000; + tv->tv_usec = t_curr % 1000000; } - -uint8_t* ICACHE_FLASH_ATTR get_timestr(int16_t timezone) -{ - struct timeval tv; - static uint8_t buf[10]; - - get_cur_time(&tv); - tv.tv_sec += timezone * 3600; - os_sprintf(buf, "%02d:%02d:%02d", (tv.tv_sec/3600)%24, (tv.tv_sec/60)%60, tv.tv_sec%60); - return buf; +void ICACHE_FLASH_ATTR set_timezone(int16_t timezone) { + ntp_timezone = timezone; } +uint8_t *ICACHE_FLASH_ATTR get_timestr() { + struct timeval tv; + static uint8_t buf[10]; -void ICACHE_FLASH_ATTR ntp_to_tv(uint8_t ntp[8], struct timeval *tv) -{ -uint64_t aux = 0; - - tv->tv_sec = ntohl(*(uint32_t*)ntp) - OFFSET; - - aux = ntohl(*(uint32_t*)&ntp[4]); - - // aux is the NTP fraction (0..2^32-1) - aux *= 1000000; // multiply by 1e6 - aux >>= 32; // and divide by 2^32 - tv->tv_usec = (uint32_t)aux; + get_cur_time(&tv); + tv.tv_sec += ntp_timezone * 3600; + os_sprintf(buf, "%02d:%02d:%02d", (tv.tv_sec / 3600) % 24, (tv.tv_sec / 60) % 60, tv.tv_sec % 60); + return buf; } +void ICACHE_FLASH_ATTR ntp_to_tv(uint8_t ntp[8], struct timeval *tv) { + uint64_t aux = 0; -LOCAL void ICACHE_FLASH_ATTR ntp_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) { -struct espconn *pespconn = (struct espconn *)arg; + tv->tv_sec = ntohl(*(uint32_t *) ntp) - OFFSET; - if (ipaddr != NULL) { - os_printf("Got NTP server: %d.%d.%d.%d\r\n", IP2STR(ipaddr)); - // Call the NTP update - ntp_server_ip.addr = ipaddr->addr; - ntp_get_time(); - } + aux = ntohl(*(uint32_t *) & ntp[4]); + + // aux is the NTP fraction (0..2^32-1) + aux *= 1000000; // multiply by 1e6 + aux >>= 32; // and divide by 2^32 + tv->tv_usec = (uint32_t) aux; } +LOCAL void ICACHE_FLASH_ATTR ntp_dns_found(const char *name, ip_addr_t * ipaddr, void *arg) { + struct espconn *pespconn = (struct espconn *)arg; + + if (ipaddr != NULL) { + os_printf("Got NTP server: %d.%d.%d.%d\r\n", IP2STR(ipaddr)); + // Call the NTP update + ntp_server_ip.addr = ipaddr->addr; + ntp_get_time(); + } +} static void ICACHE_FLASH_ATTR ntp_udp_timeout(void *arg) { - - os_timer_disarm(&ntp_timeout); - os_printf("NTP timout\r\n"); - // clean up connection - if (pCon) { - espconn_delete(pCon); - os_free(pCon->proto.udp); - os_free(pCon); - pCon = 0; - } + os_timer_disarm(&ntp_timeout); + os_printf("NTP timout\r\n"); + + // clean up connection + if (pCon) { + espconn_delete(pCon); + os_free(pCon->proto.udp); + os_free(pCon); + pCon = 0; + } } - static void ICACHE_FLASH_ATTR ntp_udp_recv(void *arg, char *pdata, unsigned short len) { -ntp_t *ntp; + ntp_t *ntp; -struct timeval tv; -int32_t hh, mm, ss; + struct timeval tv; + int32_t hh, mm, ss; - get_cur_time(&tv); + if (!ntp_sync_done()) + os_printf("NTP synced\r\n"); - // get the according sys_time; - t_offset = get_long_systime(); + get_cur_time(&tv); - os_timer_disarm(&ntp_timeout); + // get the according sys_time; + t_offset = get_long_systime(); - // extract ntp time - ntp = (ntp_t*)pdata; + os_timer_disarm(&ntp_timeout); - ntp_to_tv(ntp->trans_time, &t_tv); + // extract ntp time + ntp = (ntp_t *) pdata; - os_printf("NTP resync - diff: %d usecs\r\n", t_tv.tv_usec-tv.tv_usec); + ntp_to_tv(ntp->trans_time, &t_tv); + +// os_printf("NTP re-sync - diff: %d usecs\r\n", t_tv.tv_usec-tv.tv_usec); /* ss = t_tv.tv_sec%60; mm = (t_tv.tv_sec/60)%60; hh = (t_tv.tv_sec/3600)%24; os_printf("time: %2d:%02d:%02d\r\n", hh, mm, ss); */ - // clean up connection - if (pCon) { - espconn_delete(pCon); - os_free(pCon->proto.udp); - os_free(pCon); - pCon = 0; - } + // clean up connection + if (pCon) { + espconn_delete(pCon); + os_free(pCon->proto.udp); + os_free(pCon); + pCon = 0; + } } +void ICACHE_FLASH_ATTR ntp_set_server(uint8_t * ntp_server) { -void ICACHE_FLASH_ATTR ntp_set_server(uint8_t *ntp_server) { + ntp_server_ip.addr = 0; - ntp_server_ip.addr = 0; + // invalid arg? + if (ntp_server == NULL) + return; - // invalid arg? - if (ntp_server == NULL) - return; - - // ip or DNS name? - if (UTILS_IsIPV4(ntp_server)) { - // read address - UTILS_StrToIP(ntp_server, &ntp_server_ip); - ntp_get_time(); - } else { - // call DNS and wait for callback - espconn_gethostbyname(&pConDNS, ntp_server, &ntp_server_ip, ntp_dns_found); - } + // ip or DNS name? + if (UTILS_IsIPV4(ntp_server)) { + // read address + UTILS_StrToIP(ntp_server, &ntp_server_ip); + ntp_get_time(); + } else { + // call DNS and wait for callback + espconn_gethostbyname(&pConDNS, ntp_server, &ntp_server_ip, ntp_dns_found); + } } - bool ICACHE_FLASH_ATTR ntp_sync_done() { - return t_offset!=0; + return t_offset != 0; } - void ICACHE_FLASH_ATTR ntp_get_time() { -ntp_t ntp; + ntp_t ntp; - // either ongoing request or invalid ip? - if (pCon != 0 || ntp_server_ip.addr == 0) - return; + // either ongoing request or invalid ip? + if (pCon != 0 || ntp_server_ip.addr == 0) + return; - // set up the udp "connection" - pCon = (struct espconn*)os_zalloc(sizeof(struct espconn)); - pCon->type = ESPCONN_UDP; - pCon->state = ESPCONN_NONE; - pCon->proto.udp = (esp_udp*)os_zalloc(sizeof(esp_udp)); - pCon->proto.udp->local_port = espconn_port(); - pCon->proto.udp->remote_port = 123; - os_memcpy(pCon->proto.udp->remote_ip, &ntp_server_ip, 4); + // set up the udp "connection" + pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + pCon->type = ESPCONN_UDP; + pCon->state = ESPCONN_NONE; + pCon->proto.udp = (esp_udp *) os_zalloc(sizeof(esp_udp)); + pCon->proto.udp->local_port = espconn_port(); + pCon->proto.udp->remote_port = 123; + os_memcpy(pCon->proto.udp->remote_ip, &ntp_server_ip, 4); - // create a really simple ntp request packet - os_memset(&ntp, 0, sizeof(ntp_t)); - ntp.options = 0b00011011; // leap = 0, version = 3, mode = 3 (client) + // create a really simple ntp request packet + os_memset(&ntp, 0, sizeof(ntp_t)); + ntp.options = 0b00011011; // leap = 0, version = 3, mode = 3 (client) - // set timeout timer - os_timer_disarm(&ntp_timeout); - os_timer_setfn(&ntp_timeout, (os_timer_func_t*)ntp_udp_timeout, pCon); - os_timer_arm(&ntp_timeout, NTP_TIMEOUT_MS, 0); + // set timeout timer + os_timer_disarm(&ntp_timeout); + os_timer_setfn(&ntp_timeout, (os_timer_func_t *) ntp_udp_timeout, pCon); + os_timer_arm(&ntp_timeout, NTP_TIMEOUT_MS, 0); - // send the ntp request - espconn_create(pCon); - espconn_regist_recvcb(pCon, ntp_udp_recv); - espconn_sent(pCon, (uint8*)&ntp, sizeof(ntp_t)); + // send the ntp request + espconn_create(pCon); + espconn_regist_recvcb(pCon, ntp_udp_recv); + espconn_sent(pCon, (uint8 *) & ntp, sizeof(ntp_t)); } diff --git a/ntp/ntp.h b/ntp/ntp.h index 4774fd6..60e68be 100644 --- a/ntp/ntp.h +++ b/ntp/ntp.h @@ -22,6 +22,7 @@ void ntp_set_server(uint8_t *ntp_server); bool ntp_sync_done(); void ntp_get_time(); void get_cur_time(struct timeval *tv); -uint8_t *get_timestr(int16_t timezone); +void set_timezone(int16_t timezone); +uint8_t *get_timestr(); #endif diff --git a/user/config_flash.c b/user/config_flash.c index f9dbe1a..4179d15 100644 --- a/user/config_flash.c +++ b/user/config_flash.c @@ -1,108 +1,102 @@ #include "lwip/ip.h" #include "config_flash.h" - /* From the document 99A-SDK-Espressif IOT Flash RW Operation_v0.2 * * -------------------------------------------------------------------------* * Flash is erased sector by sector, which means it has to erase 4Kbytes one * time at least. When you want to change some data in flash, you have to * erase the whole sector, and then write it back with the new data. *--------------------------------------------------------------------------*/ -void config_load_default(sysconfig_p config) -{ -uint8_t mac[6]; +void config_load_default(sysconfig_p config) { + uint8_t mac[6]; os_memset(config, 0, sizeof(sysconfig_t)); os_printf("Loading default configuration\r\n"); - config->magic_number = MAGIC_NUMBER; - config->length = sizeof(sysconfig_t); - os_sprintf(config->ssid,"%s", WIFI_SSID); - os_sprintf(config->password,"%s", WIFI_PASSWORD); - config->auto_connect = 1; - os_sprintf(config->ap_ssid,"%s", WIFI_AP_SSID); - os_sprintf(config->ap_password,"%s",WIFI_AP_PASSWORD); - config->ap_open = 1; - config->ap_on = 1; - config->locked = 0; + config->magic_number = MAGIC_NUMBER; + config->length = sizeof(sysconfig_t); + os_sprintf(config->ssid, "%s", WIFI_SSID); + os_sprintf(config->password, "%s", WIFI_PASSWORD); + config->auto_connect = 1; + os_sprintf(config->ap_ssid, "%s", WIFI_AP_SSID); + os_sprintf(config->ap_password, "%s", WIFI_AP_PASSWORD); + config->ap_open = 1; + config->ap_on = 1; + + config->locked = 0; + config->lock_password[0] = '\0'; IP4_ADDR(&config->network_addr, 192, 168, 4, 1); - config->dns_addr.addr = 0; // use DHCP - config->my_addr.addr = 0; // use DHCP - config->my_netmask.addr = 0; // use DHCP - config->my_gw.addr = 0; // use DHCP + config->dns_addr.addr = 0; // use DHCP + config->my_addr.addr = 0; // use DHCP + config->my_netmask.addr = 0; // use DHCP + config->my_gw.addr = 0; // use DHCP - config->clock_speed = 80; - config->config_port = CONSOLE_SERVER_PORT; + config->clock_speed = 80; + config->config_port = CONSOLE_SERVER_PORT; #ifdef MQTT_CLIENT - os_sprintf(config->mqtt_host,"%s", "none"); - config->mqtt_port = 1883; - os_sprintf(config->mqtt_user,"%s", "none"); - config->mqtt_password[0] = 0; + os_sprintf(config->mqtt_host, "%s", "none"); + config->mqtt_port = 1883; + os_sprintf(config->mqtt_user, "%s", "none"); + config->mqtt_password[0] = 0; wifi_get_macaddr(0, mac); - os_sprintf(config->mqtt_id,"%s_%2x%2x%2x", MQTT_ID, mac[3], mac[4], mac[5]); + os_sprintf(config->mqtt_id, "%s_%2x%2x%2x", MQTT_ID, mac[3], mac[4], mac[5]); #endif #ifdef NTP - os_sprintf(config->ntp_server,"%s", "none"); - config->ntp_interval = 60000000; - config->ntp_timezone = 0; + os_sprintf(config->ntp_server, "%s", "1.pool.ntp.org"); + config->ntp_interval = 300000000; + config->ntp_timezone = 0; #endif } -int config_load(sysconfig_p config) -{ - if (config == NULL) return -1; +int config_load(sysconfig_p config) { + if (config == NULL) + return -1; uint16_t base_address = FLASH_BLOCK_NO; - spi_flash_read(base_address* SPI_FLASH_SEC_SIZE, &config->magic_number, 4); + spi_flash_read(base_address * SPI_FLASH_SEC_SIZE, &config->magic_number, 4); - if((config->magic_number != MAGIC_NUMBER)) - { - os_printf("\r\nNo config found, saving default in flash\r\n"); - config_load_default(config); - config_save(config); - return -1; + if ((config->magic_number != MAGIC_NUMBER)) { + os_printf("\r\nNo config found, saving default in flash\r\n"); + config_load_default(config); + config_save(config); + return -1; } os_printf("\r\nConfig found and loaded\r\n"); spi_flash_read(base_address * SPI_FLASH_SEC_SIZE, (uint32 *) config, sizeof(sysconfig_t)); - if (config->length != sizeof(sysconfig_t)) - { - os_printf("Length Mismatch, probably old version of config, loading defaults\r\n"); - config_load_default(config); - config_save(config); + if (config->length != sizeof(sysconfig_t)) { + os_printf("Length Mismatch, probably old version of config, loading defaults\r\n"); + config_load_default(config); + config_save(config); return -1; } return 0; } -void config_save(sysconfig_p config) -{ +void config_save(sysconfig_p config) { uint16_t base_address = FLASH_BLOCK_NO; os_printf("Saving configuration\r\n"); spi_flash_erase_sector(base_address); - spi_flash_write(base_address * SPI_FLASH_SEC_SIZE, (uint32 *)config, sizeof(sysconfig_t)); + spi_flash_write(base_address * SPI_FLASH_SEC_SIZE, (uint32 *) config, sizeof(sysconfig_t)); } -void blob_save(uint8_t blob_no, uint32_t *data, uint16_t len) -{ +void blob_save(uint8_t blob_no, uint32_t * data, uint16_t len) { uint16_t base_address = FLASH_BLOCK_NO + 1 + blob_no; spi_flash_erase_sector(base_address); spi_flash_write(base_address * SPI_FLASH_SEC_SIZE, data, len); } -void blob_load(uint8_t blob_no, uint32_t *data, uint16_t len) -{ +void blob_load(uint8_t blob_no, uint32_t * data, uint16_t len) { uint16_t base_address = FLASH_BLOCK_NO + 1 + blob_no; spi_flash_read(base_address * SPI_FLASH_SEC_SIZE, data, len); } -void blob_zero(uint8_t blob_no, uint16_t len) -{ -int i; +void blob_zero(uint8_t blob_no, uint16_t len) { + int i; uint8_t z[len]; - os_memset(z, 0,len); + os_memset(z, 0, len); uint16_t base_address = FLASH_BLOCK_NO + 1 + blob_no; spi_flash_erase_sector(base_address); - spi_flash_write(base_address * SPI_FLASH_SEC_SIZE, (uint32_t *)z, len); + spi_flash_write(base_address * SPI_FLASH_SEC_SIZE, (uint32_t *) z, len); } diff --git a/user/config_flash.h b/user/config_flash.h index 59beff0..6e405ec 100644 --- a/user/config_flash.h +++ b/user/config_flash.h @@ -35,6 +35,7 @@ typedef struct uint8_t ap_on; // AP enabled? uint8_t locked; // Should we allow for config changes + uint8_t lock_password[32]; // Password of config lock ip_addr_t network_addr; // Address of the internal network ip_addr_t dns_addr; // Optional: address of the dns server diff --git a/user/demo_script b/user/demo_script new file mode 100644 index 0000000..29f8375 --- /dev/null +++ b/user/demo_script @@ -0,0 +1,49 @@ +% 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 + +% Now the events, checked whenever something happens + +% Here a remote republish, of any local topic starting with "/test/" +on topic local /test/# +do + publish remote $this_topic $this_data + +% When timer 1 expires, do some stuff +on timer 1 +do + % publish a timestamp locally + publish local /t/time $timestamp + + % Let the LED on GPIO 2 blink + gpio_out 2 $1 + setvar $1 not $1 + + % Count occurences in var $2 + setvar $2 $2 add 1 + + % And if we have reached 10, print that to the console + if $2 gte 10 then + print "We have reached " + println $2 + setvar $2 0 + 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" diff --git a/user/lang.c b/user/lang.c new file mode 100644 index 0000000..37b44a8 --- /dev/null +++ b/user/lang.c @@ -0,0 +1,800 @@ +#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(×tamps, 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; +} diff --git a/user/lang.h b/user/lang.h new file mode 100644 index 0000000..3b39260 --- /dev/null +++ b/user/lang.h @@ -0,0 +1,29 @@ +#include "mqtt_server.h" + +extern MQTT_Client mqttClient; +extern bool mqtt_enabled, mqtt_connected; +uint8_t syntax_error_buffer[128]; + +typedef enum {SYNTAX_CHECK, CONFIG, INIT, RE_INIT, TOPIC_LOCAL, TOPIC_REMOTE, TIMER, CLOCK} Interpreter_Status; +typedef enum {STRING_T, DATA_T} Value_Type; + +int text_into_tokens(char *str); +void free_tokens(void); +bool is_token(int i, char *s); +int search_token(int i, char *s); +int syntax_error(int i, char *message); + +int parse_statement(int next_token); +int parse_event(int next_token, bool *happened); +int parse_action(int next_token, bool doit); +int parse_value(int next_token, char **data, int *data_len, Value_Type *data_type); + +extern bool script_enabled; +int interpreter_syntax_check(); +int interpreter_config(); +int interpreter_init(); +int interpreter_init_reconnect(void); +int interpreter_topic_received(const char *topic, const char *data, int data_len, bool local); + +void init_timestamps(uint8_t *curr_time); +void check_timestamps(uint8_t *curr_time); diff --git a/user/rfinit.c b/user/rfinit.c index 64aabf2..cd288ae 100644 --- a/user/rfinit.c +++ b/user/rfinit.c @@ -25,52 +25,50 @@ * Parameters : none * Returns : rf cal sector *******************************************************************************/ -uint32 ICACHE_FLASH_ATTR __attribute__((weak)) -user_rf_cal_sector_set(void) -{ - enum flash_size_map size_map = system_get_flash_size_map(); - uint32 rf_cal_sec = 0; +uint32 ICACHE_FLASH_ATTR __attribute__ ((weak)) + user_rf_cal_sector_set(void) { + enum flash_size_map size_map = system_get_flash_size_map(); + uint32 rf_cal_sec = 0; - switch (size_map) { + switch (size_map) { case FLASH_SIZE_4M_MAP_256_256: - rf_cal_sec = 128 - 5; - break; + rf_cal_sec = 128 - 5; + break; case FLASH_SIZE_8M_MAP_512_512: - rf_cal_sec = 256 - 5; - break; + rf_cal_sec = 256 - 5; + break; case FLASH_SIZE_16M_MAP_512_512: case FLASH_SIZE_16M_MAP_1024_1024: - rf_cal_sec = 512 - 5; - break; + rf_cal_sec = 512 - 5; + break; case FLASH_SIZE_32M_MAP_512_512: case FLASH_SIZE_32M_MAP_1024_1024: - rf_cal_sec = 1024 - 5; - break; + rf_cal_sec = 1024 - 5; + break; default: - rf_cal_sec = 0; - break; - } + rf_cal_sec = 0; + break; + } - return rf_cal_sec; + return rf_cal_sec; } -void __attribute__((weak)) -user_rf_pre_init(void) -{ - // Warning: IF YOU DON'T KNOW WHAT YOU ARE DOING, DON'T TOUCH THESE CODE +void __attribute__ ((weak)) + user_rf_pre_init(void) { + // Warning: IF YOU DON'T KNOW WHAT YOU ARE DOING, DON'T TOUCH THESE CODE - // Control RF_CAL by esp_init_data_default.bin(0~127byte) 108 byte when wakeup - // Will low current - // system_phy_set_rfoption(0)ï¼› + // Control RF_CAL by esp_init_data_default.bin(0~127byte) 108 byte when wakeup + // Will low current + // system_phy_set_rfoption(0)ï¼› - // Process RF_CAL when wakeup. - // Will high current - system_phy_set_rfoption(1); + // Process RF_CAL when wakeup. + // Will high current + system_phy_set_rfoption(1); - // Set Wi-Fi Tx Power, Unit: 0.25dBm, Range: [0, 82] - system_phy_set_max_tpw(82); + // Set Wi-Fi Tx Power, Unit: 0.25dBm, Range: [0, 82] + system_phy_set_max_tpw(82); } diff --git a/user/ringbuf.c b/user/ringbuf.c index 7668b5e..3a8670a 100644 --- a/user/ringbuf.c +++ b/user/ringbuf.c @@ -35,56 +35,45 @@ * intended. */ -struct ringbuf_t -{ +struct ringbuf_t { uint8_t *buf; uint8_t *head, *tail; size_t size; }; -ringbuf_t -ringbuf_new(size_t capacity) -{ - ringbuf_t rb = (ringbuf_t)os_malloc(sizeof(struct ringbuf_t)); +ringbuf_t ringbuf_new(size_t capacity) { + ringbuf_t rb = (ringbuf_t) os_malloc(sizeof(struct ringbuf_t)); if (rb) { - /* One byte is used for detecting the full condition. */ - rb->size = capacity + 1; - rb->buf = (uint8_t *)os_malloc(rb->size); - if (rb->buf) - ringbuf_reset(rb); - else { - os_free(rb); - return 0; - } + /* One byte is used for detecting the full condition. */ + rb->size = capacity + 1; + rb->buf = (uint8_t *) os_malloc(rb->size); + if (rb->buf) + ringbuf_reset(rb); + else { + os_free(rb); + return 0; + } } return rb; } -size_t -ringbuf_buffer_size(const struct ringbuf_t *rb) -{ +size_t ringbuf_buffer_size(const struct ringbuf_t * rb) { return rb->size; } -void -ringbuf_reset(ringbuf_t rb) -{ +void ringbuf_reset(ringbuf_t rb) { rb->head = rb->tail = rb->buf; } -void -ringbuf_free(ringbuf_t *rb) -{ +void ringbuf_free(ringbuf_t * rb) { assert(rb && *rb); os_free((*rb)->buf); os_free(*rb); *rb = 0; } -size_t -ringbuf_capacity(const struct ringbuf_t *rb) -{ +size_t ringbuf_capacity(const struct ringbuf_t *rb) { return ringbuf_buffer_size(rb) - 1; } @@ -93,48 +82,34 @@ ringbuf_capacity(const struct ringbuf_t *rb) * contiguous buffer. You shouldn't normally need to use this function * unless you're writing a new ringbuf_* function. */ -static const uint8_t * -ringbuf_end(const struct ringbuf_t *rb) -{ +static const uint8_t *ringbuf_end(const struct ringbuf_t *rb) { return rb->buf + ringbuf_buffer_size(rb); } -size_t -ringbuf_bytes_free(const struct ringbuf_t *rb) -{ +size_t ringbuf_bytes_free(const struct ringbuf_t * rb) { if (rb->head >= rb->tail) - return ringbuf_capacity(rb) - (rb->head - rb->tail); + return ringbuf_capacity(rb) - (rb->head - rb->tail); else - return rb->tail - rb->head - 1; + return rb->tail - rb->head - 1; } -size_t -ringbuf_bytes_used(const struct ringbuf_t *rb) -{ +size_t ringbuf_bytes_used(const struct ringbuf_t * rb) { return ringbuf_capacity(rb) - ringbuf_bytes_free(rb); } -int -ringbuf_is_full(const struct ringbuf_t *rb) -{ +int ringbuf_is_full(const struct ringbuf_t *rb) { return ringbuf_bytes_free(rb) == 0; } -int -ringbuf_is_empty(const struct ringbuf_t *rb) -{ +int ringbuf_is_empty(const struct ringbuf_t *rb) { return ringbuf_bytes_free(rb) == ringbuf_capacity(rb); } -const void * -ringbuf_tail(const struct ringbuf_t *rb) -{ +const void *ringbuf_tail(const struct ringbuf_t *rb) { return rb->tail; } -const void * -ringbuf_head(const struct ringbuf_t *rb) -{ +const void *ringbuf_head(const struct ringbuf_t *rb) { return rb->head; } @@ -143,9 +118,7 @@ ringbuf_head(const struct ringbuf_t *rb) * contiguous buffer, return the a pointer to the next logical * location in the ring buffer. */ -static uint8_t * -ringbuf_nextp(ringbuf_t rb, const uint8_t *p) -{ +static uint8_t *ringbuf_nextp(ringbuf_t rb, const uint8_t * p) { /* * The assert guarantees the expression (++p - rb->buf) is * non-negative; therefore, the modulus operation is safe and @@ -155,96 +128,89 @@ ringbuf_nextp(ringbuf_t rb, const uint8_t *p) return rb->buf + ((++p - rb->buf) % ringbuf_buffer_size(rb)); } -void * -ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count) -{ +void *ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count) { const uint8_t *u8src = src; const uint8_t *bufend = ringbuf_end(dst); int overflow = count > ringbuf_bytes_free(dst); size_t nread = 0; while (nread != count) { - /* don't copy beyond the end of the buffer */ - assert(bufend > dst->head); - size_t n = MIN(bufend - dst->head, count - nread); - os_memcpy(dst->head, u8src + nread, n); - dst->head += n; - nread += n; + /* don't copy beyond the end of the buffer */ + assert(bufend > dst->head); + size_t n = MIN(bufend - dst->head, count - nread); + os_memcpy(dst->head, u8src + nread, n); + dst->head += n; + nread += n; - /* wrap? */ - if (dst->head == bufend) - dst->head = dst->buf; + /* wrap? */ + if (dst->head == bufend) + dst->head = dst->buf; } if (overflow) { - dst->tail = ringbuf_nextp(dst, dst->head); - assert(ringbuf_is_full(dst)); + dst->tail = ringbuf_nextp(dst, dst->head); + assert(ringbuf_is_full(dst)); } return dst->head; } -void * -ringbuf_memcpy_from(void *dst, ringbuf_t src, size_t count) -{ +void *ringbuf_memcpy_from(void *dst, ringbuf_t src, size_t count) { size_t bytes_used = ringbuf_bytes_used(src); if (count > bytes_used) - return 0; + return 0; uint8_t *u8dst = dst; const uint8_t *bufend = ringbuf_end(src); size_t nwritten = 0; while (nwritten != count) { - assert(bufend > src->tail); - size_t n = MIN(bufend - src->tail, count - nwritten); - os_memcpy(u8dst + nwritten, src->tail, n); - src->tail += n; - nwritten += n; + assert(bufend > src->tail); + size_t n = MIN(bufend - src->tail, count - nwritten); + os_memcpy(u8dst + nwritten, src->tail, n); + src->tail += n; + nwritten += n; - /* wrap ? */ - if (src->tail == bufend) - src->tail = src->buf; + /* wrap ? */ + if (src->tail == bufend) + src->tail = src->buf; } assert(count + ringbuf_bytes_used(src) == bytes_used); return src->tail; } -void * -ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count) -{ +void *ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count) { size_t src_bytes_used = ringbuf_bytes_used(src); if (count > src_bytes_used) - return 0; + return 0; int overflow = count > ringbuf_bytes_free(dst); const uint8_t *src_bufend = ringbuf_end(src); const uint8_t *dst_bufend = ringbuf_end(dst); size_t ncopied = 0; while (ncopied != count) { - assert(src_bufend > src->tail); - size_t nsrc = MIN(src_bufend - src->tail, count - ncopied); - assert(dst_bufend > dst->head); - size_t n = MIN(dst_bufend - dst->head, nsrc); - os_memcpy(dst->head, src->tail, n); - src->tail += n; - dst->head += n; - ncopied += n; + assert(src_bufend > src->tail); + size_t nsrc = MIN(src_bufend - src->tail, count - ncopied); + assert(dst_bufend > dst->head); + size_t n = MIN(dst_bufend - dst->head, nsrc); + os_memcpy(dst->head, src->tail, n); + src->tail += n; + dst->head += n; + ncopied += n; - /* wrap ? */ - if (src->tail == src_bufend) - src->tail = src->buf; - if (dst->head == dst_bufend) - dst->head = dst->buf; + /* wrap ? */ + if (src->tail == src_bufend) + src->tail = src->buf; + if (dst->head == dst_bufend) + dst->head = dst->buf; } assert(count + ringbuf_bytes_used(src) == src_bytes_used); - + if (overflow) { - dst->tail = ringbuf_nextp(dst, dst->head); - assert(ringbuf_is_full(dst)); + dst->tail = ringbuf_nextp(dst, dst->head); + assert(ringbuf_is_full(dst)); } return dst->head; } - diff --git a/user/sys_time.c b/user/sys_time.c index 183a2c9..3961616 100644 --- a/user/sys_time.c +++ b/user/sys_time.c @@ -2,31 +2,29 @@ #include "osapi.h" typedef union _timer { -uint32_t time_s[2]; -uint64_t time_l; + uint32_t time_s[2]; + uint64_t time_l; } long_time_t; static long_time_t time; static uint32_t old; uint64_t ICACHE_FLASH_ATTR get_long_systime() { - uint32_t now = system_get_time(); - if (now < old) { - time.time_s[1]++; - } - old = now; - time.time_s[0] = now; - return time.time_l; + uint32_t now = system_get_time(); + if (now < old) { + time.time_s[1]++; + } + old = now; + time.time_s[0] = now; + return time.time_l; } uint64_t ICACHE_FLASH_ATTR get_low_systime() { - get_long_systime(); - return time.time_s[0]; + get_long_systime(); + return time.time_s[0]; } void init_long_systime() { - old = system_get_time(); - time.time_l = (uint64_t)old; + old = system_get_time(); + time.time_l = (uint64_t) old; } - - diff --git a/user/user_config.h b/user/user_config.h index 65e15cb..e20aae9 100644 --- a/user/user_config.h +++ b/user/user_config.h @@ -1,7 +1,7 @@ #ifndef _USER_CONFIG_ #define _USER_CONFIG_ -typedef enum {SIG_DO_NOTHING=0, SIG_START_SERVER=1, SIG_SEND_DATA, SIG_UART0, SIG_CONSOLE_RX, SIG_CONSOLE_TX, SIG_GPIO_INT} USER_SIGNALS; +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; #define WIFI_SSID "ssid" #define WIFI_PASSWORD "password" @@ -26,6 +26,20 @@ typedef enum {SIG_DO_NOTHING=0, SIG_START_SERVER=1, SIG_SEND_DATA, SIG_UART0, SI #define MQTT_ID "ESPBroker" +// +// Define this if you want to have script support. +// +#define SCRIPTED 1 +// Define this if you want to have GPIO OUT support in scripts. +#define GPIO 1 +// Some params for scripts + +#define MAX_SCRIPT_SIZE 0x1000 +#define MAX_TIMERS 4 +#define MAX_VARS 6 +#define MAX_VAR_LEN 64 +#define MAX_TIMESTAMPS 6 + // // Define this if you want to have NTP support. // @@ -44,7 +58,7 @@ typedef enum {SIG_DO_NOTHING=0, SIG_START_SERVER=1, SIG_SEND_DATA, SIG_UART0, SI // // Define this if you want to have access to the config console via TCP. -// Ohterwise only local access via serial is possible +// Otherwise only local access via serial is possible // #define REMOTE_CONFIG 1 #define CONSOLE_SERVER_PORT 7777 diff --git a/user/user_main.c b/user/user_main.c index a5282b4..beb72bc 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -23,13 +23,23 @@ uint64_t t_ntp_resync = 0; #endif +#ifdef SCRIPTED +#include "lang.h" + +struct espconn *downloadCon; +struct espconn *scriptcon; +uint8_t *load_script; +uint32_t load_size; +bool timestamps_init; +#endif + /* System Task, for signals refer to user_config.h */ #define user_procTaskPrio 0 #define user_procTaskQueueLen 1 -os_event_t user_procTaskQueue[user_procTaskQueueLen]; -static void user_procTask(os_event_t *events); +os_event_t user_procTaskQueue[user_procTaskQueueLen]; +static void user_procTask(os_event_t * events); -static os_timer_t ptimer; +static os_timer_t ptimer; /* Some stats */ uint64_t t_old; @@ -45,181 +55,271 @@ bool connected; uint8_t my_channel; bool do_ip_config; -uint8_t remote_console_disconnect; - void ICACHE_FLASH_ATTR user_set_softap_wifi_config(void); void ICACHE_FLASH_ATTR user_set_softap_ip_config(void); +uint8_t remote_console_disconnect; +struct espconn *console_conn; +bool client_sent_pending; + + +void ICACHE_FLASH_ATTR to_console(char *str) { + ringbuf_memcpy_into(console_tx_buffer, str, os_strlen(str)); +} + #ifdef MQTT_CLIENT MQTT_Client mqttClient; bool mqtt_enabled, mqtt_connected; -static void ICACHE_FLASH_ATTR mqttConnectedCb(uint32_t *args) -{ -uint8_t ip_str[16]; +static void ICACHE_FLASH_ATTR mqttConnectedCb(uint32_t * args) { + uint8_t ip_str[16]; - MQTT_Client* client = (MQTT_Client*)args; - mqtt_connected = true; - os_printf("MQTT client connected\r\n"); + MQTT_Client *client = (MQTT_Client *) args; + mqtt_connected = true; +#ifdef SCRIPTED + interpreter_init_reconnect(); +#endif + os_printf("MQTT client connected\r\n"); } -static void ICACHE_FLASH_ATTR mqttDisconnectedCb(uint32_t *args) -{ - MQTT_Client* client = (MQTT_Client*)args; - mqtt_connected = false; - os_printf("MQTT client disconnected\r\n"); +static void ICACHE_FLASH_ATTR mqttDisconnectedCb(uint32_t * args) { + MQTT_Client *client = (MQTT_Client *) args; + mqtt_connected = false; + os_printf("MQTT client disconnected\r\n"); } -static void ICACHE_FLASH_ATTR mqttPublishedCb(uint32_t *args) -{ - MQTT_Client* client = (MQTT_Client*)args; +static void ICACHE_FLASH_ATTR mqttPublishedCb(uint32_t * args) { + MQTT_Client *client = (MQTT_Client *) args; // os_printf("MQTT: Published\r\n"); } -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; - } +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]; - strncpy(buffer, topic, topic_len); - buffer[topic_len] = 0; - MQTT_local_publish(buffer, (uint8_t*)data, data_len, 0, 0); + 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); +#endif } -#endif /* MQTT_CLIENT */ +#endif /* MQTT_CLIENT */ -int parse_str_into_tokens(char *str, char **tokens, int max_tokens) -{ -char *p, *q; -int token_count = 0; -bool in_token = false; +#ifdef SCRIPTED +static void ICACHE_FLASH_ATTR script_recv_cb(void *arg, char *data, unsigned short length) { + struct espconn *pespconn = (struct espconn *)arg; + int index; + uint8_t ch; - // preprocessing - for (p = q = str; *p != 0; p++) { + for (index = 0; index < length; index++) { + ch = *(data + index); + //os_printf("%c", ch); + if (load_size < MAX_SCRIPT_SIZE - 5) + load_script[4 + load_size++] = ch; + } +} + +static void ICACHE_FLASH_ATTR script_discon_cb(void *arg) { + char response[64]; + + load_script[4 + load_size] = '\0'; + *(uint32_t *) load_script = load_size + 5; + blob_save(0, (uint32_t *) load_script, load_size + 5); + os_free(load_script); + + os_sprintf(response, "\rScript upload completed (%d Bytes)\r\n", load_size); + to_console(response); + + system_os_post(user_procTaskPrio, SIG_SCRIPT_LOADED, (ETSParam) scriptcon); +} + +/* Called when a client connects to the script server */ +static void ICACHE_FLASH_ATTR script_connected_cb(void *arg) { + char response[64]; + struct espconn *pespconn = (struct espconn *)arg; + + load_script = (uint8_t *) os_malloc(MAX_SCRIPT_SIZE); + load_size = 0; + + //espconn_regist_sentcb(pespconn, tcp_client_sent_cb); + espconn_regist_disconcb(pespconn, script_discon_cb); + espconn_regist_recvcb(pespconn, script_recv_cb); + espconn_regist_time(pespconn, 300, 1); +} + +uint32_t ICACHE_FLASH_ATTR get_script_size(void) { + uint32_t size; + + blob_load(0, &size, 4); + return size; +} + +static uint8_t *my_script = NULL; +uint32_t ICACHE_FLASH_ATTR read_script(void) { + uint32_t size = get_script_size(); + if (size <= 5) + return 0; + + my_script = (uint8_t *) os_malloc(size); + + if (my_script == 0) { + os_printf("Out of memory"); + return 0; + } + + blob_load(0, (uint32_t *) my_script, size); + + uint32_t num_token = text_into_tokens(my_script + 4); + + if (num_token == 0) { + os_free(my_script); + my_script = NULL; + } + return num_token; +} + +void ICACHE_FLASH_ATTR free_script(void) { + if (my_script != NULL) { + free_tokens(); + os_free(my_script); + } + my_script = NULL; +} +#endif /* SCRIPTED */ + +int parse_str_into_tokens(char *str, char **tokens, int max_tokens) { + char *p, *q; + int token_count = 0; + bool in_token = false; + + // preprocessing + for (p = q = str; *p != 0; p++) { if (*p == '\\') { - // next char is quoted, copy it skip, this one - if (*(p+1) != 0) *q++ = *++p; + // next char is quoted, copy it skip, this one + if (*(p + 1) != 0) + *q++ = *++p; } else if (*p == 8) { - // backspace - delete previous char - if (q != str) q--; + // backspace - delete previous char + if (q != str) + q--; } else if (*p <= ' ') { - // mark this as whitespace - *q++ = 1; + // mark this as whitespace + *q++ = 1; } else { - *q++ = *p; + *q++ = *p; } - } + } - *q = 0; + *q = 0; - // cut into tokens - for (p = str; *p != 0; p++) { + // cut into tokens + for (p = str; *p != 0; p++) { if (*p == 1) { - if (in_token) { + if (in_token) { *p = 0; in_token = false; - } + } } else { - if (*p & 0x80) *p &= 0x7f; - if (!in_token) { + if (*p & 0x80) + *p &= 0x7f; + if (!in_token) { tokens[token_count++] = p; if (token_count == max_tokens) - return token_count; + return token_count; in_token = true; - } + } } - } - return token_count; + } + return token_count; } - -void console_send_response(struct espconn *pespconn) -{ - char payload[MAX_CON_SEND_SIZE+4]; +void console_send_response(struct espconn *pespconn) { + char payload[MAX_CON_SEND_SIZE]; uint16_t len = ringbuf_bytes_used(console_tx_buffer); + if (len == 0) + return; + ringbuf_memcpy_from(payload, console_tx_buffer, len); - os_memcpy(&payload[len], "CMD>", 4); - - if (pespconn != NULL) - espconn_sent(pespconn, payload, len+4); - else - UART_Send(0, &payload, len+4); + if (pespconn != NULL) { + if (!client_sent_pending) { + espconn_sent(pespconn, payload, len); + client_sent_pending = true; + } + } else { + UART_Send(0, &payload, len); + } } - #ifdef ALLOW_SCANNING -struct espconn *scanconn; -void ICACHE_FLASH_ATTR scan_done(void *arg, STATUS status) -{ - char response[128]; +void ICACHE_FLASH_ATTR scan_done(void *arg, STATUS status) { + char response[128]; - if (status == OK) - { - struct bss_info *bss_link = (struct bss_info *)arg; + if (status == OK) { + struct bss_info *bss_link = (struct bss_info *)arg; - ringbuf_memcpy_into(console_tx_buffer, "\r", 1); - while (bss_link != NULL) - { - os_sprintf(response, "%d,\"%s\",%d,\""MACSTR"\",%d\r\n", - bss_link->authmode, bss_link->ssid, bss_link->rssi, - MAC2STR(bss_link->bssid),bss_link->channel); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - bss_link = bss_link->next.stqe_next; + ringbuf_memcpy_into(console_tx_buffer, "\r", 1); + while (bss_link != NULL) { + os_sprintf(response, "%d,\"%s\",%d,\"" MACSTR "\",%d\r\n", + bss_link->authmode, bss_link->ssid, bss_link->rssi, MAC2STR(bss_link->bssid), bss_link->channel); + to_console(response); + bss_link = bss_link->next.stqe_next; + } + } else { + os_sprintf(response, "scan fail !!!\r\n"); + to_console(response); } - } - else - { - os_sprintf(response, "scan fail !!!\r\n"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - } - system_os_post(0, SIG_CONSOLE_TX, (ETSParam) scanconn); + system_os_post(user_procTaskPrio, SIG_CONSOLE_TX, (ETSParam) console_conn); } #endif -bool ICACHE_FLASH_ATTR printf_topic(topic_entry *topic, void *user_data) -{ - uint8_t *response = (uint8_t *)user_data; - - os_sprintf(response, "%s: \"%s\" (QoS %d)\r\n", - topic->clientcon!=LOCAL_MQTT_CLIENT?topic->clientcon->connect_info.client_id:"local", topic->topic, topic->qos); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - return false; +void ICACHE_FLASH_ATTR con_print(uint8_t *str) { + ringbuf_memcpy_into(console_tx_buffer, str, os_strlen(str)); +// console_send_response(console_conn); + system_os_post(user_procTaskPrio, SIG_CONSOLE_TX_RAW, (ETSParam) console_conn); } -bool ICACHE_FLASH_ATTR printf_retainedtopic(retained_entry *entry, void *user_data) -{ - uint8_t *response = (uint8_t *)user_data; +bool ICACHE_FLASH_ATTR printf_topic(topic_entry * topic, void *user_data) { + uint8_t *response = (uint8_t *) user_data; - os_sprintf(response, "\"%s\" len: %d (QoS %d)\r\n", entry->topic, entry->data_len, entry->qos); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - return false; + os_sprintf(response, "%s: \"%s\" (QoS %d)\r\n", + topic->clientcon != + LOCAL_MQTT_CLIENT ? topic->clientcon->connect_info.client_id : "local", topic->topic, topic->qos); + to_console(response); + return false; } -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 MQTT_CLIENT - if (mqtt_connected) { - MQTT_Publish(&mqttClient, topic, data, length, 0, 0); - } +bool ICACHE_FLASH_ATTR printf_retainedtopic(retained_entry * entry, void *user_data) { + uint8_t *response = (uint8_t *) user_data; + + os_sprintf(response, "\"%s\" len: %d (QoS %d)\r\n", entry->topic, entry->data_len, entry->qos); + to_console(response); + return false; +} + +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); #endif } + static char INVALID_LOCKED[] = "Invalid command. Config locked\r\n"; static char INVALID_NUMARGS[] = "Invalid number of arguments\r\n"; static char INVALID_ARG[] = "Invalid argument\r\n"; -void ICACHE_FLASH_ATTR console_handle_command(struct espconn *pespconn) -{ +void ICACHE_FLASH_ATTR console_handle_command(struct espconn *pespconn) { #define MAX_CMD_TOKENS 4 - char cmd_line[MAX_CON_CMD_SIZE+1]; + char cmd_line[MAX_CON_CMD_SIZE + 1]; char response[256]; char *tokens[MAX_CMD_TOKENS]; @@ -239,549 +339,599 @@ void ICACHE_FLASH_ATTR console_handle_command(struct espconn *pespconn) goto command_handled_2; } - if (strcmp(tokens[0], "help") == 0) - { - os_sprintf(response, "show [config|stats|mqtt|mqtt_broker]\r\n|set [ssid|password|auto_connect|ap_ssid|ap_password|network|dns|ip|netmask|gw|ap_on|ap_open|speed|config_port] \r\n|quit|save [config]|reset [factory]|lock|unlock |publish |subscribe [local|remote] |unsubscribe [local|remote] "); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); + if (strcmp(tokens[0], "help") == 0) { + os_sprintf(response, + "show [config|stats|mqtt|script]\r\n|set [ssid|password|auto_connect|ap_ssid|ap_password|network|dns|ip|netmask|gw|ap_on|ap_open|speed|config_port] \r\n|quit|save [config]|reset [factory]|lock []|unlock "); + to_console(response); +#ifdef SCRIPTED + os_sprintf(response, "|script "); + to_console(response); +#endif #ifdef ALLOW_SCANNING - os_sprintf(response, "|scan"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); + os_sprintf(response, "|scan"); + to_console(response); #endif #ifdef NTP - os_sprintf(response, "|time|set ntp_server |set ntp_interval |set \r\n"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); + os_sprintf(response, "|time|set ntp_server |set ntp_interval |set \r\n"); + to_console(response); #endif #ifdef MQTT_CLIENT - os_sprintf(response, "|set [mqtt_host|mqtt_port|mqtt_user|mqtt_password|mqtt_id] \r\n"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); + os_sprintf(response, "|set [mqtt_host|mqtt_port|mqtt_user|mqtt_password|mqtt_id] \r\n"); + to_console(response); #endif ringbuf_memcpy_into(console_tx_buffer, "\r\n", 2); - goto command_handled_2; - } - - if (strcmp(tokens[0], "show") == 0) - { - int16_t i; - ip_addr_t i_ip; - - if (nTokens == 1 || (nTokens == 2 && strcmp(tokens[1], "config") == 0)) { - os_sprintf(response, "STA: SSID:%s PW:%s%s\r\n", - config.ssid, - config.locked?"***":(char*)config.password, - config.auto_connect?"":" [AutoConnect:0]"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - - os_sprintf(response, "AP: SSID:%s PW:%s%s%s IP:%d.%d.%d.%d/24\r\n", - config.ap_ssid, - config.locked?"***":(char*)config.ap_password, - config.ap_open?" [open]":"", - config.ap_on?"":" [disabled]", - IP2STR(&config.network_addr)); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - - // if static IP, add it - os_sprintf(response, config.my_addr.addr?"Static IP: %d.%d.%d.%d Netmask: %d.%d.%d.%d Gateway: %d.%d.%d.%d\r\n":"", - IP2STR(&config.my_addr), IP2STR(&config.my_netmask), IP2STR(&config.my_gw)); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - // if static DNS, add it - os_sprintf(response, config.dns_addr.addr?"DNS: %d.%d.%d.%d\r\n":"", IP2STR(&config.dns_addr)); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); -#ifdef MQTT_CLIENT - os_sprintf(response, "MQTT client %s\r\n", mqtt_enabled?"enabled":"disabled"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - - if (os_strcmp(config.mqtt_host, "none") != 0) { - os_sprintf(response, "MQTT host: %s\r\nMQTT port: %d\r\nMQTT user: %s\r\nMQTT password: %s\r\nMQTT id: %s\r\n", - config.mqtt_host, config.mqtt_port, config.mqtt_user, - config.locked?"***":(char*)config.mqtt_password, config.mqtt_id); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - } -#endif -#ifdef NTP - if (os_strcmp(config.ntp_server, "none") != 0) { - os_sprintf(response, "NTP server: %s (interval: %d s, tz: %d)\r\n", - config.ntp_server, config.ntp_interval/1000000, config.ntp_timezone); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - } -#endif - os_sprintf(response, "Clock speed: %d\r\n", config.clock_speed); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); goto command_handled_2; - } + } - if (nTokens == 2 && strcmp(tokens[1], "stats") == 0) { - uint32_t time = (uint32_t)(get_long_systime()/1000000); - int16_t i; + if (strcmp(tokens[0], "show") == 0) { + int16_t i; + ip_addr_t i_ip; - os_sprintf(response, "System uptime: %d:%02d:%02d\r\n", - time/3600, (time%3600)/60, time%60); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); + if (nTokens == 1 || (nTokens == 2 && strcmp(tokens[1], "config") == 0)) { + os_sprintf(response, "STA: SSID:%s PW:%s%s\r\n", + config.ssid, + config.locked ? "***" : (char *)config.password, config.auto_connect ? "" : " [AutoConnect:0]"); + to_console(response); - if (connected) { - os_sprintf(response, "External IP-address: " IPSTR "\r\n", IP2STR(&my_ip)); - } else { - os_sprintf(response, "Not connected to AP\r\n"); - } - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - if (config.ap_on) - os_sprintf(response, "%d Station%s connected to AP\r\n", wifi_softap_get_station_num(), - wifi_softap_get_station_num()==1?"":"s"); - else - os_sprintf(response, "AP disabled\r\n"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - goto command_handled_2; - } + os_sprintf(response, "AP: SSID:%s PW:%s%s%s IP:%d.%d.%d.%d/24\r\n", + config.ap_ssid, + config.locked ? "***" : (char *)config.ap_password, + config.ap_open ? " [open]" : "", + config.ap_on ? "" : " [disabled]", IP2STR(&config.network_addr)); + to_console(response); - if (nTokens == 2 && (strcmp(tokens[1], "mqtt_broker")==0 || strcmp(tokens[1], "mqtt")==0)) { - MQTT_ClientCon *clientcon; - int ccnt = 0; - - os_sprintf(response, "Current clients:\r\n"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - for (clientcon = clientcon_list; clientcon != NULL; clientcon = clientcon->next, ccnt++) { - os_sprintf(response, "%s%s", clientcon->connect_info.client_id, clientcon->next != NULL?", ":""); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - } - os_sprintf(response, "%sCurrent subsriptions:\r\n", ccnt?"\r\n":""); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - iterate_topics(printf_topic, response); - os_sprintf(response, "Retained topics:\r\n"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); - iterate_retainedtopics(printf_retainedtopic, response); + // if static IP, add it + os_sprintf(response, + config.my_addr.addr ? + "Static IP: %d.%d.%d.%d Netmask: %d.%d.%d.%d Gateway: %d.%d.%d.%d\r\n" + : "", IP2STR(&config.my_addr), IP2STR(&config.my_netmask), IP2STR(&config.my_gw)); + to_console(response); + // if static DNS, add it + os_sprintf(response, config.dns_addr.addr ? "DNS: %d.%d.%d.%d\r\n" : "", IP2STR(&config.dns_addr)); + to_console(response); #ifdef MQTT_CLIENT - os_sprintf(response, "MQTT client %s\r\n", mqtt_connected?"connected":"disconnected"); - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); -#endif - goto command_handled_2; - } - } + os_sprintf(response, "MQTT client %s\r\n", mqtt_enabled ? "enabled" : "disabled"); + to_console(response); - if (strcmp(tokens[0], "save") == 0) - { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - if (nTokens == 1 || (nTokens == 2 && strcmp(tokens[1], "config") == 0)) { - config_save(&config); - os_sprintf(response, "Config saved\r\n"); - goto command_handled; - } - } -#ifdef ALLOW_SCANNING - if (strcmp(tokens[0], "scan") == 0) - { - scanconn = pespconn; - wifi_station_scan(NULL,scan_done); - os_sprintf(response, "Scanning...\r\n"); - goto command_handled; - } + if (os_strcmp(config.mqtt_host, "none") != 0) { + os_sprintf(response, + "MQTT host: %s\r\nMQTT port: %d\r\nMQTT user: %s\r\nMQTT password: %s\r\nMQTT id: %s\r\n", + config.mqtt_host, config.mqtt_port, config.mqtt_user, + config.locked ? "***" : (char *)config.mqtt_password, config.mqtt_id); + to_console(response); + } #endif #ifdef NTP - if (strcmp(tokens[0], "time") == 0) - { - os_sprintf(response, "%s\r\n", get_timestr(config.ntp_timezone)); - goto command_handled; - } + if (os_strcmp(config.ntp_server, "none") != 0) { + os_sprintf(response, + "NTP server: %s (interval: %d s, tz: %d)\r\n", + config.ntp_server, config.ntp_interval / 1000000, config.ntp_timezone); + to_console(response); + } #endif - if (strcmp(tokens[0], "reset") == 0) - { - if (nTokens == 2 && strcmp(tokens[1], "factory") == 0) { - config_load_default(&config); - config_save(&config); + os_sprintf(response, "Clock speed: %d\r\n", config.clock_speed); + to_console(response); + goto command_handled_2; } - os_printf("Restarting ... \r\n"); - system_restart(); // if it works this will not return - os_sprintf(response, "Reset failed\r\n"); - goto command_handled; - } + if (nTokens == 2 && strcmp(tokens[1], "stats") == 0) { + uint32_t time = (uint32_t) (get_long_systime() / 1000000); + int16_t i; - if (strcmp(tokens[0], "quit") == 0) - { - remote_console_disconnect = 1; - os_sprintf(response, "Quitting console\r\n"); - goto command_handled; - } + os_sprintf(response, "System uptime: %d:%02d:%02d\r\n", time / 3600, (time % 3600) / 60, time % 60); + to_console(response); - if (strcmp(tokens[0], "publish") == 0) - { - if (nTokens != 4) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - if (strcmp(tokens[1], "local") == 0) { - MQTT_local_publish(tokens[2], tokens[3], os_strlen(tokens[3]), 0, 0); + if (connected) { + os_sprintf(response, "External IP-address: " IPSTR "\r\n", IP2STR(&my_ip)); + } else { + os_sprintf(response, "Not connected to AP\r\n"); + } + to_console(response); + if (config.ap_on) + os_sprintf(response, "%d Station%s connected to AP\r\n", + wifi_softap_get_station_num(), wifi_softap_get_station_num() == 1 ? "" : "s"); + else + os_sprintf(response, "AP disabled\r\n"); + to_console(response); +#ifdef NTP + if (ntp_sync_done()) { + os_sprintf(response, "NTP synced: %s \r\n", get_timestr()); + } else { + os_sprintf(response, "NTP no sync\r\n"); + } + to_console(response); +#endif + goto command_handled_2; } + + if (nTokens == 2 && strcmp(tokens[1], "mqtt") == 0) { + MQTT_ClientCon *clientcon; + int ccnt = 0; + + os_sprintf(response, "Current clients:\r\n"); + to_console(response); + for (clientcon = clientcon_list; clientcon != NULL; clientcon = clientcon->next, ccnt++) { + os_sprintf(response, "%s%s", clientcon->connect_info.client_id, clientcon->next != NULL ? ", " : ""); + to_console(response); + } + os_sprintf(response, "%sCurrent subsriptions:\r\n", ccnt ? "\r\n" : ""); + to_console(response); + iterate_topics(printf_topic, response); + os_sprintf(response, "Retained topics:\r\n"); + to_console(response); + iterate_retainedtopics(printf_retainedtopic, response); #ifdef MQTT_CLIENT - else if (strcmp(tokens[1], "remote") == 0 && mqtt_connected) { - MQTT_Publish(&mqttClient, tokens[2], tokens[3], os_strlen(tokens[3]), 0, 0); - } + os_sprintf(response, "MQTT client %s\r\n", mqtt_connected ? "connected" : "disconnected"); + to_console(response); #endif - os_sprintf(response, "Published topic\r\n"); - goto command_handled; - } - - if (strcmp(tokens[0], "subscribe") == 0) - { - bool retval = false; - - if (nTokens != 3) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - - if (strcmp(tokens[1], "local") == 0) { - retval = MQTT_local_subscribe(tokens[2], 0); - } -#ifdef MQTT_CLIENT - else if (strcmp(tokens[1], "remote") == 0 && mqtt_connected) { - retval = MQTT_Subscribe(&mqttClient, tokens[2], 0); - } +#ifdef SCRIPTED + os_sprintf(response, "Script %s\r\n", script_enabled ? "enabled" : "disabled"); + to_console(response); #endif - if (retval) - os_sprintf(response, "subscribed topic\r\n"); - else - os_sprintf(response, "subscribe failed\r\n"); - goto command_handled; - } - - if (strcmp(tokens[0], "unsubscribe") == 0) - { - bool retval = false; - - if (nTokens != 3) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; + goto command_handled_2; } +#ifdef SCRIPTED + if (nTokens >= 2 && strcmp(tokens[1], "script") == 0) { + uint32_t line_count, char_count, start_line = 1; + if (nTokens == 3) + start_line = atoi(tokens[2]); - if (strcmp(tokens[1], "local") == 0) { - retval = MQTT_local_unsubscribe(tokens[2]); - } -#ifdef MQTT_CLIENT - else if (strcmp(tokens[1], "remote") == 0 && mqtt_connected) { - retval = MQTT_UnSubscribe(&mqttClient, tokens[2]); - } -#endif - if (retval) - os_sprintf(response, "unsubscribed topic\r\n"); - else - os_sprintf(response, "unsubscribe failed\r\n"); - goto command_handled; - } + uint32_t size = get_script_size(); + if (size == 0) + goto command_handled; - if (strcmp(tokens[0], "lock") == 0) - { - config.locked = 1; - os_sprintf(response, "Config locked\r\n"); - goto command_handled; - } + uint8_t *script = (uint8_t *) os_malloc(size); + uint8_t *p; + bool nl; - if (strcmp(tokens[0], "unlock") == 0) - { - if (nTokens != 2) { - os_sprintf(response, INVALID_NUMARGS); - } - else if (strcmp(tokens[1],config.password) == 0) { - config.locked = 0; - os_sprintf(response, "Config unlocked\r\n"); - } else { - os_sprintf(response, "Unlock failed. Invalid password\r\n"); - } - goto command_handled; - } - - if (strcmp(tokens[0], "set") == 0) - { - if (config.locked) - { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - /* - * For set commands atleast 2 tokens "set" "parameter" "value" is needed - * hence the check - */ - if (nTokens < 3) - { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - else - { - // atleast 3 tokens, proceed - if (strcmp(tokens[1],"ssid") == 0) - { - os_sprintf(config.ssid, "%s", tokens[2]); - os_sprintf(response, "SSID set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1],"password") == 0) - { - os_sprintf(config.password, "%s", tokens[2]); - os_sprintf(response, "Password set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1],"auto_connect") == 0) - { - config.auto_connect = atoi(tokens[2]); - os_sprintf(response, "Auto Connect set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1],"ap_ssid") == 0) - { - os_sprintf(config.ap_ssid, "%s", tokens[2]); - os_sprintf(response, "AP SSID set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1],"ap_password") == 0) - { - if (os_strlen(tokens[2])<8) { - os_sprintf(response, "Password to short (min. 8)\r\n"); - } else { - os_sprintf(config.ap_password, "%s", tokens[2]); - config.ap_open = 0; - os_sprintf(response, "AP Password set\r\n"); - } - goto command_handled; - } - - if (strcmp(tokens[1],"ap_open") == 0) - { - config.ap_open = atoi(tokens[2]); - os_sprintf(response, "Open Auth set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1],"ap_on") == 0) - { - if (atoi(tokens[2])) { - if (!config.ap_on) { - wifi_set_opmode(STATIONAP_MODE); - user_set_softap_wifi_config(); - do_ip_config = true; - config.ap_on = true; - os_sprintf(response, "AP on\r\n"); - } else { - os_sprintf(response, "AP already on\r\n"); - } - - } else { - if (config.ap_on) { - wifi_set_opmode(STATION_MODE); - config.ap_on = false; - os_sprintf(response, "AP off\r\n"); - } else { - os_sprintf(response, "AP already off\r\n"); - } - } - goto command_handled; - } - - if (strcmp(tokens[1], "speed") == 0) - { - uint16_t speed = atoi(tokens[2]); - bool succ = system_update_cpu_freq(speed); - if (succ) - config.clock_speed = speed; - os_sprintf(response, "Clock speed update %s\r\n", - succ?"successful":"failed"); - goto command_handled; + if (script == 0) { + os_sprintf(response, "Out of memory"); + goto command_handled; } - if (strcmp(tokens[1],"network") == 0) - { - config.network_addr.addr = ipaddr_addr(tokens[2]); - ip4_addr4(&config.network_addr) = 0; - os_sprintf(response, "Network set to %d.%d.%d.%d/24\r\n", - IP2STR(&config.network_addr)); - goto command_handled; - } + blob_load(0, (uint32_t *) script, size); - if (strcmp(tokens[1],"dns") == 0) - { + p = script + 4; + for (line_count = 1; line_count < start_line && *p != 0; p++) { + if (*p == '\n') + line_count++; + } + nl = true; + for (char_count = 0; *p != 0 && char_count < MAX_CON_SEND_SIZE - 20; p++, char_count++) { + if (nl) { + os_sprintf(response, "\r%4d: ", line_count); + char_count += 7; + to_console(response); + line_count++; + nl = false; + } + ringbuf_memcpy_into(console_tx_buffer, p, 1); + if (*p == '\n') + nl = true; + } + if (*p == 0) { + ringbuf_memcpy_into(console_tx_buffer, "\r\n--end--", 9); + } else { + ringbuf_memcpy_into(console_tx_buffer, "...", 3); + } + ringbuf_memcpy_into(console_tx_buffer, "\r\n", 2); + + os_free(script); + goto command_handled_2; + } +#endif + } + + if (strcmp(tokens[0], "save") == 0) { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + if (nTokens == 1 || (nTokens == 2 && strcmp(tokens[1], "config") == 0)) { + config_save(&config); + os_sprintf(response, "Config saved\r\n"); + goto command_handled; + } + } +#ifdef ALLOW_SCANNING + if (strcmp(tokens[0], "scan") == 0) { + wifi_station_scan(NULL, scan_done); + os_sprintf(response, "Scanning...\r\n"); + goto command_handled; + } +#endif +#ifdef NTP + if (strcmp(tokens[0], "time") == 0) { + os_sprintf(response, "%s\r\n", get_timestr(config.ntp_timezone)); + goto command_handled; + } +#endif + if (strcmp(tokens[0], "reset") == 0) { + if (nTokens == 2 && strcmp(tokens[1], "factory") == 0) { + config_load_default(&config); + config_save(&config); +#ifdef SCRIPTED + // clear script + blob_zero(0, MAX_SCRIPT_SIZE); +#endif + } + os_printf("Restarting ... \r\n"); + system_restart(); // if it works this will not return + + os_sprintf(response, "Reset failed\r\n"); + goto command_handled; + } + + if (strcmp(tokens[0], "quit") == 0) { + remote_console_disconnect = 1; + os_sprintf(response, "Quitting console\r\n"); + goto command_handled; + } +#ifdef SCRIPTED + if (strcmp(tokens[0], "script") == 0) { + uint16_t port; + + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + if (nTokens != 2) { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } + + port = atoi(tokens[1]); + if (nTokens == 0) { + os_sprintf(response, "Invalid port"); + goto command_handled; + } + // delete and disable existing script + script_enabled = false; + free_script(); + + scriptcon = pespconn; + downloadCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + + /* Equivalent to bind */ + downloadCon->type = ESPCONN_TCP; + downloadCon->state = ESPCONN_NONE; + downloadCon->proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); + downloadCon->proto.tcp->local_port = port; + + /* Register callback when clients connect to the server */ + espconn_regist_connectcb(downloadCon, script_connected_cb); + + /* Put the connection in accept mode */ + espconn_accept(downloadCon); + + os_sprintf(response, "Waiting for script upload on port %d\r\n", port); + goto command_handled; + } +#endif + if (strcmp(tokens[0], "lock") == 0) { + if (config.locked) { + os_sprintf(response, "Config already locked\r\n"); + goto command_handled; + } + if (nTokens == 1) { + if (os_strlen(config.lock_password) == 0) { + os_sprintf(response, "No password defined\r\n"); + goto command_handled; + } + } + else if (nTokens == 2) { + os_sprintf(config.lock_password, "%s", tokens[1]); + } + else { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } + config.locked = 1; + config_save(&config); + os_sprintf(response, "Config locked (pw: %s)\r\n", config.lock_password); + goto command_handled; + } + + if (strcmp(tokens[0], "unlock") == 0) { + if (nTokens != 2) { + os_sprintf(response, INVALID_NUMARGS); + } else if (os_strcmp(tokens[1], config.lock_password) == 0) { + config.locked = 0; + config_save(&config); + os_sprintf(response, "Config unlocked\r\n"); + } else { + os_sprintf(response, "Unlock failed. Invalid password\r\n"); + } + goto command_handled; + } + + if (strcmp(tokens[0], "set") == 0) { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + /* + * For set commands atleast 2 tokens "set" "parameter" "value" is needed + * hence the check + */ + if (nTokens < 3) { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } else { + // atleast 3 tokens, proceed + if (strcmp(tokens[1], "ssid") == 0) { + os_sprintf(config.ssid, "%s", tokens[2]); + os_sprintf(response, "SSID set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "password") == 0) { + os_sprintf(config.password, "%s", tokens[2]); + os_sprintf(response, "Password set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "auto_connect") == 0) { + config.auto_connect = atoi(tokens[2]); + os_sprintf(response, "Auto Connect set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "ap_ssid") == 0) { + os_sprintf(config.ap_ssid, "%s", tokens[2]); + os_sprintf(response, "AP SSID set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "ap_password") == 0) { + if (os_strlen(tokens[2]) < 8) { + os_sprintf(response, "Password to short (min. 8)\r\n"); + } else { + os_sprintf(config.ap_password, "%s", tokens[2]); + config.ap_open = 0; + os_sprintf(response, "AP Password set\r\n"); + } + goto command_handled; + } + + if (strcmp(tokens[1], "ap_open") == 0) { + config.ap_open = atoi(tokens[2]); + os_sprintf(response, "Open Auth set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "ap_on") == 0) { + if (atoi(tokens[2])) { + if (!config.ap_on) { + wifi_set_opmode(STATIONAP_MODE); + user_set_softap_wifi_config(); + do_ip_config = true; + config.ap_on = true; + os_sprintf(response, "AP on\r\n"); + } else { + os_sprintf(response, "AP already on\r\n"); + } + + } else { + if (config.ap_on) { + wifi_set_opmode(STATION_MODE); + config.ap_on = false; + os_sprintf(response, "AP off\r\n"); + } else { + os_sprintf(response, "AP already off\r\n"); + } + } + goto command_handled; + } + + if (strcmp(tokens[1], "speed") == 0) { + uint16_t speed = atoi(tokens[2]); + bool succ = system_update_cpu_freq(speed); + if (succ) + config.clock_speed = speed; + os_sprintf(response, "Clock speed update %s\r\n", succ ? "successful" : "failed"); + goto command_handled; + } + + if (strcmp(tokens[1], "network") == 0) { + config.network_addr.addr = ipaddr_addr(tokens[2]); + ip4_addr4(&config.network_addr) = 0; + os_sprintf(response, "Network set to %d.%d.%d.%d/24\r\n", IP2STR(&config.network_addr)); + goto command_handled; + } + + if (strcmp(tokens[1], "dns") == 0) { if (os_strcmp(tokens[2], "dhcp") == 0) { config.dns_addr.addr = 0; os_sprintf(response, "DNS from DHCP\r\n"); } else { config.dns_addr.addr = ipaddr_addr(tokens[2]); - os_sprintf(response, "DNS set to %d.%d.%d.%d\r\n", - IP2STR(&config.dns_addr)); + os_sprintf(response, "DNS set to %d.%d.%d.%d\r\n", IP2STR(&config.dns_addr)); if (config.dns_addr.addr) { dns_ip.addr = config.dns_addr.addr; } } - goto command_handled; - } + goto command_handled; + } - if (strcmp(tokens[1],"ip") == 0) - { + if (strcmp(tokens[1], "ip") == 0) { if (os_strcmp(tokens[2], "dhcp") == 0) { config.my_addr.addr = 0; os_sprintf(response, "IP from DHCP\r\n"); } else { config.my_addr.addr = ipaddr_addr(tokens[2]); - os_sprintf(response, "IP address set to %d.%d.%d.%d\r\n", - IP2STR(&config.my_addr)); + os_sprintf(response, "IP address set to %d.%d.%d.%d\r\n", IP2STR(&config.my_addr)); } - goto command_handled; - } + goto command_handled; + } - if (strcmp(tokens[1],"netmask") == 0) - { - config.my_netmask.addr = ipaddr_addr(tokens[2]); - os_sprintf(response, "IP netmask set to %d.%d.%d.%d\r\n", - IP2STR(&config.my_netmask)); - goto command_handled; - } + if (strcmp(tokens[1], "netmask") == 0) { + config.my_netmask.addr = ipaddr_addr(tokens[2]); + os_sprintf(response, "IP netmask set to %d.%d.%d.%d\r\n", IP2STR(&config.my_netmask)); + goto command_handled; + } - if (strcmp(tokens[1],"gw") == 0) - { - config.my_gw.addr = ipaddr_addr(tokens[2]); - os_sprintf(response, "Gateway set to %d.%d.%d.%d\r\n", - IP2STR(&config.my_gw)); - goto command_handled; - } + if (strcmp(tokens[1], "gw") == 0) { + config.my_gw.addr = ipaddr_addr(tokens[2]); + os_sprintf(response, "Gateway set to %d.%d.%d.%d\r\n", IP2STR(&config.my_gw)); + goto command_handled; + } +#ifdef REMOTE_CONFIG + if (strcmp(tokens[1], "config_port") == 0) { + config.config_port = atoi(tokens[2]); + if (config.config_port == 0) + os_sprintf(response, "WARNING: if you save this, remote console access will be disabled!\r\n"); + else + os_sprintf(response, "Config port set to %d\r\n", config.config_port); + goto command_handled; + } +#endif #ifdef NTP - if (strcmp(tokens[1], "ntp_server") == 0) - { + if (strcmp(tokens[1], "ntp_server") == 0) { os_strncpy(config.ntp_server, tokens[2], 32); config.ntp_server[31] = 0; ntp_set_server(config.ntp_server); os_sprintf(response, "NTP server set to %s\r\n", config.ntp_server); - goto command_handled; - } + goto command_handled; + } - if (strcmp(tokens[1], "ntp_interval") == 0) - { - config.ntp_interval = atoi(tokens[2])*1000000; + if (strcmp(tokens[1], "ntp_interval") == 0) { + config.ntp_interval = atoi(tokens[2]) * 1000000; os_sprintf(response, "NTP interval set to %d s\r\n", atoi(tokens[2])); - goto command_handled; - } + goto command_handled; + } - if (strcmp(tokens[1], "ntp_timezone") == 0) - { + if (strcmp(tokens[1], "ntp_timezone") == 0) { config.ntp_timezone = atoi(tokens[2]); + set_timezone(config.ntp_timezone); os_sprintf(response, "NTP timezone set to %d h\r\n", config.ntp_timezone); - goto command_handled; - } + goto command_handled; + } #endif #ifdef MQTT_CLIENT - if (strcmp(tokens[1], "mqtt_host") == 0) - { + if (strcmp(tokens[1], "mqtt_host") == 0) { os_strncpy(config.mqtt_host, tokens[2], 32); config.mqtt_host[31] = 0; os_sprintf(response, "MQTT host set\r\n"); - goto command_handled; + goto command_handled; } - if (strcmp(tokens[1], "mqtt_port") == 0) - { + if (strcmp(tokens[1], "mqtt_port") == 0) { config.mqtt_port = atoi(tokens[2]); os_sprintf(response, "MQTT port set\r\n"); - goto command_handled; + goto command_handled; } - if (strcmp(tokens[1], "mqtt_user") == 0) - { + if (strcmp(tokens[1], "mqtt_user") == 0) { os_strncpy(config.mqtt_user, tokens[2], 32); config.mqtt_user[31] = 0; os_sprintf(response, "MQTT user set\r\n"); - goto command_handled; + goto command_handled; } - if (strcmp(tokens[1], "mqtt_password") == 0) - { + if (strcmp(tokens[1], "mqtt_password") == 0) { os_strncpy(config.mqtt_password, tokens[2], 32); config.mqtt_password[31] = 0; os_sprintf(response, "MQTT password set\r\n"); - goto command_handled; + goto command_handled; } - if (strcmp(tokens[1], "mqtt_id") == 0) - { + if (strcmp(tokens[1], "mqtt_id") == 0) { os_strncpy(config.mqtt_id, tokens[2], 32); config.mqtt_id[31] = 0; os_sprintf(response, "MQTT id set\r\n"); - goto command_handled; + goto command_handled; } -#endif /* MQTT_CLIENT */ - } +#endif /* MQTT_CLIENT */ + } } /* Control comes here only if the tokens[0] command is not handled */ os_sprintf(response, "\r\nInvalid Command\r\n"); -command_handled: - ringbuf_memcpy_into(console_tx_buffer, response, os_strlen(response)); -command_handled_2: - system_os_post(0, SIG_CONSOLE_TX, (ETSParam) pespconn); + command_handled: + to_console(response); + command_handled_2: + system_os_post(user_procTaskPrio, SIG_CONSOLE_TX, (ETSParam) pespconn); return; } -#ifdef REMOTE_CONFIG -static void ICACHE_FLASH_ATTR tcp_client_recv_cb(void *arg, - char *data, - unsigned short length) -{ - struct espconn *pespconn = (struct espconn *)arg; - int index; - uint8_t ch; +#ifdef SCRIPTED +void ICACHE_FLASH_ATTR do_command(char *t1, char *t2, char *t3) { - for (index=0; index "); espconn_sent(pespconn, payload, os_strlen(payload)); + client_sent_pending = true; + console_conn = pespconn; } -#endif /* REMOTE_CONFIG */ - +#endif /* REMOTE_CONFIG */ // Timer cb function -void ICACHE_FLASH_ATTR timer_func(void *arg){ -uint64_t t_new; +void ICACHE_FLASH_ATTR timer_func(void *arg) { + uint64_t t_new; // Do we still have to configure the AP netif? if (do_ip_config) { @@ -792,173 +942,199 @@ uint64_t t_new; t_new = get_long_systime(); #ifdef NTP if (t_new - t_ntp_resync > config.ntp_interval) { - ntp_get_time(); + ntp_get_time(); t_ntp_resync = t_new; } - if (ntp_sync_done()) + if (ntp_sync_done()) { + uint8_t *timestr = get_timestr(); MQTT_local_publish("$SYS/broker/time", get_timestr(config.ntp_timezone), 8, 0, 0); +#ifdef SCRIPTED + if (!timestamps_init) { + init_timestamps(timestr); + timestamps_init = true; + } + check_timestamps(timestr); #endif - - os_timer_arm(&ptimer, 1000, 0); + } +#endif + os_timer_arm(&ptimer, 1000, 0); } //Priority 0 Task -static void ICACHE_FLASH_ATTR user_procTask(os_event_t *events) -{ +static void ICACHE_FLASH_ATTR user_procTask(os_event_t * events) { //os_printf("Sig: %d\r\n", events->sig); - switch(events->sig) - { + switch (events->sig) { case SIG_START_SERVER: // Anything else to do here, when the repeater has received its IP? break; +#ifdef SCRIPTED + case SIG_SCRIPT_LOADED: + { + espconn_disconnect(downloadCon); + espconn_delete(downloadCon); + os_free(downloadCon->proto.tcp); + os_free(downloadCon); + if (read_script()) { + interpreter_syntax_check(); + ringbuf_memcpy_into(console_tx_buffer, syntax_error_buffer, os_strlen(syntax_error_buffer)); + ringbuf_memcpy_into(console_tx_buffer, "\r\n", 2); + free_script(); + } + // continue to next case and print... + } +#endif case SIG_CONSOLE_TX: - { - struct espconn *pespconn = (struct espconn *) events->par; - console_send_response(pespconn); + { + ringbuf_memcpy_into(console_tx_buffer, "CMD>", 4); + } + case SIG_CONSOLE_TX_RAW: + { + struct espconn *pespconn = (struct espconn *)events->par; + console_send_response(pespconn); - if (pespconn != 0 && remote_console_disconnect) espconn_disconnect(pespconn); + if (pespconn != 0 && remote_console_disconnect) + espconn_disconnect(pespconn); remote_console_disconnect = 0; - } - break; + } + break; case SIG_CONSOLE_RX: - { - struct espconn *pespconn = (struct espconn *) events->par; - console_handle_command(pespconn); - } - break; + { + struct espconn *pespconn = (struct espconn *)events->par; + console_handle_command(pespconn); + } + break; case SIG_DO_NOTHING: default: - // Intentionally ignoring other signals - os_printf("Spurious Signal received\r\n"); - break; + // Intentionally ignoring other signals + os_printf("Spurious Signal received\r\n"); + break; } } /* Callback called when the connection state of the module with an Access Point changes */ -void wifi_handle_event_cb(System_Event_t *evt) -{ +void wifi_handle_event_cb(System_Event_t * evt) { uint16_t i; uint8_t mac_str[20]; //os_printf("wifi_handle_event_cb: "); - switch (evt->event) - { + switch (evt->event) { case EVENT_STAMODE_CONNECTED: - os_printf("connect to ssid %s, channel %d\n", evt->event_info.connected.ssid, evt->event_info.connected.channel); + os_printf("connect to ssid %s, channel %d\n", + evt->event_info.connected.ssid, evt->event_info.connected.channel); my_channel = evt->event_info.connected.channel; - break; + break; case EVENT_STAMODE_DISCONNECTED: - os_printf("disconnect from ssid %s, reason %d\n", evt->event_info.disconnected.ssid, evt->event_info.disconnected.reason); + os_printf("disconnect from ssid %s, reason %d\n", + evt->event_info.disconnected.ssid, evt->event_info.disconnected.reason); connected = false; #ifdef MQTT_CLIENT - if (mqtt_enabled) MQTT_Disconnect(&mqttClient); -#endif /* MQTT_CLIENT */ + if (mqtt_enabled) + MQTT_Disconnect(&mqttClient); +#endif /* MQTT_CLIENT */ - break; + break; case EVENT_STAMODE_AUTHMODE_CHANGE: - os_printf("mode: %d -> %d\n", evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); - break; + os_printf("mode: %d -> %d\n", evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); + break; case EVENT_STAMODE_GOT_IP: if (config.dns_addr.addr == 0) { dns_ip.addr = dns_getserver(0); } - os_printf("ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR ",dns:" IPSTR "\n", IP2STR(&evt->event_info.got_ip.ip), IP2STR(&evt->event_info.got_ip.mask), IP2STR(&evt->event_info.got_ip.gw), IP2STR(&dns_ip)); + os_printf("ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR ",dns:" IPSTR "\n", + IP2STR(&evt->event_info.got_ip.ip), + IP2STR(&evt->event_info.got_ip.mask), IP2STR(&evt->event_info.got_ip.gw), IP2STR(&dns_ip)); my_ip = evt->event_info.got_ip.ip; connected = true; #ifdef MQTT_CLIENT - if (mqtt_enabled) MQTT_Connect(&mqttClient); -#endif /* MQTT_CLIENT */ + if (mqtt_enabled) + MQTT_Connect(&mqttClient); +#endif /* MQTT_CLIENT */ #ifdef NTP - if (os_strcmp(config.ntp_server, "none") != 0) - ntp_set_server(config.ntp_server); + if (os_strcmp(config.ntp_server, "none") != 0) + ntp_set_server(config.ntp_server); + set_timezone(config.ntp_timezone); #endif - // Post a Server Start message as the IP has been acquired to Task with priority 0 - system_os_post(user_procTaskPrio, SIG_START_SERVER, 0 ); - break; + // Post a Server Start message as the IP has been acquired to Task with priority 0 + system_os_post(user_procTaskPrio, SIG_START_SERVER, 0); + break; case EVENT_SOFTAPMODE_STACONNECTED: os_sprintf(mac_str, MACSTR, MAC2STR(evt->event_info.sta_connected.mac)); - os_printf("station: %s join, AID = %d\n", mac_str, evt->event_info.sta_connected.aid); - break; + os_printf("station: %s join, AID = %d\n", mac_str, evt->event_info.sta_connected.aid); + break; case EVENT_SOFTAPMODE_STADISCONNECTED: os_sprintf(mac_str, MACSTR, MAC2STR(evt->event_info.sta_disconnected.mac)); - os_printf("station: %s leave, AID = %d\n", mac_str, evt->event_info.sta_disconnected.aid); - break; + os_printf("station: %s leave, AID = %d\n", mac_str, evt->event_info.sta_disconnected.aid); + break; default: - break; + break; } } +void ICACHE_FLASH_ATTR user_set_softap_wifi_config(void) { + struct softap_config apConfig; -void ICACHE_FLASH_ATTR user_set_softap_wifi_config(void) -{ -struct softap_config apConfig; + wifi_softap_get_config(&apConfig); // Get config first. - wifi_softap_get_config(&apConfig); // Get config first. - - os_memset(apConfig.ssid, 0, 32); - os_sprintf(apConfig.ssid, "%s", config.ap_ssid); - os_memset(apConfig.password, 0, 64); - os_sprintf(apConfig.password, "%s", config.ap_password); - if (!config.ap_open) - apConfig.authmode = AUTH_WPA_WPA2_PSK; - else - apConfig.authmode = AUTH_OPEN; - apConfig.ssid_len = 0;// or its actual length + os_memset(apConfig.ssid, 0, 32); + os_sprintf(apConfig.ssid, "%s", config.ap_ssid); + os_memset(apConfig.password, 0, 64); + os_sprintf(apConfig.password, "%s", config.ap_password); + if (!config.ap_open) + apConfig.authmode = AUTH_WPA_WPA2_PSK; + else + apConfig.authmode = AUTH_OPEN; + apConfig.ssid_len = 0; // or its actual length - apConfig.max_connection = MAX_CLIENTS; // how many stations can connect to ESP8266 softAP at most. + apConfig.max_connection = MAX_CLIENTS; // how many stations can connect to ESP8266 softAP at most. - // Set ESP8266 softap config - wifi_softap_set_config(&apConfig); + // Set ESP8266 softap config + wifi_softap_set_config(&apConfig); } +void ICACHE_FLASH_ATTR user_set_softap_ip_config(void) { + struct ip_info info; + struct dhcps_lease dhcp_lease; + struct netif *nif; + int i; -void ICACHE_FLASH_ATTR user_set_softap_ip_config(void) -{ -struct ip_info info; -struct dhcps_lease dhcp_lease; -struct netif *nif; -int i; + // Configure the internal network - // Configure the internal network + wifi_softap_dhcps_stop(); - wifi_softap_dhcps_stop(); + info.ip = config.network_addr; + ip4_addr4(&info.ip) = 1; + info.gw = info.ip; + IP4_ADDR(&info.netmask, 255, 255, 255, 0); - info.ip = config.network_addr; - ip4_addr4(&info.ip) = 1; - info.gw = info.ip; - IP4_ADDR(&info.netmask, 255, 255, 255, 0); + wifi_set_ip_info(1, &info); - wifi_set_ip_info(1, &info); + dhcp_lease.start_ip = config.network_addr; + ip4_addr4(&dhcp_lease.start_ip) = 2; + dhcp_lease.end_ip = config.network_addr; + ip4_addr4(&dhcp_lease.end_ip) = 128; + wifi_softap_set_dhcps_lease(&dhcp_lease); - dhcp_lease.start_ip = config.network_addr; - ip4_addr4(&dhcp_lease.start_ip) = 2; - dhcp_lease.end_ip = config.network_addr; - ip4_addr4(&dhcp_lease.end_ip) = 128; - wifi_softap_set_dhcps_lease(&dhcp_lease); - - wifi_softap_dhcps_start(); + wifi_softap_dhcps_start(); } - -void ICACHE_FLASH_ATTR user_set_station_config(void) -{ +void ICACHE_FLASH_ATTR user_set_station_config(void) { struct station_config stationConf; char hostname[40]; @@ -977,10 +1153,8 @@ void ICACHE_FLASH_ATTR user_set_station_config(void) wifi_station_set_auto_connect(config.auto_connect != 0); } - -void ICACHE_FLASH_ATTR user_init() -{ -struct ip_info info; +void ICACHE_FLASH_ATTR user_init() { + struct ip_info info; connected = false; do_ip_config = false; @@ -995,10 +1169,26 @@ struct ip_info info; UART_init_console(BIT_RATE_115200, 0, console_rx_buffer, console_tx_buffer); - os_printf("\r\n\r\nWiFi Router/MQTT Broker V1.5 starting\r\n"); + os_printf("\r\n\r\nWiFi Router/MQTT Broker V2.0 starting\r\n"); // Load config config_load(&config); +#ifdef SCRIPTED + script_enabled = false; + if (read_script()) { + if (interpreter_syntax_check() != -1) { + bool lockstat = config.locked; + config.locked = false; + + script_enabled = true; + interpreter_config(); + + config.locked = lockstat; + } else { + os_printf("ERROR in script: %s\r\nScript disabled\r\n", syntax_error_buffer); + } + } +#endif // Configure the AP and start it, if required @@ -1008,7 +1198,7 @@ struct ip_info info; if (config.ap_on) { wifi_set_opmode(STATIONAP_MODE); - user_set_softap_wifi_config(); + user_set_softap_wifi_config(); do_ip_config = true; } else { wifi_set_opmode(STATION_MODE); @@ -1022,23 +1212,22 @@ struct ip_info info; wifi_set_ip_info(STATION_IF, &info); espconn_dns_setserver(0, &dns_ip); } - #ifdef REMOTE_CONFIG if (config.config_port != 0) { - os_printf("Starting Console TCP Server on %d port\r\n", CONSOLE_SERVER_PORT); - struct espconn *pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + os_printf("Starting Console TCP Server on port %d\r\n", config.config_port); + struct espconn *pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); - /* Equivalent to bind */ - pCon->type = ESPCONN_TCP; - pCon->state = ESPCONN_NONE; - pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); - pCon->proto.tcp->local_port = config.config_port; + /* Equivalent to bind */ + pCon->type = ESPCONN_TCP; + pCon->state = ESPCONN_NONE; + pCon->proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); + pCon->proto.tcp->local_port = config.config_port; - /* Register callback when clients connect to the server */ - espconn_regist_connectcb(pCon, tcp_client_connected_cb); + /* Register callback when clients connect to the server */ + espconn_regist_connectcb(pCon, tcp_client_connected_cb); - /* Put the connection in accept mode */ - espconn_accept(pCon); + /* Put the connection in accept mode */ + espconn_accept(pCon); } #endif @@ -1049,19 +1238,20 @@ struct ip_info info; MQTT_InitConnection(&mqttClient, config.mqtt_host, config.mqtt_port, 0); if (os_strcmp(config.mqtt_user, "none") == 0) { - MQTT_InitClient(&mqttClient, config.mqtt_id, 0, 0, 120, 1); + MQTT_InitClient(&mqttClient, config.mqtt_id, 0, 0, 120, 1); } else { - MQTT_InitClient(&mqttClient, config.mqtt_id, config.mqtt_user, config.mqtt_password, 120, 1); + MQTT_InitClient(&mqttClient, config.mqtt_id, config.mqtt_user, config.mqtt_password, 120, 1); } -// MQTT_InitLWT(&mqttClient, "/lwt", "offline", 0, 0); +// MQTT_InitLWT(&mqttClient, "/lwt", "offline", 0, 0); MQTT_OnConnected(&mqttClient, mqttConnectedCb); MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb); MQTT_OnPublished(&mqttClient, mqttPublishedCb); MQTT_OnData(&mqttClient, mqttDataCb); } -#endif /* MQTT_CLIENT */ +#endif /* MQTT_CLIENT */ remote_console_disconnect = 0; + console_conn = NULL; // Now start the STA-Mode user_set_station_config(); @@ -1071,16 +1261,18 @@ struct ip_info info; espconn_tcp_set_max_con(10); os_printf("Max number of TCP clients: %d\r\n", espconn_tcp_get_max_con()); - MQTT_server_start(1883 /*port*/, 30 /*max_subscriptions*/, 30 /*max_retained_items*/); + MQTT_server_start(1883 /*port */ , 30 /*max_subscriptions */ , + 30 /*max_retained_items */ ); - //MQTT_local_subscribe("/test/#", 0); MQTT_local_onData(MQTT_local_DataCallback); - +#ifdef SCRIPTED + timestamps_init = false; + interpreter_init(); +#endif // Start the timer os_timer_setfn(&ptimer, timer_func, 0); - os_timer_arm(&ptimer, 500, 0); + os_timer_arm(&ptimer, 500, 0); //Start task system_os_task(user_procTask, user_procTaskPrio, user_procTaskQueue, user_procTaskQueueLen); } - diff --git a/user_basic/user_main.c b/user_basic/user_main.c index 155b73e..6bcb209 100644 --- a/user_basic/user_main.c +++ b/user_basic/user_main.c @@ -2,8 +2,7 @@ #include "mqtt_server.h" #include "user_config.h" -void ICACHE_FLASH_ATTR user_init() -{ +void ICACHE_FLASH_ATTR user_init() { struct station_config stationConf; // Initialize the UART