diff --git a/Makefile b/Makefile index f7b0573..e709fe9 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,33 @@ CFLAGS= # mode trying to connect to the specified AP *only* if the flash wireless settings are empty! # This happens on a full serial flash and avoids having to hunt for the AP... # STA_SSID ?= -# STA_PASS ?= +# STA_PASS ?= + +# The SOFTAP configuration can be hard-coded here, the minimum parameters to set are AP_SSID && AP_PASS +# The AP SSID has to be at least 8 characters long, same for AP PASSWORD +# The AP AUTH MODE can be set to: +# 0 = AUTH_OPEN, +# 1 = AUTH_WEP, +# 2 = AUTH_WPA_PSK, +# 3 = AUTH_WPA2_PSK, +# 4 = AUTH_WPA_WPA2_PSK +# SSID hidden default 0, ( 0 | 1 ) +# Max connections default 4, ( 1 ~ 4 ) +# Beacon interval default 100, ( 100 ~ 60000ms ) +# +# AP_SSID ?=esp_link_test +# AP_PASS ?=esp_link_test +# AP_AUTH_MODE ?=4 +# AP_SSID_HIDDEN ?=0 +# AP_MAX_CONN ?=4 +# AP_BEACON_INTERVAL ?=100 + +# If CHANGE_TO_STA is set to "yes" the esp-link module will switch to station mode +# once successfully connected to an access point. Else it will stay in STA+AP mode. +CHANGE_TO_STA ?= yes + +# hostname or IP address for wifi flashing +ESP_HOSTNAME ?= esp-link # --------------- toolchain configuration --------------- @@ -43,15 +69,6 @@ ESPTOOL ?= $(abspath ../esp-open-sdk/esptool/esptool.py) ESPPORT ?= /dev/ttyUSB0 ESPBAUD ?= 460800 -# The Wifi station configuration can be hard-coded here, which makes esp-link come up in STA+AP -# mode trying to connect to the specified AP *only* if the flash wireless settings are empty! -# This happens on a full serial flash and avoids having to hunt for the AP... -# STA_SSID ?= -# STA_PASS ?= - -# hostname or IP address for wifi flashing -ESP_HOSTNAME ?= esp-link - # --------------- chipset configuration --------------- # Pick your flash size: "512KB", "1MB", or "4MB" @@ -68,14 +85,9 @@ LED_CONN_PIN ?= 0 # GPIO pin used for "serial activity" LED, active low LED_SERIAL_PIN ?= 14 -# --------------- esp-link config options --------------- - -# If CHANGE_TO_STA is set to "yes" the esp-link module will switch to station mode -# once successfully connected to an access point. Else it will stay in AP+STA mode. +# --------------- esp-link modules config options --------------- -CHANGE_TO_STA ?= yes - -# Optional Modules +# Optional Modules mqtt MODULES ?= mqtt rest syslog # --------------- esphttpd config options --------------- @@ -270,6 +282,30 @@ ifneq ($(strip $(STA_PASS)),) CFLAGS += -DSTA_PASS="$(STA_PASS)" endif +ifneq ($(strip $(AP_SSID)),) +CFLAGS += -DAP_SSID="$(AP_SSID)" +endif + +ifneq ($(strip $(AP_PASS)),) +CFLAGS += -DAP_PASS="$(AP_PASS)" +endif + +ifneq ($(strip $(AP_AUTH_MODE)),) +CFLAGS += -DAP_AUTH_MODE="$(AP_AUTH_MODE)" +endif + +ifneq ($(strip $(AP_SSID_HIDDEN)),) +CFLAGS += -DAP_SSID_HIDDEN="$(AP_SSID_HIDDEN)" +endif + +ifneq ($(strip $(AP_MAX_CONN)),) +CFLAGS += -DAP_MAX_CONN="$(AP_MAX_CONN)" +endif + +ifneq ($(strip $(AP_BEACON_INTERVAL)),) +CFLAGS += -DAP_BEACON_INTERVAL="$(AP_BEACON_INTERVAL)" +endif + ifeq ("$(GZIP_COMPRESSION)","yes") CFLAGS += -DGZIP_COMPRESSION endif @@ -352,15 +388,17 @@ flash: all 0x00000 "$(BOOTFILE)" 0x01000 $(FW_BASE)/user1.bin \ $(ET_BLANK) $(SDK_BASE)/bin/blank.bin +ifeq ($(OS),Windows_NT) tools/$(HTML_COMPRESSOR): $(Q) mkdir -p tools - ifeq ($(OS),Windows_NT) cd tools; wget --no-check-certificate https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR) -O $(YUI_COMPRESSOR) cd tools; wget --no-check-certificate https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) -O $(HTML_COMPRESSOR) - else +else +tools/$(HTML_COMPRESSOR): + $(Q) mkdir -p tools cd tools; wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR) cd tools; wget https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) - endif +endif ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") $(BUILD_BASE)/espfs_img.o: tools/$(HTML_COMPRESSOR) diff --git a/README.md b/README.adoc similarity index 79% rename from README.md rename to README.adoc index b99950c..d6f226f 100644 --- a/README.md +++ b/README.adoc @@ -1,36 +1,75 @@ -ESP-LINK -======== +ESP-LINK: Wifi-Serial Bridge w/REST&MQTT +======================================== +Thorsten von Eicken +:toc: +:toc-title!: +:toc-placement!: This firmware connects an attached micro-controller to the internet using a ESP8266 Wifi module. It implements a number of features: + +[options="compact"] - transparent bridge between Wifi and serial, useful for debugging or inputting into a uC - flash-programming attached Arduino/AVR microcontrollers, esp8266 modules, as well as LPC800-series and other ARM microcontrollers via Wifi -- built-in stk500v1 programmer for AVR uC's with optiboot: program using HTTP upload of hex file -- outbound TCP (and thus HTTP) connections from the attached micro-controller to the internet -- outbound REST HTTP requests from the attached micro-controller to the internet, protocol - based on espduino and compatible with [tuanpmt/espduino](https://github.com/tuanpmt/espduino) +- built-in stk500v1 programmer for AVR uC's: program using HTTP upload of hex file +- outbound REST HTTP requests from the attached micro-controller to the internet +- MQTT client pub/sub from the attached micro-controller to the internet The firmware includes a tiny HTTP server based on -[esphttpd](http://www.esp8266.com/viewforum.php?f=34) +http://www.esp8266.com/viewforum.php?f=34[esphttpd] with a simple web interface, many thanks to Jeroen Domburg for making it available! +The REST and MQTT functionality are loosely based on https://github.com/tuanpmt/espduino +but significantly reqritten and no longer protocol compatible, thanks to tuanpmt for the +inspiration! Many thanks to https://github.com/brunnels for contributions in particular around the espduino functionality. Thank you also to https://github.com/susisstrolch and https://github.com/bc547 for additional contributions! -###[Releases & Downloads](https://github.com/jeelabs/esp-link/releases) +[float] +Table of Contents +----------------- + +toc::[] + +Releases & Downloads +-------------------- -- [V2.1.7](https://github.com/jeelabs/esp-link/releases/tag/v2.1.7) is the most recent release. +- https://github.com/jeelabs/esp-link/releases/tag/v2.1.7[V2.1.7] is the most recent release. It has the new built-in stk500v1 programmer and works on all modules (esp-01 through esp-12). -- [V2.2.beta1](https://github.com/jeelabs/esp-link/releases/tag/v2.2.beta1) will be coming +- https://github.com/jeelabs/esp-link/releases/tag/v2.2.beta2[V2.2.beta2] will be coming up shortly with mDNS, sNTP, and syslog support, stay tuned... +- See https://github.com/jeelabs/esp-link/releases[all releases]. + +For quick support and questions chat at +image:https://badges.gitter.im/Join%20Chat.svg[link="https://gitter.im/jeelabs/esp-link"] + +Intro +----- + +### Esp-link goals -For quick support and questions: -[![Chat at https://gitter.im/jeelabs/esp-link](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jeelabs/esp-link?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +The goal of the esp-link project is to create an advanced Wifi co-processor. Esp-link assumes that +there is a "main processor" (also referred to as "attached uController") and that esp-link's role +is to facilitate communication over Wifi. Where esp-link is a bit unusual is that it's not really +just a Wifi interface or a slave co-processor. In some sense it's the master, because the main +processor can be reset, controlled and reprogrammed through esp-link. The three main areas of +functionality in esp-link are: + +- reprogramming and debugging the attached uC +- letting the attached uC make outbound communication and offloading the protocol processing +- forwarding inbound communication and offloading the protocol processing (this part is the +least developed) + +The goal of the project is also to remain focused on the above mission. In particular, esp-link +is not a platform for stand-alone applications and it does not support connecting sensors or +actuators directly to it. A few users have taken esp-link as a starting point for doing these +things and that's great, but there's also value in keeping the mainline esp-link project +focused on a clear mission. + +### Esp-link uses -Esp-link uses -------------- The simplest use of esp-link is as a transparent serial to wifi bridge. You can flash an attached uC over wifi and you can watch the uC's serial debug output by connecting to port 23 or looking at the uC Console web page. @@ -51,26 +90,31 @@ the attached uC registers callbacks at start-up such that the code in the esp do know which exact sensors/actuators the attached uC has, it learns that through the initial callback registration. -Eye Candy ---------- +### Eye Candy + These screen shots show the Home page, the Wifi configuration page, the console for the attached microcontroller, and the pin assignments card: - - - - +image:https://cloud.githubusercontent.com/assets/39480/8261425/6ca395a6-167f-11e5-8e92-77150371135a.png[width="45%"] +image:https://cloud.githubusercontent.com/assets/39480/8261427/6caf7326-167f-11e5-8085-bc8b20159b2b.png[width="45%"] +image:https://cloud.githubusercontent.com/assets/39480/8261426/6ca7f75e-167f-11e5-827d-9a1c582ad05d.png[width="45%"] +image:https://cloud.githubusercontent.com/assets/39480/8261658/11e6c64a-1681-11e5-82d0-ea5ec90a6ddb.png[width="45%"] + +Getting Started +--------------- + +### Hardware configuration -Hardware info -------------- This firmware is designed for any esp8266 module. The recommended connections for an esp-01 module are: + - URXD: connect to TX of microcontroller - UTXD: connect to RX of microcontroller - GPIO0: connect to RESET of microcontroller - GPIO2: optionally connect green LED to 3.3V (indicates wifi status) The recommended connections for an esp-12 module are: + - URXD: connect to TX of microcontroller - UTXD: connect to RX of microcontroller - GPIO12: connect to RESET of microcontroller @@ -82,6 +126,7 @@ The recommended connections for an esp-12 module are: If your application has problems with the boot message that is output at ~74600 baud by the ROM at boot time you can connect an esp-12 module as follows and choose the "swap_uart" pin assignment in the esp-link web interface: + - GPIO13: connect to TX of microcontroller - GPIO15: connect to RX of microcontroller - GPIO1/UTXD: connect to RESET of microcontroller @@ -95,10 +140,10 @@ seen both used, sigh). The GPIO pin assignments can be changed dynamically in the web UI and are saved in flash. -Initial flashing ----------------- +### Initial flashing + If you want to simply flash a pre-built firmware binary, you can download the latest -[release](https://github.com/jeelabs/esp-link/releases) and use your favorite +https://github.com/jeelabs/esp-link/releases[release] and use your favorite ESP8266 flashing tool to flash the bootloader, the firmware, and blank settings. Detailed instructions are provided in the release notes. @@ -107,16 +152,17 @@ stored in the boot sector (address 0). This is the standard way that the esp8266 the flash size. What this means is that you need to set this properly when you flash the bootloader. If you use esptool.py you can do it using the -ff and -fs options. -Wifi configuration overview ------------------- +### Wifi configuration overview + For proper operation the end state that esp-link needs to arrive at is to have it join your pre-existing wifi network as a pure station. However, in order to get there esp-link will start out as an access point and you'll have to join its network to configure it. The short version is: + 1. esp-link creates a wifi access point with an SSID of the form `ESP_012ABC` (some modules use a different SSID form, such as `ai-thinker-012ABC`) 2. you join your laptop or phone to esp-link's network as a station and you configure - esp-link wifi with your network info by pointing your browser at http://192.168.4.1/ + esp-link wifi with your network info by pointing your browser at `http://192.168.4.1/` 3. you set a hostname for esp-link on the "home" page, or leave the default ("esp-link") 4. esp-link starts to connect to your network while continuing to also be an access point ("AP+STA"), the esp-link may show up with a `${hostname}.local` hostname @@ -125,10 +171,11 @@ to join its network to configure it. The short version is: you reconnect your laptop/phone to your normal network and access esp-link via its hostname or IP address -LED indicators --------------- +### LED indicators + Assuming appropriate hardware attached to GPIO pins, the green "conn" LED will show the wifi status as follows: + - Very short flash once a second: not connected to a network and running as AP+STA, i.e. trying to connect to the configured network - Very short flash once every two seconds: not connected to a network and running as AP-only @@ -138,8 +185,31 @@ status as follows: The yellow "ser" LED will blink briefly every time serial data is sent or received by the esp-link. -Wifi configuration details --------------------------- +### Troubleshooting + +- verify that you have sufficient power, borderline power can cause the esp module to seemingly + function until it tries to transmit and the power rail collapses +- if you just cannot flash your esp8266 module (some people call it the zombie mode) make sure you + have gpio0 and gpio15 pulled to gnd with a 1K resistor, gpio2 tied to 3.3V with 1K resistor, and + RX/TX connected without anything in series. If you need to level shift the signal going into the + esp8266's RX use a 1K resistor. Use 115200 baud in the flasher. + (For a permanent set-up I would use higher resistor values but + when nothing seems to work these are the ones I try.) +- if the flashing succeeded, check the "conn" LED to see which mode esp-link is in (see LED info above) +- reset or power-cycle the esp-link to force it to become an access-point if it can't + connect to your network within 15-20 seconds +- if the LED says that esp-link is on your network but you can't get to it, make sure your + laptop is on the same network (and no longer on the esp's network) +- if you do not know the esp-link's IP address on your network, try `esp-link.local`, try to find + the lease in your DHCP server; if all fails, you may have to turn off your access point (or walk + far enough away) and reset/power-cycle esp-link, it will then fail to connect and start its + own AP after 15-20 seconds + +Configuration details +--------------------- + +### Wifi + After you have serially flashed the module it will create a wifi access point (AP) with an SSID of the form `ESP_012ABC` where 012ABC is a piece of the module's MAC address. Using a laptop, phone, or tablet connect to this SSID and then open a browser pointed at @@ -173,11 +243,12 @@ used to configure the ssid/password info. The problem is that the initial STA+AP channel 1 and you configure it to connect to an AP on channel 6. This requires the ESP8266's AP to also switch to channel 6 disconnecting you in the meantime. -Hostname, description, DHCP, mDNS ---------------------------------- +### Hostname, description, DHCP, mDNS + You can set a hostname on the "home" page, this should be just the hostname and not a domain name, i.e., something like "test-module-1" and not "test-module-1.mydomain.com". This has a number of effects: + - you will see the first 12 chars of the hostname in the menu bar (top left of the page) so if you have multiple modules you can distinguish them visually - esp-link will use the hostname in its DHCP request, which allows you to identify the module's @@ -192,49 +263,47 @@ You can also enter a description of up to 128 characters on the home page (botto allows you to leave a memo for yourself, such as "installed in basement to control the heating system". This descritpion is not used anywhere else. -Troubleshooting ---------------- -- verify that you have sufficient power, borderline power can cause the esp module to seemingly - function until it tries to transmit and the power rail collapses -- if you just cannot flash your esp8266 module (some people call it the zombie mode) make sure you - have gpio0 and gpio15 pulled to gnd with a 1K resistor, gpio2 tied to 3.3V with 1K resistor, and - RX/TX connected without anything in series. If you need to level shift the signal going into the - esp8266's RX use a 1K resistor. Use 115200 baud in the flasher. - (For a permanent set-up I would use higher resistor values but - when nothing seems to work these are the ones I try.) -- if the flashing succeeded, check the "conn" LED to see which mode esp-link is in (see LED info above) -- reset or power-cycle the esp-link to force it to become an access-point if it can't - connect to your network within 15-20 seconds -- if the LED says that esp-link is on your network but you can't get to it, make sure your - laptop is on the same network (and no longer on the esp's network) -- if you do not know the esp-link's IP address on your network, try `esp-link.local`, try to find - the lease in your DHCP server; if all fails, you may have to turn off your access point (or walk - far enough away) and reset/power-cycle esp-link, it will then fail to connect and start its - own AP after 15-20 seconds - Building the firmware --------------------- -The firmware has been built using the [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk) -on a Linux system. Create an esp8266 directory, install the esp-open-sdk into a sub-directory. + +### Linux + +The firmware has been built using the https://github.com/pfalcon/esp-open-sdk[esp-open-sdk] +on a Linux system. Create an esp8266 directory, install the esp-open-sdk into a sub-directory +using the *non-standalone* install (i.e., there should not be an sdk directory in the esp-open-sdk +dir when done installing, if you use the standalone install you will get compilation errors +with std types, such as `uint32_t`). + Download the Espressif SDK (use the version mentioned in the release notes) from their -[download forum](http://bbs.espressif.com/viewforum.php?f=5) and also expand it into a -sub-directory. Then clone the esp-link repository into a third sub-directory. +http://bbs.espressif.com/viewforum.php?f=5[download forum] and also expand it into a +sub-directory. + +Clone the esp-link repository into a third sub-directory and check out the tag you would like, +such as `git checkout v2.1.7`. This way the relative paths in the Makefile will work. If you choose a different directory structure look at the Makefile for the appropriate environment variables to define. +Do not use the source tarballs from the release page on github, +these will give you trouble compiling because the Makefile uses git to determine the esp-link +version being built. In order to OTA-update the esp8266 you should `export ESP_HOSTNAME=...` with the hostname or IP address of your module. -Now, build the code: `make` in the top-level of esp-link. +Now, build the code: `make` in the top-level of esp-link. If you want to se the commands being +issued, use `VERBOSE=1 make`. A few notes from others (I can't fully verify these): + - You may need to install `zlib1g-dev` and `python-serial` - Make sure you have the correct version of the esp_iot_sdk - Make sure the paths at the beginning of the makefile are correct - Make sure `esp-open-sdk/xtensa-lx106-elf/bin` is in the PATH set in the Makefile +### Windows + It is possible to build esp-link on Windows, but it requires a gaggle of software to be installed: + - Install the unofficial sdk, mingw, SourceTree (gui git client), python 2.7, git cli, Java - Use SourceTree to checkout under C:\espressif or wherever you installed the unofficial sdk, (see this thread for the unofficial sdk http://www.esp8266.com/viewtopic.php?t=820) @@ -242,8 +311,8 @@ It is possible to build esp-link on Windows, but it requires a gaggle of softwar the java bin directory under program files. - ... -Updating the firmware over-the-air ---------------------- +### Updating the firmware over-the-air + This firmware supports over-the-air (OTA) flashing, so you do not have to deal with serial flashing again after the initial one! The recommended way to flash is to use `make wiflash` if you are also building the firmware. @@ -272,6 +341,7 @@ The flash configuration and the OTA upgrade process is described in more detail Serial bridge and connections to Arduino, AVR, ARM, LPC microcontrollers ------------------------------------------------------------------------ + In order to connect through the esp-link to a microcontroller use port 23. For example, on linux you can use `nc esp-hostname 23` or `telnet esp-hostname 23`. @@ -282,6 +352,7 @@ broadcast incoming characters from the serial RX to all connections. Use with ca ### Flashing an attached AVR/Arduino There are three options for reprogramming an attached AVR/Arduino microcontroller: + - Use avrdude and point it at port 23 of esp-link. Esp-link automatically detects the programming sequence and issues a reset to the AVR. - Use avrdude and point it at port 2323 of esp-link. This is the same as port 23 except that the @@ -319,11 +390,13 @@ If your AVR doesn't use optiboot then use port 2323 since esp-link may not recog sequence and not issue a reset if you use port 23. If you are having trouble with the built-in programmer and see something like this: -``` + +-------------------- # ./avrflash 192.168.3.104 blink.hex Error checking sync: FAILED to SYNC: abandoned after timeout, got: :\xF/\x00\xCj\xCz\xCJ\xCZ\xC\xAÜ\xC\xAä\xC\xAÜ\xC\xAä\xC\xBì\xC\xBô\xC\xBì\xC\xBô\xC\xAÜ\xC\xAä\xC -``` +-------------------- + the most likely cause is a baud rate mismatch and/or a bad connection from the esp8266 to the AVRs reset line. The baud rate used by esp-link is set on the uC Console web page and, as mentioned above, it will @@ -335,9 +408,11 @@ correct baud rate configured but reset isn't functioning, or reset may be functi baud rate may be incorrect. The output of a successful flash using the built-in programmer looks like this: -``` + +-------------------- Success. 3098 bytes at 57600 baud in 0.8s, 3674B/s 63% efficient -``` +-------------------- + This says that the sketch comprises 3098 bytes of flash, was written in 0.8 seconds (excludes the initial sync time) at 57600 baud, and the 3098 bytes were flashed at a rate of 3674 bytes per second. @@ -347,6 +422,35 @@ The efficiency is not 100% because there is protocol overhead (such as sync, rec length characters) and there is dead time waiting for an ack or preparing the next record to be sent. +### Details of built-in AVR flash algorithm + +The built-in flashing algorithm differs a bit from what avrdude does. The programming protocol +states that STK_GET_SYNC+CRC_EOP (0x30 0x20) should be sent to synchronize, but that works poorly +because the AVR's UART only buffers one character. This means that if STK_GET_SYNC+CRC_EOP is +sent twice there is a high chance that only the last character (CRC_EOP) is actually +received. If that is followed by another STK_GET_SYNC+CRC_EOP sequence then optiboot receives +CRC_EOP+STK_GET_SYNC+CRC_EOP which causes it to abort and run the old sketch. Ending up in that +situation is quite likely because optiboot initializes the UART as one of the first things, but +then goes off an flashes an LED for ~300ms during which it doesn't empty the UART. + +Looking at the optiboot code, the good news is that CRC_EOP+CRC_EOP can be used to get an initial +response without the overrun danger of the normal sync sequence and this is what esp-link does. +The programming sequence runs as follows: + +- esp-link sends a brief reset pulse (1ms) +- esp-link sends CRC_EOP+CRC_EOP ~50ms later +- esp-link sends CRC_EOP+CRC_EOP every ~70-80ms +- eventually optiboot responds with STK_INSYNC+STK_OK (0x14;0x10) +- esp-link sends one CRC_EOP to sort out the even/odd issue +- either optiboot responds with STK_INSYNC+STK_OK or nothing happens for 70-80ms, in which case + esp-link sends another CRC_EOP +- esp-link sends STK_GET_SYNC+CRC_EOP and optiboot responds with STK_INSYNC+STK_OK and we're in + sync now +- esp-link sends the next command (starts with 'u') and programming starts... + +If no sync is achieved, esp-link changes baud rate and the whole thing starts over with a reset +pulse about 600ms, esp-link gives up after about 5 seconds and reports an error. + ### Flashing an attached ARM processor You can reprogram NXP's LPC800-series and many other ARM processors as well by pointing your @@ -376,7 +480,7 @@ baud rate to 115200 is recommended.) Another option is to use a serial-to-tcp port forwarding driver and point that to port 2323 of esp-link. On windows users have reported success with -[HW Virtual Serial Port](http://www.hw-group.com/products/hw_vsp/hw_vsp2_en.html) +http://www.hw-group.com/products/hw_vsp/hw_vsp2_en.html[HW Virtual Serial Port] Now to the interference problem: once the attached esp8266 is reset it starts outputting its 26Mhz clock on gpio0, which needs to be attached to @@ -393,12 +497,13 @@ add a series 100ohm resistor and 100pf capacitor to ground as close to the gpio0 pin as possible (basically a low pass filter); and/or pass the cable connecting the two esp8266's through a ferrite bead. -Debug log ---------- +### Debug log + The esp-link web UI can display the esp-link debug log (os_printf statements in the code). This is handy but sometimes not sufficient. Esp-link also prints the debug info to the UART where it is sometimes more convenient and sometimes less... For this reason three UART debug log modes are supported that can be set in the web UI (and the mode is saved in flash): + - auto: the UART log starts enabled at boot using uart0 and disables itself when esp-link associates with an AP. It re-enables itself if the association is lost. - off: the UART log is always off @@ -410,7 +515,8 @@ esp8266 comes out of reset. This cannot be disabled. Outbound HTTP REST requests and MQTT client ------------------------------------------- -The V2 versions of esp-link support the espduino SLIP protocol that supports simple outbound + +The V2 versions of esp-link use the SLIP protocol over the serial link to support simple outbound HTTP REST requests as well as an MQTT client. The SLIP protocol consists of commands with binary arguments sent from the attached microcontroller to the esp8266, which then performs the command and responds back. @@ -419,13 +525,11 @@ command sent by the uC contains a callback address and the response from the esp with that callback address. This enables asynchronous communication where esp-link can notify the uC when requests complete or when other actions happen, such as wifi connectivity status changes. -You can find a demo sketch in a fork of the espduino library at -https://github.com/tve/espduino in the -[examples/demo folder](https://github.com/tve/espduino/tree/master/espduino/examples/demo). - -More docs forthcoming... +You can find REST and MQTT libraries as well as demo sketches in the +https://github.com/jeelabs/el-client[el-client] repository. Contact ------- + If you find problems with esp-link, please create a github issue. If you have a question, please use the gitter chat link at the top of this page. diff --git a/cmd/cmd.c b/cmd/cmd.c index 1f4c2a5..714d185 100644 --- a/cmd/cmd.c +++ b/cmd/cmd.c @@ -8,14 +8,7 @@ #include "uart.h" #ifdef CMD_DBG -#define DBG(format, ...) os_printf(format, ## __VA_ARGS__) -static const char *cmd_names[] = { - "NULL", "RESET", "IS_READY", "WIFI_CONNECT", - "MQTT_SETUP", "MQTT_CONNECT", "MQTT_DISCONNECT", - "MQTT_PUBLISH", "MQTT_SUBSCRIBE", "MQTT_LWT", "MQTT_EVENTS", - "REST_SETUP", "REST_REQUEST", "REST_SETHEADER", "REST_EVENTS", - "CB_ADD", "CB_EVENTS", -}; +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) #else #define DBG(format, ...) do { } while(0) #endif @@ -25,13 +18,15 @@ extern const CmdList commands[]; //===== ESP -> Serial responses static void ICACHE_FLASH_ATTR -CMD_ProtoWrite(uint8_t data) { +cmdProtoWrite(uint8_t data) { switch(data){ - case SLIP_START: case SLIP_END: - case SLIP_REPL: - uart0_write_char(SLIP_REPL); - uart0_write_char(SLIP_ESC(data)); + uart0_write_char(SLIP_ESC); + uart0_write_char(SLIP_ESC_END); + break; + case SLIP_ESC: + uart0_write_char(SLIP_ESC); + uart0_write_char(SLIP_ESC_ESC); break; default: uart0_write_char(data); @@ -39,98 +34,83 @@ CMD_ProtoWrite(uint8_t data) { } static void ICACHE_FLASH_ATTR -CMD_ProtoWriteBuf(uint8_t *data, short len) { - while (len--) CMD_ProtoWrite(*data++); +cmdProtoWriteBuf(const uint8_t *data, short len) { + while (len--) cmdProtoWrite(*data++); } +static uint16_t resp_crc; + // Start a response, returns the partial CRC -uint16_t ICACHE_FLASH_ATTR -CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc) { - uint16_t crc = 0; - - uart0_write_char(SLIP_START); - CMD_ProtoWriteBuf((uint8_t*)&cmd, 2); - crc = crc16_data((uint8_t*)&cmd, 2, crc); - CMD_ProtoWriteBuf((uint8_t*)&callback, 4); - crc = crc16_data((uint8_t*)&callback, 4, crc); - CMD_ProtoWriteBuf((uint8_t*)&_return, 4); - crc = crc16_data((uint8_t*)&_return, 4, crc); - CMD_ProtoWriteBuf((uint8_t*)&argc, 2); - crc = crc16_data((uint8_t*)&argc, 2, crc); - return crc; +void ICACHE_FLASH_ATTR +cmdResponseStart(uint16_t cmd, uint32_t value, uint16_t argc) { + DBG("cmdResponse: cmd=%d val=%ld argc=%d\n", cmd, value, argc); + + uart0_write_char(SLIP_END); + cmdProtoWriteBuf((uint8_t*)&cmd, 2); + resp_crc = crc16_data((uint8_t*)&cmd, 2, 0); + cmdProtoWriteBuf((uint8_t*)&argc, 2); + resp_crc = crc16_data((uint8_t*)&argc, 2, resp_crc); + cmdProtoWriteBuf((uint8_t*)&value, 4); + resp_crc = crc16_data((uint8_t*)&value, 4, resp_crc); } // Adds data to a response, returns the partial CRC -uint16_t ICACHE_FLASH_ATTR -CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len) { - short pad_len = len+3 - (len+3)%4; // round up to multiple of 4 - CMD_ProtoWriteBuf((uint8_t*)&pad_len, 2); - crc_in = crc16_data((uint8_t*)&pad_len, 2, crc_in); +void ICACHE_FLASH_ATTR +cmdResponseBody(const void *data, uint16_t len) { + cmdProtoWriteBuf((uint8_t*)&len, 2); + resp_crc = crc16_data((uint8_t*)&len, 2, resp_crc); - CMD_ProtoWriteBuf(data, len); - crc_in = crc16_data(data, len, crc_in); + cmdProtoWriteBuf(data, len); + resp_crc = crc16_data(data, len, resp_crc); - if (pad_len > len) { + uint16_t pad = (4-(len&3))&3; // get to multiple of 4 + if (pad > 0) { uint32_t temp = 0; - CMD_ProtoWriteBuf((uint8_t*)&temp, pad_len-len); - crc_in = crc16_data((uint8_t*)&temp, pad_len-len, crc_in); + cmdProtoWriteBuf((uint8_t*)&temp, pad); + resp_crc = crc16_data((uint8_t*)&temp, pad, resp_crc); } - - return crc_in; } // Ends a response void ICACHE_FLASH_ATTR -CMD_ResponseEnd(uint16_t crc) { - CMD_ProtoWriteBuf((uint8_t*)&crc, 2); +cmdResponseEnd() { + cmdProtoWriteBuf((uint8_t*)&resp_crc, 2); uart0_write_char(SLIP_END); } //===== serial -> ESP commands // Execute a parsed command -static uint32_t ICACHE_FLASH_ATTR -CMD_Exec(const CmdList *scp, CmdPacket *packet) { - uint16_t crc = 0; +static void ICACHE_FLASH_ATTR +cmdExec(const CmdList *scp, CmdPacket *packet) { // Iterate through the command table and call the appropriate function while (scp->sc_function != NULL) { if(scp->sc_name == packet->cmd) { - DBG("CMD_Exec: Dispatching cmd=%s\n", cmd_names[packet->cmd]); + DBG("cmdExec: Dispatching cmd=%s\n", scp->sc_text); // call command function - uint32_t ret = scp->sc_function(packet); - // if requestor asked for a response, send it - if (packet->_return){ - DBG("CMD_Exec: Response: 0x%lx, cmd: %d\r\n", ret, packet->cmd); - crc = CMD_ResponseStart(packet->cmd, 0, ret, 0); - CMD_ResponseEnd(crc); - } else { - DBG("CMD_Exec: no response (%lu)\n", packet->_return); - } - return ret; + scp->sc_function(packet); + return; } scp++; } - DBG("CMD_Exec: cmd=%d not found\n", packet->cmd); - return 0; + DBG("cmdExec: cmd=%d not found\n", packet->cmd); } // Parse a packet and print info about it void ICACHE_FLASH_ATTR -CMD_parse_packet(uint8_t *buf, short len) { +cmdParsePacket(uint8_t *buf, short len) { // minimum command length - if (len < 12) return; + if (len < sizeof(CmdPacket)) return; // init pointers into buffer CmdPacket *packet = (CmdPacket*)buf; uint8_t *data_ptr = (uint8_t*)&packet->args; uint8_t *data_limit = data_ptr+len; - DBG("CMD_parse_packet: cmd=%d(%s) argc=%d cb=%p ret=%lu\n", + DBG("cmdParsePacket: cmd=%d argc=%d value=%lu\n", packet->cmd, - cmd_names[packet->cmd], packet->argc, - (void *)packet->callback, - packet->_return + packet->value ); #if 0 @@ -139,7 +119,7 @@ CMD_parse_packet(uint8_t *buf, short len) { uint16_t argc = packet->argc; while (data_ptr+2 < data_limit && argc--) { short l = *(uint16_t*)data_ptr; - os_printf("CMD_parse_packet: arg[%d] len=%d:", argn++, l); + os_printf("cmdParsePacket: arg[%d] len=%d:", argn++, l); data_ptr += 2; while (data_ptr < data_limit && l--) { os_printf(" %02X", *data_ptr++); @@ -149,9 +129,9 @@ CMD_parse_packet(uint8_t *buf, short len) { #endif if (data_ptr <= data_limit) { - CMD_Exec(commands, packet); + cmdExec(commands, packet); } else { - DBG("CMD_parse_packet: packet length overrun, parsing arg %d\n", packet->argc); + DBG("cmdParsePacket: packet length overrun, parsing arg %d\n", packet->argc); } } @@ -159,7 +139,7 @@ CMD_parse_packet(uint8_t *buf, short len) { // Fill out a CmdRequest struct given a CmdPacket void ICACHE_FLASH_ATTR -CMD_Request(CmdRequest *req, CmdPacket* cmd) { +cmdRequest(CmdRequest *req, CmdPacket* cmd) { req->cmd = cmd; req->arg_num = 0; req->arg_ptr = (uint8_t*)&cmd->args; @@ -167,14 +147,14 @@ CMD_Request(CmdRequest *req, CmdPacket* cmd) { // Return the number of arguments given a command struct uint32_t ICACHE_FLASH_ATTR -CMD_GetArgc(CmdRequest *req) { +cmdGetArgc(CmdRequest *req) { return req->cmd->argc; } // Copy the next argument from a command structure into the data pointer, returns 0 on success // -1 on error int32_t ICACHE_FLASH_ATTR -CMD_PopArg(CmdRequest *req, void *data, uint16_t len) { +cmdPopArg(CmdRequest *req, void *data, uint16_t len) { uint16_t length; if (req->arg_num >= req->cmd->argc) @@ -185,7 +165,7 @@ CMD_PopArg(CmdRequest *req, void *data, uint16_t len) { req->arg_ptr += 2; os_memcpy(data, req->arg_ptr, length); - req->arg_ptr += length; + req->arg_ptr += (length+3)&~3; // round up to multiple of 4 req->arg_num ++; return 0; @@ -193,7 +173,7 @@ CMD_PopArg(CmdRequest *req, void *data, uint16_t len) { // Skip the next argument void ICACHE_FLASH_ATTR -CMD_SkipArg(CmdRequest *req) { +cmdSkipArg(CmdRequest *req) { uint16_t length; if (req->arg_num >= req->cmd->argc) return; @@ -201,12 +181,12 @@ CMD_SkipArg(CmdRequest *req) { length = *(uint16_t*)req->arg_ptr; req->arg_ptr += 2; - req->arg_ptr += length; + req->arg_ptr += (length+3)&~3; req->arg_num ++; } // Return the length of the next argument uint16_t ICACHE_FLASH_ATTR -CMD_ArgLen(CmdRequest *req) { +cmdArgLen(CmdRequest *req) { return *(uint16_t*)req->arg_ptr; } diff --git a/cmd/cmd.h b/cmd/cmd.h index ecd8140..6b4b018 100644 --- a/cmd/cmd.h +++ b/cmd/cmd.h @@ -6,19 +6,11 @@ #define CMD_H #include -// Escape chars used by tuanpmt, dunno why he didn't use std ones... -#define SLIP_START 0x7E -#define SLIP_END 0x7F -#define SLIP_REPL 0x7D -#define SLIP_ESC(x) (x ^ 0x20) - -#if 0 -// Proper SLIP escape chars from RFC +// Standard SLIP escape chars from RFC #define SLIP_END 0300 // indicates end of packet #define SLIP_ESC 0333 // indicates byte stuffing #define SLIP_ESC_END 0334 // ESC ESC_END means END data byte #define SLIP_ESC_ESC 0335 // ESC ESC_ESC means ESC data byte -#endif typedef struct __attribute__((__packed__)) { uint16_t len; // length of data @@ -27,9 +19,8 @@ typedef struct __attribute__((__packed__)) { typedef struct __attribute__((__packed__)) { uint16_t cmd; // command to perform, from CmdName enum - uint32_t callback; // callback pointer to embed in response - uint32_t _return; // return value to embed in response (?) uint16_t argc; // number of arguments to command + uint32_t value; // callback pointer for response or first argument CmdArg args[0]; // really args[argc] } CmdPacket; @@ -41,66 +32,70 @@ typedef struct { typedef enum { CMD_NULL = 0, - CMD_RESET, // reset esp (not honored in this implementation) - CMD_IS_READY, // health-check - CMD_WIFI_CONNECT, // (3) connect to AP (not honored in this implementation) - CMD_MQTT_SETUP, - CMD_MQTT_CONNECT, - CMD_MQTT_DISCONNECT, - CMD_MQTT_PUBLISH, - CMD_MQTT_SUBSCRIBE, - CMD_MQTT_LWT, - CMD_MQTT_EVENTS, - CMD_REST_SETUP, // (11) + CMD_SYNC, // synchronize and clear + CMD_RESP_V, // response with a value + CMD_RESP_CB, // response with a callback + CMD_WIFI_STATUS, // get the current wifi status + CMD_CB_ADD, + CMD_CB_EVENTS, + CMD_GET_TIME, // get current time in seconds since the unix epoch + + CMD_MQTT_SETUP = 10, // set-up callbacks + CMD_MQTT_PUBLISH, // publish a message + CMD_MQTT_SUBSCRIBE, // subscribe to a topic + CMD_MQTT_LWT, // set the last-will-topic and messge + + CMD_REST_SETUP = 20, CMD_REST_REQUEST, CMD_REST_SETHEADER, - CMD_REST_EVENTS, - CMD_CB_ADD, // 15 - CMD_CB_EVENTS } CmdName; -typedef uint32_t (*cmdfunc_t)(CmdPacket *cmd); +typedef void (*cmdfunc_t)(CmdPacket *cmd); typedef struct { - CmdName sc_name; - cmdfunc_t sc_function; + CmdName sc_name; // name as CmdName enum + char *sc_text; // name as string + cmdfunc_t sc_function; // pointer to function } CmdList; #define CMD_CBNLEN 16 typedef struct { char name[CMD_CBNLEN]; uint32_t callback; -} cmdCallback; +} CmdCallback; // Used by slip protocol to cause parsing of a received packet -void CMD_parse_packet(uint8_t *buf, short len); +void cmdParsePacket(uint8_t *buf, short len); // Return the info about a callback to the attached uC by name, these are callbacks that the // attached uC registers using the ADD_SENSOR command -cmdCallback* CMD_GetCbByName(char* name); +CmdCallback* cmdGetCbByName(char* name); + +// Add a callback +uint32_t cmdAddCb(char *name, uint32_t callback); // Responses -// Start a response, returns the partial CRC -uint16_t CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc); -// Adds data to a response, returns the partial CRC -uint16_t CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len); +// Start a response +void cmdResponseStart(uint16_t cmd, uint32_t value, uint16_t argc); +// Adds data to a response +void cmdResponseBody(const void* data, uint16_t len); // Ends a response -void CMD_ResponseEnd(uint16_t crc); +void cmdResponseEnd(); -//void CMD_Response(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc, CmdArg* args[]); +//void cmdResponse(uint16_t cmd, uint32_t callback, uint32_t value, uint16_t argc, CmdArg* args[]); // Requests // Fill out a CmdRequest struct given a CmdPacket -void CMD_Request(CmdRequest *req, CmdPacket* cmd); +void cmdRequest(CmdRequest *req, CmdPacket* cmd); // Return the number of arguments given a request -uint32_t CMD_GetArgc(CmdRequest *req); +uint32_t cmdGetArgc(CmdRequest *req); // Return the length of the next argument -uint16_t CMD_ArgLen(CmdRequest *req); +uint16_t cmdArgLen(CmdRequest *req); // Copy next arg from request into the data pointer, returns 0 on success, -1 on error -int32_t CMD_PopArg(CmdRequest *req, void *data, uint16_t len); +int32_t cmdPopArg(CmdRequest *req, void *data, uint16_t len); // Skip next arg -void CMD_SkipArg(CmdRequest *req); +void cmdSkipArg(CmdRequest *req); #endif diff --git a/cmd/handlers.c b/cmd/handlers.c index 5baf821..cea05cd 100644 --- a/cmd/handlers.c +++ b/cmd/handlers.c @@ -18,11 +18,10 @@ #define DBG(format, ...) do { } while(0) #endif -static uint32_t CMD_Null(CmdPacket *cmd); -static uint32_t CMD_IsReady(CmdPacket *cmd); -static uint32_t CMD_Reset(CmdPacket *cmd); -static uint32_t CMD_WifiConnect(CmdPacket *cmd); -static uint32_t CMD_AddCallback(CmdPacket *cmd); +static void cmdNull(CmdPacket *cmd); +static void cmdSync(CmdPacket *cmd); +static void cmdWifiStatus(CmdPacket *cmd); +static void cmdAddCallback(CmdPacket *cmd); // keep track of last status sent to uC so we can notify it when it changes static uint8_t lastWifiStatus = wifiIsDisconnected; @@ -30,137 +29,143 @@ static bool wifiCbAdded = false; // Command dispatch table for serial -> ESP commands const CmdList commands[] = { - {CMD_NULL, CMD_Null}, - {CMD_RESET, CMD_Reset}, - {CMD_IS_READY, CMD_IsReady}, - {CMD_WIFI_CONNECT, CMD_WifiConnect}, + {CMD_NULL, "NULL", cmdNull}, // no-op + {CMD_SYNC, "SYNC", cmdSync}, // synchronize + {CMD_WIFI_STATUS, "WIFI_STATUS", cmdWifiStatus}, + {CMD_CB_ADD, "ADD_CB", cmdAddCallback}, #ifdef MQTT - {CMD_MQTT_SETUP, MQTTCMD_Setup}, - {CMD_MQTT_CONNECT, MQTTCMD_Connect}, - {CMD_MQTT_DISCONNECT, MQTTCMD_Disconnect}, - {CMD_MQTT_PUBLISH, MQTTCMD_Publish}, - {CMD_MQTT_SUBSCRIBE , MQTTCMD_Subscribe}, - {CMD_MQTT_LWT, MQTTCMD_Lwt}, + {CMD_MQTT_SETUP, "MQTT_SETUP", MQTTCMD_Setup}, + {CMD_MQTT_PUBLISH, "MQTT_PUB", MQTTCMD_Publish}, + {CMD_MQTT_SUBSCRIBE , "MQTT_SUB", MQTTCMD_Subscribe}, + {CMD_MQTT_LWT, "MQTT_LWT", MQTTCMD_Lwt}, #endif #ifdef REST - {CMD_REST_SETUP, REST_Setup}, - {CMD_REST_REQUEST, REST_Request}, - {CMD_REST_SETHEADER, REST_SetHeader}, + {CMD_REST_SETUP, "REST_SETUP", REST_Setup}, + {CMD_REST_REQUEST, "REST_REQ", REST_Request}, + {CMD_REST_SETHEADER, "REST_SETHDR", REST_SetHeader}, #endif - {CMD_CB_ADD, CMD_AddCallback}, - {CMD_NULL, NULL} }; +//===== List of registered callbacks (to uC) + // WifiCb plus 10 for sensors #define MAX_CALLBACKS 12 -cmdCallback callbacks[MAX_CALLBACKS]; // cleared in CMD_Reset - -// Command handler for IsReady (healthcheck) command -static uint32_t ICACHE_FLASH_ATTR -CMD_IsReady(CmdPacket *cmd) { - return 1; -} - -// Command handler for Null command -static uint32_t ICACHE_FLASH_ATTR -CMD_Null(CmdPacket *cmd) { - return 1; -} - -// Command handler for Reset command, this was originally to reset the ESP but we don't want to -// do that is esp-link. It is still good to clear any information the ESP has about the attached -// uC. -static uint32_t ICACHE_FLASH_ATTR -CMD_Reset(CmdPacket *cmd) { - // clear callbacks table - os_memset(callbacks, 0, sizeof(callbacks)); - return 1; -} +CmdCallback callbacks[MAX_CALLBACKS]; // cleared in cmdSync -static uint32_t ICACHE_FLASH_ATTR -CMD_AddCb(char* name, uint32_t cb) { +uint32_t ICACHE_FLASH_ATTR +cmdAddCb(char* name, uint32_t cb) { for (uint8_t i = 0; i < MAX_CALLBACKS; i++) { - //os_printf("CMD_AddCb: index %d name=%s cb=%p\n", i, callbacks[i].name, + //os_printf("cmdAddCb: index %d name=%s cb=%p\n", i, callbacks[i].name, // (void *)callbacks[i].callback); // find existing callback or add to the end if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0 || callbacks[i].name[0] == '\0') { os_strncpy(callbacks[i].name, name, sizeof(callbacks[i].name)); callbacks[i].name[CMD_CBNLEN-1] = 0; // strncpy doesn't null terminate callbacks[i].callback = cb; - DBG("CMD_AddCb: cb %s added at index %d\n", callbacks[i].name, i); + DBG("cmdAddCb: '%s'->0x%lx added at %d\n", callbacks[i].name, cb, i); return 1; } } return 0; } -cmdCallback* ICACHE_FLASH_ATTR -CMD_GetCbByName(char* name) { +CmdCallback* ICACHE_FLASH_ATTR +cmdGetCbByName(char* name) { for (uint8_t i = 0; i < MAX_CALLBACKS; i++) { - //os_printf("CMD_GetCbByName: index %d name=%s cb=%p\n", i, callbacks[i].name, + //os_printf("cmdGetCbByName: index %d name=%s cb=%p\n", i, callbacks[i].name, // (void *)callbacks[i].callback); // if callback doesn't exist or it's null if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0) { - DBG("CMD_GetCbByName: cb %s found at index %d\n", name, i); + DBG("cmdGetCbByName: cb %s found at index %d\n", name, i); return &callbacks[i]; } } - os_printf("CMD_GetCbByName: cb %s not found\n", name); + os_printf("cmdGetCbByName: cb %s not found\n", name); return 0; } +//===== Wifi callback + // Callback from wifi subsystem to notify us of status changes static void ICACHE_FLASH_ATTR -CMD_WifiCb(uint8_t wifiStatus) { +cmdWifiCb(uint8_t wifiStatus) { if (wifiStatus != lastWifiStatus){ - DBG("CMD_WifiCb: wifiStatus=%d\n", wifiStatus); + DBG("cmdWifiCb: wifiStatus=%d\n", wifiStatus); lastWifiStatus = wifiStatus; - cmdCallback *wifiCb = CMD_GetCbByName("wifiCb"); + CmdCallback *wifiCb = cmdGetCbByName("wifiCb"); if ((uint32_t)wifiCb->callback != -1) { uint8_t status = wifiStatus == wifiGotIP ? 5 : 1; - uint16_t crc = CMD_ResponseStart(CMD_WIFI_CONNECT, (uint32_t)wifiCb->callback, 0, 1); - crc = CMD_ResponseBody(crc, (uint8_t*)&status, 1); - CMD_ResponseEnd(crc); + cmdResponseStart(CMD_RESP_CB, (uint32_t)wifiCb->callback, 1); + cmdResponseBody((uint8_t*)&status, 1); + cmdResponseEnd(); } } } -// Command handler for Wifi connect command -static uint32_t ICACHE_FLASH_ATTR -CMD_WifiConnect(CmdPacket *cmd) { +//===== Command handlers + +// Command handler for Null command +static void ICACHE_FLASH_ATTR +cmdNull(CmdPacket *cmd) { +} + +// Command handler for sync command +static void ICACHE_FLASH_ATTR +cmdSync(CmdPacket *cmd) { CmdRequest req; - CMD_Request(&req, cmd); - if(cmd->argc != 2 || cmd->callback == 0) - return 0; + cmdRequest(&req, cmd); + if(cmd->argc != 0 || cmd->value == 0) { + cmdResponseStart(CMD_RESP_V, 0, 0); + cmdResponseEnd(); + return; + } + + // clear callbacks table + os_memset(callbacks, 0, sizeof(callbacks)); + // register our callback with wifi subsystem if (!wifiCbAdded) { - wifiAddStateChangeCb(CMD_WifiCb); // register our callback with wifi subsystem + wifiAddStateChangeCb(cmdWifiCb); wifiCbAdded = true; } - CMD_AddCb("wifiCb", (uint32_t)cmd->callback); // save the MCU's callback + + // send OK response + cmdResponseStart(CMD_RESP_V, cmd->value, 0); + cmdResponseEnd(); + + // save the MCU's callback and trigger an initial callback + cmdAddCb("wifiCb", cmd->value); lastWifiStatus = 0xff; // set to invalid value so we immediately send status cb in all cases - CMD_WifiCb(wifiState); + cmdWifiCb(wifiState); - return 1; + return; } +// Command handler for wifi status command +static void ICACHE_FLASH_ATTR +cmdWifiStatus(CmdPacket *cmd) { + cmdResponseStart(CMD_RESP_V, wifiState, 0); + cmdResponseEnd(); + return; +} + + // Command handler to add a callback to the named-callbacks list, this is for a callback to the uC -static uint32_t ICACHE_FLASH_ATTR -CMD_AddCallback(CmdPacket *cmd) { +static void ICACHE_FLASH_ATTR +cmdAddCallback(CmdPacket *cmd) { CmdRequest req; - CMD_Request(&req, cmd); - if (cmd->argc != 1 || cmd->callback == 0) - return 0; + cmdRequest(&req, cmd); + if (cmd->argc != 1 || cmd->value == 0) return; char name[16]; uint16_t len; - // get the sensor name - len = CMD_ArgLen(&req); - if (len > 15) return 0; // max size of name is 15 characters - if (CMD_PopArg(&req, (uint8_t *)name, len)) return 0; + // get the callback name + len = cmdArgLen(&req); + if (len > 15) return; // max size of name is 15 characters + if (cmdPopArg(&req, (uint8_t *)name, len)) return; name[len] = 0; - DBG("CMD_AddCallback: name=%s\n", name); + DBG("cmdAddCallback: name=%s\n", name); - return CMD_AddCb(name, (uint32_t)cmd->callback); // save the sensor callback + cmdAddCb(name, cmd->value); // save the sensor callback } diff --git a/esp-link/cgi.c b/esp-link/cgi.c index 6959833..b80fa51 100644 --- a/esp-link/cgi.c +++ b/esp-link/cgi.c @@ -206,7 +206,8 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) { "{ " "\"menu\": [ " "\"Home\", \"/home.html\", " - "\"WiFI\", \"/wifi/wifi.html\", " + "\"WiFi Station\", \"/wifi/wifiSta.html\", " + "\"WiFi Soft-AP\", \"/wifi/wifiAp.html\", " "\"µC Console\", \"/console.html\", " "\"Services\", \"/services.html\", " #ifdef MQTT diff --git a/esp-link/cgioptiboot.c b/esp-link/cgioptiboot.c index da1cb2f..b709b2d 100644 --- a/esp-link/cgioptiboot.c +++ b/esp-link/cgioptiboot.c @@ -10,11 +10,11 @@ #include "serbridge.h" #include "serled.h" -#define SYNC_TIMEOUT 4800 // to achieve sync, in milliseconds -#define SYNC_INTERVAL 77 // interval at which we try to sync +#define INIT_DELAY 150 // wait this many millisecs before sending anything #define BAUD_INTERVAL 600 // interval after which we change baud rate -#define PGM_TIMEOUT 20000 // timeout when sync is achieved, in milliseconds +#define PGM_TIMEOUT 20000 // timeout after sync is achieved, in milliseconds #define PGM_INTERVAL 200 // send sync at this interval in ms when in programming mode +#define ATTEMPTS 8 // number of attempts total to make #ifdef OPTIBOOT_DBG #define DBG(format, ...) os_printf(format, ## __VA_ARGS__) @@ -29,15 +29,14 @@ static ETSTimer optibootTimer; static enum { // overall programming states - stateSync = 0, // trying to get initial response - stateSync2, // trying to get in sync - stateSync3, // trying to get second sync + stateInit = 0, // initial delay + stateSync, // waiting to hear back stateGetSig, // reading device signature stateGetVersLo, // reading optiboot version, low bits stateGetVersHi, // reading optiboot version, high bits stateProg, // programming... } progState; -static short syncCnt; // counter & timeout for sync attempts +static char* progStates[] = { "init", "sync", "sig", "ver0", "ver1", "prog" }; static short baudCnt; // counter for sync attempts at different baud rates static short ackWait; // counter of expected ACKs static uint16_t optibootVers; @@ -70,12 +69,11 @@ static void optibootTimerCB(void *); static void optibootUartRecv(char *buffer, short length); static bool processRecord(char *buf, short len); static bool programPage(void); -static void armTimer(void); +static void armTimer(uint32_t ms); static void initBaud(void); static void ICACHE_FLASH_ATTR optibootInit() { - progState = stateSync; - syncCnt = 0; + progState = stateInit; baudCnt = 0; uart0_baud(flashConfig.baud_rate); ackWait = 0; @@ -142,7 +140,7 @@ int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) { // start sync timer os_timer_disarm(&optibootTimer); os_timer_setfn(&optibootTimer, optibootTimerCB, NULL); - os_timer_arm(&optibootTimer, 50, 0); // fire in 50ms and don't recur + os_timer_arm(&optibootTimer, INIT_DELAY, 0); // respond with optimistic OK noCacheHeaders(connData, 204); @@ -155,7 +153,7 @@ int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) { if (!errMessage[0] && progState >= stateProg) { char buf[64]; DBG("OB got sync\n"); - os_sprintf(buf, "SYNC at %ld baud: Optiboot %d.%d", + os_sprintf(buf, "SYNC at %ld baud: bootloader v%d.%d", baudRate, optibootVers>>8, optibootVers&0xff); httpdSend(connData, buf, -1); } else if (errMessage[0] && progState == stateSync) { @@ -202,7 +200,8 @@ static uint32_t ICACHE_FLASH_ATTR getHexValue(char *buf, short len) { //===== Cgi to write firmware to Optiboot, requires prior sync call int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) { if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - DBG("OB pgm: state=%d PrivData=%p postLen=%d\n", progState, connData->cgiPrivData, connData->post->len); + if (!optibootData) + DBG("OB pgm: state=%d postLen=%d\n", progState, connData->post->len); // check that we have sync if (errMessage[0] || progState < stateProg) { @@ -427,7 +426,7 @@ static bool pollAck() { // Program a flash page static bool ICACHE_FLASH_ATTR programPage(void) { if (optibootData->pageLen == 0) return true; - armTimer(); // keep the timerCB out of the picture + armTimer(PGM_TIMEOUT); // keep the timerCB out of the picture if (ackWait > 7) { os_sprintf(errMessage, "Lost sync while programming\n"); @@ -436,7 +435,7 @@ static bool ICACHE_FLASH_ATTR programPage(void) { uint16_t pgmLen = optibootData->pageLen; if (pgmLen > optibootData->pgmSz) pgmLen = optibootData->pgmSz; - DBG("OB pgm %d@0x%lx ackWait=%d\n", pgmLen, optibootData->address, ackWait); + DBG("OB pgm %d@0x%lx\n", pgmLen, optibootData->address); // send address to optiboot (little endian format) #ifdef DBG_GPIO5 @@ -448,12 +447,12 @@ static bool ICACHE_FLASH_ATTR programPage(void) { uart0_write_char(addr & 0xff); uart0_write_char(addr >> 8); uart0_write_char(CRC_EOP); - armTimer(); + armTimer(PGM_TIMEOUT); if (!pollAck()) { DBG("OB pgm failed in load address\n"); return false; } - armTimer(); + armTimer(PGM_TIMEOUT); // send page length (big-endian format, go figure...) #ifdef DBG_GPIO5 @@ -470,9 +469,9 @@ static bool ICACHE_FLASH_ATTR programPage(void) { uart0_write_char(optibootData->pageBuf[i]); uart0_write_char(CRC_EOP); - armTimer(); + armTimer(PGM_TIMEOUT); bool ok = pollAck(); - armTimer(); + armTimer(PGM_TIMEOUT); if (!ok) { DBG("OB pgm failed in prog page\n"); return false; @@ -490,18 +489,17 @@ static bool ICACHE_FLASH_ATTR programPage(void) { //===== Rebooting and getting sync -static void ICACHE_FLASH_ATTR armTimer() { +static void ICACHE_FLASH_ATTR armTimer(uint32_t ms) { os_timer_disarm(&optibootTimer); - // time-out every 50ms, except when programming to allow for 9600baud (133ms for 128 bytes) - os_timer_arm(&optibootTimer, progState==stateProg ? PGM_INTERVAL : SYNC_INTERVAL, 0); + os_timer_arm(&optibootTimer, ms, 0); } static int baudRates[] = { 0, 9600, 57600, 115200 }; static void ICACHE_FLASH_ATTR setBaud() { - baudRate = baudRates[(syncCnt / (BAUD_INTERVAL/SYNC_INTERVAL)) % 4]; + baudRate = baudRates[(baudCnt++) % 4]; uart0_baud(baudRate); - //DBG("OB changing to %d baud\n", b); + //DBG("OB changing to %ld baud\n", baudRate); } static void ICACHE_FLASH_ATTR initBaud() { @@ -511,45 +509,41 @@ static void ICACHE_FLASH_ATTR initBaud() { static void ICACHE_FLASH_ATTR optibootTimerCB(void *arg) { // see whether we've issued so many sync in a row that it's time to give up - syncCnt++; switch (progState) { - case stateSync: // we're trying to get sync, all we do here is send a sync request - if (syncCnt >= SYNC_TIMEOUT/SYNC_INTERVAL) { + case stateInit: // initial delay expired, send sync chars + uart0_write_char(STK_GET_SYNC); + uart0_write_char(CRC_EOP); + progState++; + armTimer(BAUD_INTERVAL-INIT_DELAY); + return; + case stateSync: // oops, must have not heard back!? + if (baudCnt > ATTEMPTS) { // we're doomed, give up - DBG("OB sync abandoned after timeout, state=%d syncCnt=%d\n", progState, syncCnt); + DBG("OB abandoned after %d attempts\n", baudCnt); optibootInit(); - strcpy(errMessage, "sync abandoned after timeout"); + strcpy(errMessage, "sync abandoned after 8 attempts"); return; } - if (syncCnt % (BAUD_INTERVAL/SYNC_INTERVAL) == 0) { - // time to switch baud rate and issue a reset - setBaud(); - serbridgeReset(); - // no point sending chars if we just switched - } else { - //uart0_write_char(STK_GET_SYNC); - uart0_write_char(CRC_EOP); - uart0_write_char(CRC_EOP); - } - break; - case stateSync2: // need one more CRC_EOP? - uart0_write_char(CRC_EOP); - progState++; - break; + // time to switch baud rate and issue a reset + DBG("OB no sync response @%ld baud\n", baudRate); + setBaud(); + serbridgeReset(); + progState = stateInit; + armTimer(INIT_DELAY); + return; case stateProg: // we're programming and we timed-out of inaction uart0_write_char(STK_GET_SYNC); uart0_write_char(CRC_EOP); ackWait++; // we now expect an ACK - break; + armTimer(PGM_INTERVAL); + return; default: // we're trying to get some info from optiboot and it should have responded! optibootInit(); // abort - os_sprintf(errMessage, "No response in state %d\n", progState); + os_sprintf(errMessage, "No response in state %s(%d) @%ld baud\n", + progStates[progState], progState, baudRate); DBG("OB %s\n", errMessage); return; // do not re-arm timer } - - // we need to come back... - armTimer(); } // skip in-sync responses @@ -575,8 +569,9 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { // dispatch based the current state switch (progState) { - case stateSync: // we're trying to get a sync response - case stateSync3: // we're trying to get a second sync response + case stateInit: // we haven't sent anything, this must be garbage + break; + case stateSync: // we're trying to get a sync response // look for STK_INSYNC+STK_OK at end of buffer if (responseLen > 0 && responseBuf[responseLen-1] == STK_INSYNC) { // missing STK_OK after STK_INSYNC, shift stuff out and try again @@ -584,19 +579,14 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { responseLen = 1; } else if (responseLen > 1 && responseBuf[responseLen-2] == STK_INSYNC && responseBuf[responseLen-1] == STK_OK) { - // got sync response, send more... + // got sync response os_memcpy(responseBuf, responseBuf+2, responseLen-2); responseLen -= 2; - if (progState==stateSync) { - // need to deal with odd-even sync issue, send one more to see whether we get a response - uart0_write_char(CRC_EOP); - } else { - // got clean sync, send request to get signature - uart0_write_char(STK_READ_SIGN); - uart0_write_char(CRC_EOP); - } + // send request to get signature + uart0_write_char(STK_READ_SIGN); + uart0_write_char(CRC_EOP); progState++; - armTimer(); // reset timer + armTimer(PGM_INTERVAL); // reset timer } else { // nothing useful, keep at most half the buffer for error message purposes if (responseLen > RESP_SZ/2) { @@ -606,18 +596,6 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { } } break; - case stateSync2: // we're trying to actually get in sync - if (responseLen > 1 && responseBuf[responseLen-2] == STK_INSYNC && - responseBuf[responseLen-1] == STK_OK) { - // got sync response, send signature request - os_memcpy(responseBuf, responseBuf+2, responseLen-2); - responseLen -= 2; - uart0_write_char(STK_READ_SIGN); - uart0_write_char(CRC_EOP); - progState = stateGetSig; - } - armTimer(); // reset timer - break; case stateGetSig: // expecting signature responseLen = skipInSync(responseBuf, responseLen); if (responseLen >= 5 && responseBuf[0] == STK_INSYNC && responseBuf[4] == STK_OK) { @@ -627,7 +605,7 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { uart0_write_char(STK_GET_PARAMETER); uart0_write_char(0x82); uart0_write_char(CRC_EOP); - armTimer(); // reset timer + armTimer(PGM_INTERVAL); // reset timer } else { optibootInit(); // abort os_sprintf(errMessage, "Bad programmer signature: 0x%02x 0x%02x 0x%02x\n", @@ -646,7 +624,7 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { uart0_write_char(STK_GET_PARAMETER); uart0_write_char(0x81); uart0_write_char(CRC_EOP); - armTimer(); // reset timer + armTimer(PGM_INTERVAL); // reset timer } break; case stateGetVersHi: // expecting version @@ -655,7 +633,7 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { progState++; os_memcpy(responseBuf, responseBuf+3, responseLen-3); responseLen -= 3; - armTimer(); // reset timer + armTimer(PGM_INTERVAL); // reset timer ackWait = 0; } break; @@ -666,7 +644,7 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { os_memmove(responseBuf, responseBuf+2, responseLen-2); responseLen -= 2; } - armTimer(); // reset timer + armTimer(PGM_INTERVAL); // reset timer default: break; } diff --git a/esp-link/cgiwifi.c b/esp-link/cgiwifi.c old mode 100644 new mode 100755 index a76634c..0971dbc --- a/esp-link/cgiwifi.c +++ b/esp-link/cgiwifi.c @@ -47,12 +47,20 @@ Cgi/template routines for the /wifi url. } while(0) +# define VERS_STR_STR(V) #V +# define VERS_STR(V) VERS_STR_STR(V) bool mdns_started = false; // ===== wifi status change callbacks static WifiStateChangeCb wifi_state_change_cb[4]; +// Temp store for new station config +struct station_config stconf; + +// Temp store for new ap config +struct softap_config apconf; + uint8_t wifiState = wifiIsDisconnected; // reasons for which a connection failed uint8_t wifiReason = 0; @@ -287,14 +295,21 @@ static int ICACHE_FLASH_ATTR cgiWiFiGetScan(HttpdConnData *connData) { } int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { - if (connData->requestType == HTTPD_METHOD_GET) { - return cgiWiFiGetScan(connData); - } else if (connData->requestType == HTTPD_METHOD_POST) { - return cgiWiFiStartScan(connData); - } else { - jsonHeader(connData, 404); - return HTTPD_CGI_DONE; - } + if (connData->requestType == HTTPD_METHOD_GET) { + return cgiWiFiGetScan(connData); + }else if(connData->requestType == HTTPD_METHOD_POST) { + // DO NOT start APs scan in AP mode + int mode = wifi_get_opmode(); + if(mode==2){ + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + }else{ + return cgiWiFiStartScan(connData); + } + }else{ + jsonHeader(connData, 404); + return HTTPD_CGI_DONE; + } } // ===== timers to change state and rescue from failed associations @@ -311,8 +326,9 @@ static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { int m = wifi_get_opmode() & 0x3; NOTICE("check: mode=%s status=%d", wifiMode[m], x); - if (x == STATION_GOT_IP) { - if (m != 1) { + if(m!=2){ + if ( x == STATION_GOT_IP ) { + if (m != 1) { #ifdef CHANGE_TO_STA // We're happily connected, go to STA mode NOTICE("got IP. Going into STA mode.."); @@ -326,15 +342,15 @@ static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { if (m != 3) { NOTICE("connect failed. Going into STA+AP mode.."); wifi_set_opmode(3); + wifi_softap_set_config(&apconf); } log_uart(true); INFO("Enabling/continuing uart log"); os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); + } } } -// Temp store for new ap info. -static struct station_config stconf; // Reassociate timer to delay change of association so the original request can finish static ETSTimer reassTimer; @@ -356,6 +372,12 @@ static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { // This cgi uses the routines above to connect to a specific access point with the // given ESSID using the given password. int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { + int mode = wifi_get_opmode(); + if(mode == 2){ + jsonHeader(connData, 400); + httpdSend(connData, "Can't associate to an AP en SoftAP mode", -1); + return HTTPD_CGI_DONE; + } char essid[128]; char passwd[128]; @@ -504,38 +526,198 @@ int ICACHE_FLASH_ATTR cgiWiFiSpecial(HttpdConnData *connData) { return HTTPD_CGI_DONE; } +// ==== Soft-AP related functions + +// Change Soft-AP main settings +int ICACHE_FLASH_ATTR cgiApSettingsChange(HttpdConnData *connData) { + + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + // No changes for Soft-AP in STA mode + int mode = wifi_get_opmode(); + if ( mode == 1 ){ + jsonHeader(connData, 400); + httpdSend(connData, "No changes allowed in STA mode", -1); + return HTTPD_CGI_DONE; + } + + char buff[96]; + int len; + + // Check extra security measure, this must be 1 + len=httpdFindArg(connData->getArgs, "100", buff, sizeof(buff)); + if(len>0){ + if(atoi(buff)!=1){ + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + } + } + // Set new SSID + len=httpdFindArg(connData->getArgs, "ap_ssid", buff, sizeof(buff)); + if(checkString(buff) && len>7 && len<32){ + // STRING PREPROCESSING DONE IN CLIENT SIDE + os_memset(apconf.ssid, 0, 32); + os_memcpy(apconf.ssid, buff, len); + apconf.ssid_len = len; + }else{ + jsonHeader(connData, 400); + httpdSend(connData, "SSID not valid or out of range", -1); + return HTTPD_CGI_DONE; + } + // Set new PASSWORD + len=httpdFindArg(connData->getArgs, "ap_password", buff, sizeof(buff)); + if(checkString(buff) && len>7 && len<64){ + // String preprocessing done in client side, wifiap.js line 31 + os_memset(apconf.password, 0, 64); + os_memcpy(apconf.password, buff, len); + }else if (len == 0){ + os_memset(apconf.password, 0, 64); + }else{ + jsonHeader(connData, 400); + httpdSend(connData, "PASSWORD not valid or out of range", -1); + return HTTPD_CGI_DONE; + } + // Set auth mode + if(len != 0){ + // Set authentication mode, before password to check open settings + len=httpdFindArg(connData->getArgs, "ap_authmode", buff, sizeof(buff)); + if(len>0){ + int value = atoi(buff); + if(value >= 0 && value <= 4){ + apconf.authmode = value; + }else{ + // If out of range set by default + apconf.authmode = 4; + } + }else{ + // Valid password but wrong auth mode, default 4 + apconf.authmode = 4; + } + }else{ + apconf.authmode = 0; + } + // Set max connection number + len=httpdFindArg(connData->getArgs, "ap_maxconn", buff, sizeof(buff)); + if(len>0){ + + int value = atoi(buff); + if(value > 0 && value <= 4){ + apconf.max_connection = value; + }else{ + // If out of range, set by default + apconf.max_connection = 4; + } + } + // Set beacon interval value + len=httpdFindArg(connData->getArgs, "ap_beacon", buff, sizeof(buff)); + if(len>0){ + int value = atoi(buff); + if(value >= 100 && value <= 60000){ + apconf.beacon_interval = value; + }else{ + // If out of range, set by default + apconf.beacon_interval = 100; + } + } + // Set ssid to be hidden or not + len=httpdFindArg(connData->getArgs, "ap_hidden", buff, sizeof(buff)); + if(len>0){ + int value = atoi(buff); + if(value == 0 || value == 1){ + apconf.ssid_hidden = value; + }else{ + // If out of range, set by default + apconf.ssid_hidden = 0; + } + } + // Store new configuration + wifi_softap_set_config(&apconf); + + jsonHeader(connData, 200); + return HTTPD_CGI_DONE; +} + +// Get current Soft-AP settings +int ICACHE_FLASH_ATTR cgiApSettingsInfo(HttpdConnData *connData) { + + char buff[1024]; + if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + os_sprintf(buff, + "{ " + "\"ap_ssid\": \"%s\", " + "\"ap_password\": \"%s\", " + "\"ap_authmode\": %d, " + "\"ap_maxconn\": %d, " + "\"ap_beacon\": %d, " + "\"ap_hidden\": \"%s\" " + " }", + apconf.ssid, + apconf.password, + apconf.authmode, + apconf.max_connection, + apconf.beacon_interval, + apconf.ssid_hidden ? "enabled" : "disabled" + ); + + jsonHeader(connData, 200); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} + //This cgi changes the operating mode: STA / AP / STA+AP int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { int len; char buff[1024]; - + int previous_mode = wifi_get_opmode(); if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); - if (len!=0) { - int m = atoi(buff); - NOTICE("switching to mode %d", m); - wifi_set_opmode(m&3); - if (m == 1) { - // STA-only mode, reset into STA+AP after a timeout if we don't get an IP address - os_timer_disarm(&resetTimer); - os_timer_setfn(&resetTimer, resetTimerCb, NULL); - os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); + int next_mode = atoi(buff); + + if (len!=0) { + if (next_mode == 2){ + // moving to AP mode, so disconnect before leave STA mode + wifi_station_disconnect(); + } + + DBG("Wifi switching to mode %d\n", next_mode); + wifi_set_opmode(next_mode&3); + + if (previous_mode == 2) { + // moving to STA or STA+AP mode from AP, try to connect and set timer + stconf.bssid_set = 0; + wifi_station_set_config(&stconf); + wifi_station_connect(); + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); + } + if(previous_mode == 1){ + // moving to AP or STA+AP from STA, so softap config call needed + wifi_softap_set_config(&apconf); + } + jsonHeader(connData, 200); + } else { + jsonHeader(connData, 400); } - jsonHeader(connData, 200); - } else { - jsonHeader(connData, 400); - } - return HTTPD_CGI_DONE; + return HTTPD_CGI_DONE; } static char *connStatuses[] = { "idle", "connecting", "wrong password", "AP not found", "failed", "got IP address" }; static char *wifiWarn[] = { 0, - "Switch to STA+AP mode", - "Can't scan in this mode! Switch to STA+AP mode", - "Switch to STA mode", + "Switch to STA+AP mode", + "Switch to STA+AP mode", + "Switch to STA mode", + "Switch to AP mode", +}; + +static char *apAuthMode[] = { "OPEN", + "WEP", + "WPA_PSK", + "WPA2_PSK", + "WPA_WPA2_PSK", }; #ifdef CHANGE_TO_STA @@ -547,43 +729,50 @@ static char *wifiWarn[] = { 0, // print various Wifi information into json buffer int ICACHE_FLASH_ATTR printWifiInfo(char *buff) { int len; + //struct station_config stconf; + wifi_station_get_config(&stconf); + //struct softap_config apconf; + wifi_softap_get_config(&apconf); + + uint8_t op = wifi_get_opmode() & 0x3; + char *mode = wifiMode[op]; + char *status = "unknown"; + int st = wifi_station_get_connect_status(); + if (st >= 0 && st < sizeof(connStatuses)) status = connStatuses[st]; + int p = wifi_get_phy_mode(); + char *phy = wifiPhy[p&3]; + char *warn = wifiWarn[op]; + if (op == 3) op = 4; // Done to let user switch to AP only mode from Soft-AP settings page, using only one set of warnings + char *apwarn = wifiWarn[op]; + char *apauth = apAuthMode[apconf.authmode]; + sint8 rssi = wifi_station_get_rssi(); + if (rssi > 0) rssi = 0; + uint8 mac_addr[6]; + uint8 apmac_addr[6]; + wifi_get_macaddr(0, mac_addr); + wifi_get_macaddr(1, apmac_addr); + uint8_t chan = wifi_get_channel(); + + len = os_sprintf(buff, + "\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", " + "\"rssi\": \"%ddB\", \"warn\": \"%s\", \"apwarn\": \"%s\",\"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":\"%d\", \"apssid\": \"%s\", " + "\"appass\": \"%s\", \"apchan\": \"%d\", \"apmaxc\": \"%d\", \"aphidd\": \"%s\", \"apbeac\": \"%d\", \"apauth\": \"%s\",\"apmac\":\"%02x:%02x:%02x:%02x:%02x:%02x\"", + mode, MODECHANGE, (char*)stconf.ssid, status, phy, rssi, warn, apwarn, + mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], chan, (char*)apconf.ssid,(char*)apconf.password,apconf.channel,apconf.max_connection,apconf.ssid_hidden?"enabled":"disabled",apconf.beacon_interval, apauth,apmac_addr[0], apmac_addr[1], apmac_addr[2], apmac_addr[3], apmac_addr[4], apmac_addr[5]); + + struct ip_info info; + if (wifi_get_ip_info(0, &info)) { + len += os_sprintf(buff+len, ", \"ip\": \"%d.%d.%d.%d\"", IP2STR(&info.ip.addr)); + len += os_sprintf(buff+len, ", \"netmask\": \"%d.%d.%d.%d\"", IP2STR(&info.netmask.addr)); + len += os_sprintf(buff+len, ", \"gateway\": \"%d.%d.%d.%d\"", IP2STR(&info.gw.addr)); + len += os_sprintf(buff+len, ", \"hostname\": \"%s\"", flashConfig.hostname); + } else { + len += os_sprintf(buff+len, ", \"ip\": \"-none-\""); + } + len += os_sprintf(buff+len, ", \"staticip\": \"%d.%d.%d.%d\"", IP2STR(&flashConfig.staticip)); + len += os_sprintf(buff+len, ", \"dhcp\": \"%s\"", flashConfig.staticip > 0 ? "off" : "on"); - struct station_config stconf; - wifi_station_get_config(&stconf); - - uint8_t op = wifi_get_opmode() & 0x3; - char *mode = wifiMode[op]; - char *status = "unknown"; - int st = wifi_station_get_connect_status(); - if (st >= 0 && st < sizeof(connStatuses)) status = connStatuses[st]; - int p = wifi_get_phy_mode(); - char *phy = wifiPhy[p&3]; - char *warn = wifiWarn[op]; - sint8 rssi = wifi_station_get_rssi(); - if (rssi > 0) rssi = 0; - uint8 mac_addr[6]; - wifi_get_macaddr(0, mac_addr); - uint8_t chan = wifi_get_channel(); - - len = os_sprintf(buff, - "\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", " - "\"rssi\": \"%ddB\", \"warn\": \"%s\", \"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":%d", - mode, MODECHANGE, (char*)stconf.ssid, status, phy, rssi, warn, - mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], chan); - - struct ip_info info; - if (wifi_get_ip_info(0, &info)) { - len += os_sprintf(buff+len, ", \"ip\": \"%d.%d.%d.%d\"", IP2STR(&info.ip.addr)); - len += os_sprintf(buff+len, ", \"netmask\": \"%d.%d.%d.%d\"", IP2STR(&info.netmask.addr)); - len += os_sprintf(buff+len, ", \"gateway\": \"%d.%d.%d.%d\"", IP2STR(&info.gw.addr)); - len += os_sprintf(buff+len, ", \"hostname\": \"%s\"", flashConfig.hostname); - } else { - len += os_sprintf(buff+len, ", \"ip\": \"-none-\""); - } - len += os_sprintf(buff+len, ", \"staticip\": \"%d.%d.%d.%d\"", IP2STR(&flashConfig.staticip)); - len += os_sprintf(buff+len, ", \"dhcp\": \"%s\"", flashConfig.staticip > 0 ? "off" : "on"); - - return len; + return len; } int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { @@ -636,24 +825,123 @@ int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) { return HTTPD_CGI_DONE; } -// Init the wireless, which consists of setting a timer if we expect to connect to an AP -// so we can revert to STA+AP mode if we can't connect. -void ICACHE_FLASH_ATTR wifiInit() { - // wifi_set_phy_mode(2); // limit to 802.11b/g 'cause n is flaky - int x = wifi_get_opmode() & 0x3; - x = x; - NOTICE("init, mode=%s", wifiMode[x]); - configWifiIP(); - - // The default sleep mode should be modem_sleep, but we set it here explicitly for good - // measure. We can't use light_sleep because that powers off everthing and we would loose - // all connections. - wifi_set_sleep_type(MODEM_SLEEP_T); - - wifi_set_event_handler_cb(wifiHandleEventCb); - // check on the wifi in a few seconds to see whether we need to switch mode - os_timer_disarm(&resetTimer); - os_timer_setfn(&resetTimer, resetTimerCb, NULL); - os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); +// Check string againt invalid characters +int ICACHE_FLASH_ATTR checkString(char *str){ + int i = 0; + for(; i < os_strlen(str); i++) + { + // Alphanumeric and underscore allowed + if (!(isalnum((unsigned char)str[i]) || str[i] == '_')) + { + DBG("Error: String has non alphanumeric chars\n"); + return 0; + } + } + return 1; } +/* Init the wireless + * + * Call both Soft-AP and Station default config + * Change values according to Makefile hard-coded variables + * Anyway set wifi opmode to STA+AP, it will change to STA if CHANGE_TO_STA is set to yes in Makefile + * Call a timer to check the STA connection + */ +void ICACHE_FLASH_ATTR wifiInit() { + + // Check te wifi opmode + int x = wifi_get_opmode() & 0x3; + + // Set opmode to 3 to let system scan aps, otherwise it won't scan + wifi_set_opmode(3); + + // Call both STATION and SOFTAP default config + wifi_station_get_config_default(&stconf); + wifi_softap_get_config_default(&apconf); + + DBG("Wifi init, mode=%s\n",wifiMode[x]); + + // STATION parameters +#if defined(STA_SSID) && defined(STA_PASS) + // Set parameters + if (os_strlen((char*)stconf.ssid) == 0 && os_strlen((char*)stconf.password) == 0) { + os_strncpy((char*)stconf.ssid, VERS_STR(STA_SSID), 32); + os_strncpy((char*)stconf.password, VERS_STR(STA_PASS), 64); + + DBG("Wifi pre-config trying to connect to AP %s pw %s\n",(char*)stconf.ssid, (char*)stconf.password); + + // wifi_set_phy_mode(2); // limit to 802.11b/g 'cause n is flaky + stconf.bssid_set = 0; + wifi_station_set_config(&stconf); + } +#endif + + // Change SOFT_AP settings if defined +#if defined(AP_SSID) + // Check if ssid and pass are alphanumeric values + int ssidlen = os_strlen(VERS_STR(AP_SSID)); + if(checkString(VERS_STR(AP_SSID)) && ssidlen > 7 && ssidlen < 32){ + // Clean memory and set the value of SSID + os_memset(apconf.ssid, 0, 32); + os_memcpy(apconf.ssid, VERS_STR(AP_SSID), os_strlen(VERS_STR(AP_SSID))); + // Specify the length of ssid + apconf.ssid_len= ssidlen; +#if defined(AP_PASS) + // If pass is at least 8 and less than 64 + int passlen = os_strlen(VERS_STR(AP_PASS)); + if( checkString(VERS_STR(AP_PASS)) && passlen > 7 && passlen < 64 ){ + // Clean memory and set the value of PASS + os_memset(apconf.password, 0, 64); + os_memcpy(apconf.password, VERS_STR(AP_PASS), passlen); + // Can't choose auth mode without a valid ssid and password +#ifdef AP_AUTH_MODE + // If set, use specified auth mode + if(AP_AUTH_MODE >= 0 && AP_AUTH_MODE <=4) + apconf.authmode = AP_AUTH_MODE; +#else + // If not, use WPA2 + apconf.authmode = AUTH_WPA_WPA2_PSK; +#endif + }else if ( passlen == 0){ + // If ssid is ok and no pass, set auth open + apconf.authmode = AUTH_OPEN; + // Remove stored password + os_memset(apconf.password, 0, 64); + } +#endif + }// end of ssid and pass check +#ifdef AP_SSID_HIDDEN + // If set, use specified ssid hidden parameter + if(AP_SSID_HIDDEN == 0 || AP_SSID_HIDDEN ==1) + apconf.ssid_hidden = AP_SSID_HIDDEN; +#endif +#ifdef AP_MAX_CONN + // If set, use specified max conn number + if(AP_MAX_CONN > 0 && AP_MAX_CONN <5) + apconf.max_connection = AP_MAX_CONN; +#endif +#ifdef AP_BEACON_INTERVAL + // If set use specified beacon interval + if(AP_BEACON_INTERVAL >= 100 && AP_BEACON_INTERVAL <= 60000) + apconf.beacon_interval = AP_BEACON_INTERVAL; +#endif + // Check softap config + bool softap_set_conf = wifi_softap_set_config(&apconf); + // Debug info + + DBG("Wifi Soft-AP parameters change: %s\n",softap_set_conf? "success":"fail"); +#endif // AP_SSID && AP_PASS + + configWifiIP(); + + // The default sleep mode should be modem_sleep, but we set it here explicitly for good + // measure. We can't use light_sleep because that powers off everthing and we would loose + // all connections. + wifi_set_sleep_type(MODEM_SLEEP_T); + + wifi_set_event_handler_cb(wifiHandleEventCb); + // check on the wifi in a few seconds to see whether we need to switch mode + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); +} diff --git a/esp-link/cgiwifi.h b/esp-link/cgiwifi.h index e2e98bc..667f27d 100644 --- a/esp-link/cgiwifi.h +++ b/esp-link/cgiwifi.h @@ -13,10 +13,13 @@ int cgiWiFiConnect(HttpdConnData *connData); int cgiWiFiSetMode(HttpdConnData *connData); int cgiWiFiConnStatus(HttpdConnData *connData); int cgiWiFiSpecial(HttpdConnData *connData); +int cgiApSettingsChange(HttpdConnData *connData); +int cgiApSettingsInfo(HttpdConnData *connData); void configWifiIP(); void wifiInit(void); void wifiAddStateChangeCb(WifiStateChangeCb cb); void wifiStartMDNS(struct ip_addr); +int checkString(char *str); extern uint8_t wifiState; extern bool mdns_started; diff --git a/esp-link/main.c b/esp-link/main.c index 6c855c1..dd14c58 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -72,6 +72,8 @@ HttpdBuiltInUrl builtInUrls[] = { { "/wifi/connstatus", cgiWiFiConnStatus, NULL }, { "/wifi/setmode", cgiWiFiSetMode, NULL }, { "/wifi/special", cgiWiFiSpecial, NULL }, + { "/wifi/apinfo", cgiApSettingsInfo, NULL }, + { "/wifi/apchange", cgiApSettingsChange, NULL }, { "/system/info", cgiSystemInfo, NULL }, { "/system/update", cgiSystemSet, NULL }, { "/services/info", cgiServicesInfo, NULL }, @@ -86,7 +88,6 @@ HttpdBuiltInUrl builtInUrls[] = { #ifdef SHOW_HEAP_USE static ETSTimer prHeapTimer; - static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) { os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size()); } @@ -110,47 +111,23 @@ void user_rf_pre_init(void) { // Main routine to initialize esp-link. void user_init(void) { // get the flash config so we know how to init things -// configWipe(); // uncomment to reset the config for testing purposes + //configWipe(); // uncomment to reset the config for testing purposes bool restoreOk = configRestore(); - // init gpio pin registers + // Init gpio pin registers gpio_init(); gpio_output_set(0, 0, 0, (1<<15)); // some people tie it to GND, gotta ensure it's disabled // init UART uart_init(flashConfig.baud_rate, 115200); logInit(); // must come after init of uart - // say hello (leave some time to cause break in TX after boot loader's msg + // Say hello (leave some time to cause break in TX after boot loader's msg os_delay_us(10000L); os_printf("\n\n** %s\n", esp_link_version); os_printf("Flash config restore %s\n", restoreOk ? "ok" : "*FAILED*"); - -#if defined(STA_SSID) && defined(STA_PASS) - int x = wifi_get_opmode() & 0x3; - if (x == 2) { - // we only force the STA settings when a full flash of the module has been made, which - // resets the wifi settings not to have anything configured - struct station_config stconf; - wifi_station_get_config(&stconf); - - if (os_strlen((char*)stconf.ssid) == 0 && os_strlen((char*)stconf.password) == 0) { - os_strncpy((char*)stconf.ssid, VERS_STR(STA_SSID), 32); - os_strncpy((char*)stconf.password, VERS_STR(STA_PASS), 64); -#ifdef CGIWIFI_DBG - os_printf("Wifi pre-config trying to connect to AP %s pw %s\n", - (char*)stconf.ssid, (char*)stconf.password); -#endif - wifi_set_opmode(3); // sta+ap, will switch to sta-only 15 secs after connecting - stconf.bssid_set = 0; - wifi_station_set_config(&stconf); - } - } -#endif - // Status LEDs statusInit(); serledInit(); // Wifi wifiInit(); - // init the flash filesystem with the html stuff espFsInit(&_binary_espfs_img_start); //EspFsInitResult res = espFsInit(&_binary_espfs_img_start); @@ -176,15 +153,13 @@ void user_init(void) { fid & 0xff, (fid&0xff00)|((fid>>16)&0xff)); NOTICE("** %s: ready, heap=%ld", esp_link_version, (unsigned long)system_get_free_heap_size()); + // Init SNTP service cgiServicesSNTPInit(); - #ifdef MQTT NOTICE("initializing MQTT"); mqtt_client_init(); #endif - NOTICE("initializing user application"); app_init(); - - NOTICE("waiting for work to do..."); + NOTICE("Waiting for work to do..."); } diff --git a/esp-link/mqtt_client.c b/esp-link/mqtt_client.c index 9b94576..3d390bb 100644 --- a/esp-link/mqtt_client.c +++ b/esp-link/mqtt_client.c @@ -18,34 +18,31 @@ static MqttCallback published_cb; static MqttDataCallback data_cb; void ICACHE_FLASH_ATTR -mqttConnectedCb(uint32_t *args) { +mqttConnectedCb(MQTT_Client* client) { DBG("MQTT Client: Connected\n"); - //MQTT_Client* client = (MQTT_Client*)args; //MQTT_Subscribe(client, "system/time", 0); // handy for testing if (connected_cb) - connected_cb(args); + connected_cb(client); } void ICACHE_FLASH_ATTR -mqttDisconnectedCb(uint32_t *args) { -// MQTT_Client* client = (MQTT_Client*)args; +mqttDisconnectedCb(MQTT_Client* client) { DBG("MQTT Client: Disconnected\n"); if (disconnected_cb) - disconnected_cb(args); + disconnected_cb(client); } void ICACHE_FLASH_ATTR -mqttPublishedCb(uint32_t *args) { -// MQTT_Client* client = (MQTT_Client*)args; +mqttPublishedCb(MQTT_Client* client) { DBG("MQTT Client: Published\n"); if (published_cb) - published_cb(args); + published_cb(client); } 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; - +mqttDataCb(MQTT_Client* client, const char* topic, uint32_t topic_len, + const char *data, uint32_t data_len) +{ #ifdef MQTTCLIENT_DBG char *topicBuf = (char*)os_zalloc(topic_len + 1); char *dataBuf = (char*)os_zalloc(data_len + 1); @@ -62,7 +59,7 @@ mqttDataCb(uint32_t *args, const char* topic, uint32_t topic_len, const char *da #endif if (data_cb) - data_cb(args, topic, topic_len, data, data_len); + data_cb(client, topic, topic_len, data, data_len); } void ICACHE_FLASH_ATTR diff --git a/esp-link/status.c b/esp-link/status.c index a49726f..6905850 100644 --- a/esp-link/status.c +++ b/esp-link/status.c @@ -37,7 +37,7 @@ static void ICACHE_FLASH_ATTR mqttStatusCb(void *v) { char buf[128]; mqttStatusMsg(buf); - MQTT_Publish(&mqttClient, flashConfig.mqtt_status_topic, buf, 1, 0); + MQTT_Publish(&mqttClient, flashConfig.mqtt_status_topic, buf, os_strlen(buf), 1, 0); } diff --git a/html/home.html b/html/home.html index 2413566..c977c74 100644 --- a/html/home.html +++ b/html/home.html @@ -22,8 +22,8 @@ Network SSID - WiFI status - WiFI address + WiFi status + WiFi address SLIP status MQTT status Serial baud @@ -32,7 +32,7 @@

Info

The JeeLabs esp-link firmware bridges the ESP8266 - serial port to WiFI and can + serial port to WiFi and can program microcontrollers over the serial port, in particular Arduinos, AVRs, and NXP's LPC800 and other ARM processors. Typical avrdude command line to program an Arduino:

@@ -79,7 +79,7 @@
- +
@@ -109,8 +109,8 @@

System details

- - + + -
+ + +

WiFi Association

@@ -70,7 +72,7 @@ - +