Merge branch 'master' of https://github.com/jeelabs/esp-link into TvEmaster

# By KatAst (29) and Thorsten von Eicken (8)
# Via Thorsten von Eicken
* 'master' of https://github.com/jeelabs/esp-link: (37 commits)
  fix reference to old boot file in Makefile
  convert readme to asciidoc
  fix mqtt for new slip protocol
  new SLIP protocol; basic REST working
  make new slip proto work
  fix RX pull-up; revamp avr flashing
  SDK 1.5.1; makefile and html tweaks
  change SLIP to std escape chars; add to readme
  Comments review
  From os_printf to DBG
  Fix indent
  Fix indent
  Fix indent
  Fix Indentation
  Fix indentation
  Back to default
  Back to default
  Modified comments to match actions
  Add default WPA2 when a password was specified
  Int to bool
  ...

Conflicts:
	Makefile
	cmd/cmd.c
	esp-link/cgiwifi.c
	esp-link/main.c
	mqtt/mqtt.c
	rest/rest.c
pull/95/head
susisstrolch 9 years ago
commit 647d68e495
  1. 72
      Makefile
  2. 252
      README.adoc
  3. 128
      cmd/cmd.c
  4. 79
      cmd/cmd.h
  5. 163
      cmd/handlers.c
  6. 3
      esp-link/cgi.c
  7. 120
      esp-link/cgioptiboot.c
  8. 328
      esp-link/cgiwifi.c
  9. 3
      esp-link/cgiwifi.h
  10. 37
      esp-link/main.c
  11. 23
      esp-link/mqtt_client.c
  12. 2
      esp-link/status.c
  13. 14
      html/home.html
  14. 11
      html/mqtt.html
  15. 38
      html/services.html
  16. 4
      html/style.css
  17. 124
      html/wifi/wifiAp.html
  18. 98
      html/wifi/wifiAp.js
  19. 8
      html/wifi/wifiSta.html
  20. 0
      html/wifi/wifiSta.js
  21. 2
      include/user_config.h
  22. 25
      mqtt/mqtt.c
  23. 12
      mqtt/mqtt.h
  24. 238
      mqtt/mqtt_cmd.c
  25. 12
      mqtt/mqtt_cmd.h
  26. 141
      rest/rest.c
  27. 29
      rest/rest.h
  28. 10
      serial/serbridge.c
  29. 70
      serial/slip.c

@ -25,6 +25,32 @@ CFLAGS=
# STA_SSID ?= # 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 --------------- # --------------- toolchain configuration ---------------
# Base directory for the compiler. Needs a / at the end. # Base directory for the compiler. Needs a / at the end.
@ -43,15 +69,6 @@ ESPTOOL ?= $(abspath ../esp-open-sdk/esptool/esptool.py)
ESPPORT ?= /dev/ttyUSB0 ESPPORT ?= /dev/ttyUSB0
ESPBAUD ?= 460800 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 --------------- # --------------- chipset configuration ---------------
# Pick your flash size: "512KB", "1MB", or "4MB" # 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 # GPIO pin used for "serial activity" LED, active low
LED_SERIAL_PIN ?= 14 LED_SERIAL_PIN ?= 14
# --------------- esp-link config options --------------- # --------------- esp-link modules 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.
CHANGE_TO_STA ?= yes # Optional Modules mqtt
# Optional Modules
MODULES ?= mqtt rest syslog MODULES ?= mqtt rest syslog
# --------------- esphttpd config options --------------- # --------------- esphttpd config options ---------------
@ -270,6 +282,30 @@ ifneq ($(strip $(STA_PASS)),)
CFLAGS += -DSTA_PASS="$(STA_PASS)" CFLAGS += -DSTA_PASS="$(STA_PASS)"
endif 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") ifeq ("$(GZIP_COMPRESSION)","yes")
CFLAGS += -DGZIP_COMPRESSION CFLAGS += -DGZIP_COMPRESSION
endif endif
@ -352,12 +388,14 @@ flash: all
0x00000 "$(BOOTFILE)" 0x01000 $(FW_BASE)/user1.bin \ 0x00000 "$(BOOTFILE)" 0x01000 $(FW_BASE)/user1.bin \
$(ET_BLANK) $(SDK_BASE)/bin/blank.bin $(ET_BLANK) $(SDK_BASE)/bin/blank.bin
ifeq ($(OS),Windows_NT)
tools/$(HTML_COMPRESSOR): tools/$(HTML_COMPRESSOR):
$(Q) mkdir -p tools $(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://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) 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://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR)
cd tools; wget https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) cd tools; wget https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR)
endif endif

@ -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. This firmware connects an attached micro-controller to the internet using a ESP8266 Wifi module.
It implements a number of features: It implements a number of features:
[options="compact"]
- transparent bridge between Wifi and serial, useful for debugging or inputting into a uC - 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 - flash-programming attached Arduino/AVR microcontrollers, esp8266 modules, as well as
LPC800-series and other ARM microcontrollers via Wifi 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 - built-in stk500v1 programmer for AVR uC's: 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
- outbound REST HTTP requests from the attached micro-controller to the internet, protocol - MQTT client pub/sub from the attached micro-controller to the internet
based on espduino and compatible with [tuanpmt/espduino](https://github.com/tuanpmt/espduino)
The firmware includes a tiny HTTP server based on 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! 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 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 functionality. Thank you also to https://github.com/susisstrolch and https://github.com/bc547 for
additional contributions! 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). 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... 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: The goal of the esp-link project is to create an advanced Wifi co-processor. Esp-link assumes that
[![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) 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 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 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. 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 know which exact sensors/actuators the attached uC has, it learns that through the initial
callback registration. callback registration.
Eye Candy ### Eye Candy
---------
These screen shots show the Home page, the Wifi configuration page, the console for the These screen shots show the Home page, the Wifi configuration page, the console for the
attached microcontroller, and the pin assignments card: attached microcontroller, and the pin assignments card:
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/8261425/6ca395a6-167f-11e5-8e92-77150371135a.png"> image:https://cloud.githubusercontent.com/assets/39480/8261425/6ca395a6-167f-11e5-8e92-77150371135a.png[width="45%"]
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/8261427/6caf7326-167f-11e5-8085-bc8b20159b2b.png"> image:https://cloud.githubusercontent.com/assets/39480/8261427/6caf7326-167f-11e5-8085-bc8b20159b2b.png[width="45%"]
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/8261426/6ca7f75e-167f-11e5-827d-9a1c582ad05d.png"> image:https://cloud.githubusercontent.com/assets/39480/8261426/6ca7f75e-167f-11e5-827d-9a1c582ad05d.png[width="45%"]
<img width="30%" src="https://cloud.githubusercontent.com/assets/39480/8261658/11e6c64a-1681-11e5-82d0-ea5ec90a6ddb.png"> 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. This firmware is designed for any esp8266 module.
The recommended connections for an esp-01 module are: The recommended connections for an esp-01 module are:
- URXD: connect to TX of microcontroller - URXD: connect to TX of microcontroller
- UTXD: connect to RX of microcontroller - UTXD: connect to RX of microcontroller
- GPIO0: connect to RESET of microcontroller - GPIO0: connect to RESET of microcontroller
- GPIO2: optionally connect green LED to 3.3V (indicates wifi status) - GPIO2: optionally connect green LED to 3.3V (indicates wifi status)
The recommended connections for an esp-12 module are: The recommended connections for an esp-12 module are:
- URXD: connect to TX of microcontroller - URXD: connect to TX of microcontroller
- UTXD: connect to RX of microcontroller - UTXD: connect to RX of microcontroller
- GPIO12: connect to RESET 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 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 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: in the esp-link web interface:
- GPIO13: connect to TX of microcontroller - GPIO13: connect to TX of microcontroller
- GPIO15: connect to RX of microcontroller - GPIO15: connect to RX of microcontroller
- GPIO1/UTXD: connect to RESET 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. 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 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. ESP8266 flashing tool to flash the bootloader, the firmware, and blank settings.
Detailed instructions are provided in the release notes. 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. 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. 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 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. 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 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: 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 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`) 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 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") 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 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 ("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 you reconnect your laptop/phone to your normal network and access esp-link via its hostname
or IP address or IP address
LED indicators ### LED indicators
--------------
Assuming appropriate hardware attached to GPIO pins, the green "conn" LED will show the wifi Assuming appropriate hardware attached to GPIO pins, the green "conn" LED will show the wifi
status as follows: status as follows:
- Very short flash once a second: not connected to a network and running as AP+STA, i.e. - 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 trying to connect to the configured network
- Very short flash once every two seconds: not connected to a network and running as AP-only - 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. 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 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. 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 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 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. 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 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". name, i.e., something like "test-module-1" and not "test-module-1.mydomain.com".
This has a number of effects: 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 - 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 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 - 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 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. 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 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 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 http://bbs.espressif.com/viewforum.php?f=5[download forum] and also expand it into a
sub-directory. Then clone the esp-link repository into a third sub-directory. 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. 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 If you choose a different directory structure look at the Makefile for the appropriate environment
variables to define. 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 In order to OTA-update the esp8266 you should `export ESP_HOSTNAME=...` with the hostname or
IP address of your module. 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): A few notes from others (I can't fully verify these):
- You may need to install `zlib1g-dev` and `python-serial` - You may need to install `zlib1g-dev` and `python-serial`
- Make sure you have the correct version of the esp_iot_sdk - 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 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 - 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: 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 - 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, - 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) (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. 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 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` flashing again after the initial one! The recommended way to flash is to use `make wiflash`
if you are also building the firmware. 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 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, 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`. 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 ### Flashing an attached AVR/Arduino
There are three options for reprogramming an attached AVR/Arduino microcontroller: 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 - 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. 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 - 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. 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: If you are having trouble with the built-in programmer and see something like this:
```
--------------------
# ./avrflash 192.168.3.104 blink.hex # ./avrflash 192.168.3.104 blink.hex
Error checking sync: FAILED to SYNC: abandoned after timeout, got: 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 :\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 the most likely cause is a baud rate mismatch and/or a bad connection from the esp8266 to the
AVRs reset line. AVRs reset line.
The baud rate used by esp-link is set on the uC Console web page and, as mentioned above, it will 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. baud rate may be incorrect.
The output of a successful flash using the built-in programmer looks like this: 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 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 This says that the sketch comprises 3098 bytes of flash, was written in 0.8 seconds
(excludes the initial sync time) at 57600 baud, (excludes the initial sync time) at 57600 baud,
and the 3098 bytes were flashed at a rate of 3674 bytes per second. 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) length characters)
and there is dead time waiting for an ack or preparing the next record to be sent. 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 ### Flashing an attached ARM processor
You can reprogram NXP's LPC800-series and many other ARM processors as well by pointing your 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 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 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 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 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 gpio0 pin as possible (basically a low pass filter); and/or pass
the cable connecting the two esp8266's through a ferrite bead. 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 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 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 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): 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 - 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. associates with an AP. It re-enables itself if the association is lost.
- off: the UART log is always off - 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 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 HTTP REST requests as well as an MQTT client. The SLIP protocol consists of commands with
binary arguments sent from the binary arguments sent from the
attached microcontroller to the esp8266, which then performs the command and responds back. 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 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. 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 You can find REST and MQTT libraries as well as demo sketches in the
https://github.com/tve/espduino in the https://github.com/jeelabs/el-client[el-client] repository.
[examples/demo folder](https://github.com/tve/espduino/tree/master/espduino/examples/demo).
More docs forthcoming...
Contact Contact
------- -------
If you find problems with esp-link, please create a github issue. If you have a question, please 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. use the gitter chat link at the top of this page.

@ -8,14 +8,7 @@
#include "uart.h" #include "uart.h"
#ifdef CMD_DBG #ifdef CMD_DBG
#define DBG(format, ...) os_printf(format, ## __VA_ARGS__) #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
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",
};
#else #else
#define DBG(format, ...) do { } while(0) #define DBG(format, ...) do { } while(0)
#endif #endif
@ -25,13 +18,15 @@ extern const CmdList commands[];
//===== ESP -> Serial responses //===== ESP -> Serial responses
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
CMD_ProtoWrite(uint8_t data) { cmdProtoWrite(uint8_t data) {
switch(data){ switch(data){
case SLIP_START:
case SLIP_END: case SLIP_END:
case SLIP_REPL: uart0_write_char(SLIP_ESC);
uart0_write_char(SLIP_REPL); uart0_write_char(SLIP_ESC_END);
uart0_write_char(SLIP_ESC(data)); break;
case SLIP_ESC:
uart0_write_char(SLIP_ESC);
uart0_write_char(SLIP_ESC_ESC);
break; break;
default: default:
uart0_write_char(data); uart0_write_char(data);
@ -39,98 +34,83 @@ CMD_ProtoWrite(uint8_t data) {
} }
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
CMD_ProtoWriteBuf(uint8_t *data, short len) { cmdProtoWriteBuf(const uint8_t *data, short len) {
while (len--) CMD_ProtoWrite(*data++); while (len--) cmdProtoWrite(*data++);
} }
static uint16_t resp_crc;
// Start a response, returns the partial CRC // Start a response, returns the partial CRC
uint16_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc) { cmdResponseStart(uint16_t cmd, uint32_t value, uint16_t argc) {
uint16_t crc = 0; DBG("cmdResponse: cmd=%d val=%ld argc=%d\n", cmd, value, argc);
uart0_write_char(SLIP_START); uart0_write_char(SLIP_END);
CMD_ProtoWriteBuf((uint8_t*)&cmd, 2); cmdProtoWriteBuf((uint8_t*)&cmd, 2);
crc = crc16_data((uint8_t*)&cmd, 2, crc); resp_crc = crc16_data((uint8_t*)&cmd, 2, 0);
CMD_ProtoWriteBuf((uint8_t*)&callback, 4); cmdProtoWriteBuf((uint8_t*)&argc, 2);
crc = crc16_data((uint8_t*)&callback, 4, crc); resp_crc = crc16_data((uint8_t*)&argc, 2, resp_crc);
CMD_ProtoWriteBuf((uint8_t*)&_return, 4); cmdProtoWriteBuf((uint8_t*)&value, 4);
crc = crc16_data((uint8_t*)&_return, 4, crc); resp_crc = crc16_data((uint8_t*)&value, 4, resp_crc);
CMD_ProtoWriteBuf((uint8_t*)&argc, 2);
crc = crc16_data((uint8_t*)&argc, 2, crc);
return crc;
} }
// Adds data to a response, returns the partial CRC // Adds data to a response, returns the partial CRC
uint16_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len) { cmdResponseBody(const void *data, uint16_t len) {
short pad_len = len+3 - (len+3)%4; // round up to multiple of 4 cmdProtoWriteBuf((uint8_t*)&len, 2);
CMD_ProtoWriteBuf((uint8_t*)&pad_len, 2); resp_crc = crc16_data((uint8_t*)&len, 2, resp_crc);
crc_in = crc16_data((uint8_t*)&pad_len, 2, crc_in);
CMD_ProtoWriteBuf(data, len); cmdProtoWriteBuf(data, len);
crc_in = crc16_data(data, len, crc_in); 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; uint32_t temp = 0;
CMD_ProtoWriteBuf((uint8_t*)&temp, pad_len-len); cmdProtoWriteBuf((uint8_t*)&temp, pad);
crc_in = crc16_data((uint8_t*)&temp, pad_len-len, crc_in); resp_crc = crc16_data((uint8_t*)&temp, pad, resp_crc);
} }
return crc_in;
} }
// Ends a response // Ends a response
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
CMD_ResponseEnd(uint16_t crc) { cmdResponseEnd() {
CMD_ProtoWriteBuf((uint8_t*)&crc, 2); cmdProtoWriteBuf((uint8_t*)&resp_crc, 2);
uart0_write_char(SLIP_END); uart0_write_char(SLIP_END);
} }
//===== serial -> ESP commands //===== serial -> ESP commands
// Execute a parsed command // Execute a parsed command
static uint32_t ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
CMD_Exec(const CmdList *scp, CmdPacket *packet) { cmdExec(const CmdList *scp, CmdPacket *packet) {
uint16_t crc = 0;
// Iterate through the command table and call the appropriate function // Iterate through the command table and call the appropriate function
while (scp->sc_function != NULL) { while (scp->sc_function != NULL) {
if(scp->sc_name == packet->cmd) { 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 // call command function
uint32_t ret = scp->sc_function(packet); scp->sc_function(packet);
// if requestor asked for a response, send it return;
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++; scp++;
} }
DBG("CMD_Exec: cmd=%d not found\n", packet->cmd); DBG("cmdExec: cmd=%d not found\n", packet->cmd);
return 0;
} }
// Parse a packet and print info about it // Parse a packet and print info about it
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
CMD_parse_packet(uint8_t *buf, short len) { cmdParsePacket(uint8_t *buf, short len) {
// minimum command length // minimum command length
if (len < 12) return; if (len < sizeof(CmdPacket)) return;
// init pointers into buffer // init pointers into buffer
CmdPacket *packet = (CmdPacket*)buf; CmdPacket *packet = (CmdPacket*)buf;
uint8_t *data_ptr = (uint8_t*)&packet->args; uint8_t *data_ptr = (uint8_t*)&packet->args;
uint8_t *data_limit = data_ptr+len; 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, packet->cmd,
cmd_names[packet->cmd],
packet->argc, packet->argc,
(void *)packet->callback, packet->value
packet->_return
); );
#if 0 #if 0
@ -139,7 +119,7 @@ CMD_parse_packet(uint8_t *buf, short len) {
uint16_t argc = packet->argc; uint16_t argc = packet->argc;
while (data_ptr+2 < data_limit && argc--) { while (data_ptr+2 < data_limit && argc--) {
short l = *(uint16_t*)data_ptr; 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; data_ptr += 2;
while (data_ptr < data_limit && l--) { while (data_ptr < data_limit && l--) {
os_printf(" %02X", *data_ptr++); os_printf(" %02X", *data_ptr++);
@ -149,9 +129,9 @@ CMD_parse_packet(uint8_t *buf, short len) {
#endif #endif
if (data_ptr <= data_limit) { if (data_ptr <= data_limit) {
CMD_Exec(commands, packet); cmdExec(commands, packet);
} else { } 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 // Fill out a CmdRequest struct given a CmdPacket
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
CMD_Request(CmdRequest *req, CmdPacket* cmd) { cmdRequest(CmdRequest *req, CmdPacket* cmd) {
req->cmd = cmd; req->cmd = cmd;
req->arg_num = 0; req->arg_num = 0;
req->arg_ptr = (uint8_t*)&cmd->args; 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 // Return the number of arguments given a command struct
uint32_t ICACHE_FLASH_ATTR uint32_t ICACHE_FLASH_ATTR
CMD_GetArgc(CmdRequest *req) { cmdGetArgc(CmdRequest *req) {
return req->cmd->argc; return req->cmd->argc;
} }
// Copy the next argument from a command structure into the data pointer, returns 0 on success // Copy the next argument from a command structure into the data pointer, returns 0 on success
// -1 on error // -1 on error
int32_t ICACHE_FLASH_ATTR 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; uint16_t length;
if (req->arg_num >= req->cmd->argc) if (req->arg_num >= req->cmd->argc)
@ -185,7 +165,7 @@ CMD_PopArg(CmdRequest *req, void *data, uint16_t len) {
req->arg_ptr += 2; req->arg_ptr += 2;
os_memcpy(data, req->arg_ptr, length); 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 ++; req->arg_num ++;
return 0; return 0;
@ -193,7 +173,7 @@ CMD_PopArg(CmdRequest *req, void *data, uint16_t len) {
// Skip the next argument // Skip the next argument
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
CMD_SkipArg(CmdRequest *req) { cmdSkipArg(CmdRequest *req) {
uint16_t length; uint16_t length;
if (req->arg_num >= req->cmd->argc) return; if (req->arg_num >= req->cmd->argc) return;
@ -201,12 +181,12 @@ CMD_SkipArg(CmdRequest *req) {
length = *(uint16_t*)req->arg_ptr; length = *(uint16_t*)req->arg_ptr;
req->arg_ptr += 2; req->arg_ptr += 2;
req->arg_ptr += length; req->arg_ptr += (length+3)&~3;
req->arg_num ++; req->arg_num ++;
} }
// Return the length of the next argument // Return the length of the next argument
uint16_t ICACHE_FLASH_ATTR uint16_t ICACHE_FLASH_ATTR
CMD_ArgLen(CmdRequest *req) { cmdArgLen(CmdRequest *req) {
return *(uint16_t*)req->arg_ptr; return *(uint16_t*)req->arg_ptr;
} }

@ -6,19 +6,11 @@
#define CMD_H #define CMD_H
#include <esp8266.h> #include <esp8266.h>
// Escape chars used by tuanpmt, dunno why he didn't use std ones... // Standard SLIP escape chars from RFC
#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
#define SLIP_END 0300 // indicates end of packet #define SLIP_END 0300 // indicates end of packet
#define SLIP_ESC 0333 // indicates byte stuffing #define SLIP_ESC 0333 // indicates byte stuffing
#define SLIP_ESC_END 0334 // ESC ESC_END means END data byte #define SLIP_ESC_END 0334 // ESC ESC_END means END data byte
#define SLIP_ESC_ESC 0335 // ESC ESC_ESC means ESC data byte #define SLIP_ESC_ESC 0335 // ESC ESC_ESC means ESC data byte
#endif
typedef struct __attribute__((__packed__)) { typedef struct __attribute__((__packed__)) {
uint16_t len; // length of data uint16_t len; // length of data
@ -27,9 +19,8 @@ typedef struct __attribute__((__packed__)) {
typedef struct __attribute__((__packed__)) { typedef struct __attribute__((__packed__)) {
uint16_t cmd; // command to perform, from CmdName enum 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 uint16_t argc; // number of arguments to command
uint32_t value; // callback pointer for response or first argument
CmdArg args[0]; // really args[argc] CmdArg args[0]; // really args[argc]
} CmdPacket; } CmdPacket;
@ -41,66 +32,70 @@ typedef struct {
typedef enum { typedef enum {
CMD_NULL = 0, CMD_NULL = 0,
CMD_RESET, // reset esp (not honored in this implementation) CMD_SYNC, // synchronize and clear
CMD_IS_READY, // health-check CMD_RESP_V, // response with a value
CMD_WIFI_CONNECT, // (3) connect to AP (not honored in this implementation) CMD_RESP_CB, // response with a callback
CMD_MQTT_SETUP, CMD_WIFI_STATUS, // get the current wifi status
CMD_MQTT_CONNECT, CMD_CB_ADD,
CMD_MQTT_DISCONNECT, CMD_CB_EVENTS,
CMD_MQTT_PUBLISH, CMD_GET_TIME, // get current time in seconds since the unix epoch
CMD_MQTT_SUBSCRIBE,
CMD_MQTT_LWT, CMD_MQTT_SETUP = 10, // set-up callbacks
CMD_MQTT_EVENTS, CMD_MQTT_PUBLISH, // publish a message
CMD_REST_SETUP, // (11) 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_REQUEST,
CMD_REST_SETHEADER, CMD_REST_SETHEADER,
CMD_REST_EVENTS,
CMD_CB_ADD, // 15
CMD_CB_EVENTS
} CmdName; } CmdName;
typedef uint32_t (*cmdfunc_t)(CmdPacket *cmd); typedef void (*cmdfunc_t)(CmdPacket *cmd);
typedef struct { typedef struct {
CmdName sc_name; CmdName sc_name; // name as CmdName enum
cmdfunc_t sc_function; char *sc_text; // name as string
cmdfunc_t sc_function; // pointer to function
} CmdList; } CmdList;
#define CMD_CBNLEN 16 #define CMD_CBNLEN 16
typedef struct { typedef struct {
char name[CMD_CBNLEN]; char name[CMD_CBNLEN];
uint32_t callback; uint32_t callback;
} cmdCallback; } CmdCallback;
// Used by slip protocol to cause parsing of a received packet // 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 // 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 // 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 // Responses
// Start a response, returns the partial CRC // Start a response
uint16_t CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc); void cmdResponseStart(uint16_t cmd, uint32_t value, uint16_t argc);
// Adds data to a response, returns the partial CRC // Adds data to a response
uint16_t CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len); void cmdResponseBody(const void* data, uint16_t len);
// Ends a response // 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 // Requests
// Fill out a CmdRequest struct given a CmdPacket // 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 // 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 // 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 // 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 // Skip next arg
void CMD_SkipArg(CmdRequest *req); void cmdSkipArg(CmdRequest *req);
#endif #endif

@ -18,11 +18,10 @@
#define DBG(format, ...) do { } while(0) #define DBG(format, ...) do { } while(0)
#endif #endif
static uint32_t CMD_Null(CmdPacket *cmd); static void cmdNull(CmdPacket *cmd);
static uint32_t CMD_IsReady(CmdPacket *cmd); static void cmdSync(CmdPacket *cmd);
static uint32_t CMD_Reset(CmdPacket *cmd); static void cmdWifiStatus(CmdPacket *cmd);
static uint32_t CMD_WifiConnect(CmdPacket *cmd); static void cmdAddCallback(CmdPacket *cmd);
static uint32_t CMD_AddCallback(CmdPacket *cmd);
// keep track of last status sent to uC so we can notify it when it changes // keep track of last status sent to uC so we can notify it when it changes
static uint8_t lastWifiStatus = wifiIsDisconnected; static uint8_t lastWifiStatus = wifiIsDisconnected;
@ -30,137 +29,143 @@ static bool wifiCbAdded = false;
// Command dispatch table for serial -> ESP commands // Command dispatch table for serial -> ESP commands
const CmdList commands[] = { const CmdList commands[] = {
{CMD_NULL, CMD_Null}, {CMD_NULL, "NULL", cmdNull}, // no-op
{CMD_RESET, CMD_Reset}, {CMD_SYNC, "SYNC", cmdSync}, // synchronize
{CMD_IS_READY, CMD_IsReady}, {CMD_WIFI_STATUS, "WIFI_STATUS", cmdWifiStatus},
{CMD_WIFI_CONNECT, CMD_WifiConnect}, {CMD_CB_ADD, "ADD_CB", cmdAddCallback},
#ifdef MQTT #ifdef MQTT
{CMD_MQTT_SETUP, MQTTCMD_Setup}, {CMD_MQTT_SETUP, "MQTT_SETUP", MQTTCMD_Setup},
{CMD_MQTT_CONNECT, MQTTCMD_Connect}, {CMD_MQTT_PUBLISH, "MQTT_PUB", MQTTCMD_Publish},
{CMD_MQTT_DISCONNECT, MQTTCMD_Disconnect}, {CMD_MQTT_SUBSCRIBE , "MQTT_SUB", MQTTCMD_Subscribe},
{CMD_MQTT_PUBLISH, MQTTCMD_Publish}, {CMD_MQTT_LWT, "MQTT_LWT", MQTTCMD_Lwt},
{CMD_MQTT_SUBSCRIBE , MQTTCMD_Subscribe},
{CMD_MQTT_LWT, MQTTCMD_Lwt},
#endif #endif
#ifdef REST #ifdef REST
{CMD_REST_SETUP, REST_Setup}, {CMD_REST_SETUP, "REST_SETUP", REST_Setup},
{CMD_REST_REQUEST, REST_Request}, {CMD_REST_REQUEST, "REST_REQ", REST_Request},
{CMD_REST_SETHEADER, REST_SetHeader}, {CMD_REST_SETHEADER, "REST_SETHDR", REST_SetHeader},
#endif #endif
{CMD_CB_ADD, CMD_AddCallback},
{CMD_NULL, NULL}
}; };
//===== List of registered callbacks (to uC)
// WifiCb plus 10 for sensors // WifiCb plus 10 for sensors
#define MAX_CALLBACKS 12 #define MAX_CALLBACKS 12
cmdCallback callbacks[MAX_CALLBACKS]; // cleared in CMD_Reset CmdCallback callbacks[MAX_CALLBACKS]; // cleared in cmdSync
// Command handler for IsReady (healthcheck) command uint32_t ICACHE_FLASH_ATTR
static uint32_t ICACHE_FLASH_ATTR cmdAddCb(char* name, uint32_t cb) {
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;
}
static uint32_t ICACHE_FLASH_ATTR
CMD_AddCb(char* name, uint32_t cb) {
for (uint8_t i = 0; i < MAX_CALLBACKS; i++) { 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); // (void *)callbacks[i].callback);
// find existing callback or add to the end // find existing callback or add to the end
if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0 || callbacks[i].name[0] == '\0') { 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)); os_strncpy(callbacks[i].name, name, sizeof(callbacks[i].name));
callbacks[i].name[CMD_CBNLEN-1] = 0; // strncpy doesn't null terminate callbacks[i].name[CMD_CBNLEN-1] = 0; // strncpy doesn't null terminate
callbacks[i].callback = cb; 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 1;
} }
} }
return 0; return 0;
} }
cmdCallback* ICACHE_FLASH_ATTR CmdCallback* ICACHE_FLASH_ATTR
CMD_GetCbByName(char* name) { cmdGetCbByName(char* name) {
for (uint8_t i = 0; i < MAX_CALLBACKS; i++) { 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); // (void *)callbacks[i].callback);
// if callback doesn't exist or it's null // if callback doesn't exist or it's null
if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0) { 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]; return &callbacks[i];
} }
} }
os_printf("CMD_GetCbByName: cb %s not found\n", name); os_printf("cmdGetCbByName: cb %s not found\n", name);
return 0; return 0;
} }
//===== Wifi callback
// Callback from wifi subsystem to notify us of status changes // Callback from wifi subsystem to notify us of status changes
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
CMD_WifiCb(uint8_t wifiStatus) { cmdWifiCb(uint8_t wifiStatus) {
if (wifiStatus != lastWifiStatus){ if (wifiStatus != lastWifiStatus){
DBG("CMD_WifiCb: wifiStatus=%d\n", wifiStatus); DBG("cmdWifiCb: wifiStatus=%d\n", wifiStatus);
lastWifiStatus = wifiStatus; lastWifiStatus = wifiStatus;
cmdCallback *wifiCb = CMD_GetCbByName("wifiCb"); CmdCallback *wifiCb = cmdGetCbByName("wifiCb");
if ((uint32_t)wifiCb->callback != -1) { if ((uint32_t)wifiCb->callback != -1) {
uint8_t status = wifiStatus == wifiGotIP ? 5 : 1; uint8_t status = wifiStatus == wifiGotIP ? 5 : 1;
uint16_t crc = CMD_ResponseStart(CMD_WIFI_CONNECT, (uint32_t)wifiCb->callback, 0, 1); cmdResponseStart(CMD_RESP_CB, (uint32_t)wifiCb->callback, 1);
crc = CMD_ResponseBody(crc, (uint8_t*)&status, 1); cmdResponseBody((uint8_t*)&status, 1);
CMD_ResponseEnd(crc); cmdResponseEnd();
}
} }
} }
//===== Command handlers
// Command handler for Null command
static void ICACHE_FLASH_ATTR
cmdNull(CmdPacket *cmd) {
} }
// Command handler for Wifi connect command // Command handler for sync command
static uint32_t ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
CMD_WifiConnect(CmdPacket *cmd) { cmdSync(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
if(cmd->argc != 2 || cmd->callback == 0) if(cmd->argc != 0 || cmd->value == 0) {
return 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) { if (!wifiCbAdded) {
wifiAddStateChangeCb(CMD_WifiCb); // register our callback with wifi subsystem wifiAddStateChangeCb(cmdWifiCb);
wifiCbAdded = true; 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 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 // 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 static void ICACHE_FLASH_ATTR
CMD_AddCallback(CmdPacket *cmd) { cmdAddCallback(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
if (cmd->argc != 1 || cmd->callback == 0) if (cmd->argc != 1 || cmd->value == 0) return;
return 0;
char name[16]; char name[16];
uint16_t len; uint16_t len;
// get the sensor name // get the callback name
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 15) return 0; // max size of name is 15 characters if (len > 15) return; // max size of name is 15 characters
if (CMD_PopArg(&req, (uint8_t *)name, len)) return 0; if (cmdPopArg(&req, (uint8_t *)name, len)) return;
name[len] = 0; 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
} }

@ -206,7 +206,8 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) {
"{ " "{ "
"\"menu\": [ " "\"menu\": [ "
"\"Home\", \"/home.html\", " "\"Home\", \"/home.html\", "
"\"WiFI\", \"/wifi/wifi.html\", " "\"WiFi Station\", \"/wifi/wifiSta.html\", "
"\"WiFi Soft-AP\", \"/wifi/wifiAp.html\", "
"\"&#xb5;C Console\", \"/console.html\", " "\"&#xb5;C Console\", \"/console.html\", "
"\"Services\", \"/services.html\", " "\"Services\", \"/services.html\", "
#ifdef MQTT #ifdef MQTT

@ -10,11 +10,11 @@
#include "serbridge.h" #include "serbridge.h"
#include "serled.h" #include "serled.h"
#define SYNC_TIMEOUT 4800 // to achieve sync, in milliseconds #define INIT_DELAY 150 // wait this many millisecs before sending anything
#define SYNC_INTERVAL 77 // interval at which we try to sync
#define BAUD_INTERVAL 600 // interval after which we change baud rate #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 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 #ifdef OPTIBOOT_DBG
#define DBG(format, ...) os_printf(format, ## __VA_ARGS__) #define DBG(format, ...) os_printf(format, ## __VA_ARGS__)
@ -29,15 +29,14 @@
static ETSTimer optibootTimer; static ETSTimer optibootTimer;
static enum { // overall programming states static enum { // overall programming states
stateSync = 0, // trying to get initial response stateInit = 0, // initial delay
stateSync2, // trying to get in sync stateSync, // waiting to hear back
stateSync3, // trying to get second sync
stateGetSig, // reading device signature stateGetSig, // reading device signature
stateGetVersLo, // reading optiboot version, low bits stateGetVersLo, // reading optiboot version, low bits
stateGetVersHi, // reading optiboot version, high bits stateGetVersHi, // reading optiboot version, high bits
stateProg, // programming... stateProg, // programming...
} progState; } 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 baudCnt; // counter for sync attempts at different baud rates
static short ackWait; // counter of expected ACKs static short ackWait; // counter of expected ACKs
static uint16_t optibootVers; static uint16_t optibootVers;
@ -70,12 +69,11 @@ static void optibootTimerCB(void *);
static void optibootUartRecv(char *buffer, short length); static void optibootUartRecv(char *buffer, short length);
static bool processRecord(char *buf, short len); static bool processRecord(char *buf, short len);
static bool programPage(void); static bool programPage(void);
static void armTimer(void); static void armTimer(uint32_t ms);
static void initBaud(void); static void initBaud(void);
static void ICACHE_FLASH_ATTR optibootInit() { static void ICACHE_FLASH_ATTR optibootInit() {
progState = stateSync; progState = stateInit;
syncCnt = 0;
baudCnt = 0; baudCnt = 0;
uart0_baud(flashConfig.baud_rate); uart0_baud(flashConfig.baud_rate);
ackWait = 0; ackWait = 0;
@ -142,7 +140,7 @@ int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) {
// start sync timer // start sync timer
os_timer_disarm(&optibootTimer); os_timer_disarm(&optibootTimer);
os_timer_setfn(&optibootTimer, optibootTimerCB, NULL); 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 // respond with optimistic OK
noCacheHeaders(connData, 204); noCacheHeaders(connData, 204);
@ -155,7 +153,7 @@ int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) {
if (!errMessage[0] && progState >= stateProg) { if (!errMessage[0] && progState >= stateProg) {
char buf[64]; char buf[64];
DBG("OB got sync\n"); 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); baudRate, optibootVers>>8, optibootVers&0xff);
httpdSend(connData, buf, -1); httpdSend(connData, buf, -1);
} else if (errMessage[0] && progState == stateSync) { } 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 //===== Cgi to write firmware to Optiboot, requires prior sync call
int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) { int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. 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 // check that we have sync
if (errMessage[0] || progState < stateProg) { if (errMessage[0] || progState < stateProg) {
@ -427,7 +426,7 @@ static bool pollAck() {
// Program a flash page // Program a flash page
static bool ICACHE_FLASH_ATTR programPage(void) { static bool ICACHE_FLASH_ATTR programPage(void) {
if (optibootData->pageLen == 0) return true; 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) { if (ackWait > 7) {
os_sprintf(errMessage, "Lost sync while programming\n"); os_sprintf(errMessage, "Lost sync while programming\n");
@ -436,7 +435,7 @@ static bool ICACHE_FLASH_ATTR programPage(void) {
uint16_t pgmLen = optibootData->pageLen; uint16_t pgmLen = optibootData->pageLen;
if (pgmLen > optibootData->pgmSz) pgmLen = optibootData->pgmSz; 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) // send address to optiboot (little endian format)
#ifdef DBG_GPIO5 #ifdef DBG_GPIO5
@ -448,12 +447,12 @@ static bool ICACHE_FLASH_ATTR programPage(void) {
uart0_write_char(addr & 0xff); uart0_write_char(addr & 0xff);
uart0_write_char(addr >> 8); uart0_write_char(addr >> 8);
uart0_write_char(CRC_EOP); uart0_write_char(CRC_EOP);
armTimer(); armTimer(PGM_TIMEOUT);
if (!pollAck()) { if (!pollAck()) {
DBG("OB pgm failed in load address\n"); DBG("OB pgm failed in load address\n");
return false; return false;
} }
armTimer(); armTimer(PGM_TIMEOUT);
// send page length (big-endian format, go figure...) // send page length (big-endian format, go figure...)
#ifdef DBG_GPIO5 #ifdef DBG_GPIO5
@ -470,9 +469,9 @@ static bool ICACHE_FLASH_ATTR programPage(void) {
uart0_write_char(optibootData->pageBuf[i]); uart0_write_char(optibootData->pageBuf[i]);
uart0_write_char(CRC_EOP); uart0_write_char(CRC_EOP);
armTimer(); armTimer(PGM_TIMEOUT);
bool ok = pollAck(); bool ok = pollAck();
armTimer(); armTimer(PGM_TIMEOUT);
if (!ok) { if (!ok) {
DBG("OB pgm failed in prog page\n"); DBG("OB pgm failed in prog page\n");
return false; return false;
@ -490,18 +489,17 @@ static bool ICACHE_FLASH_ATTR programPage(void) {
//===== Rebooting and getting sync //===== Rebooting and getting sync
static void ICACHE_FLASH_ATTR armTimer() { static void ICACHE_FLASH_ATTR armTimer(uint32_t ms) {
os_timer_disarm(&optibootTimer); os_timer_disarm(&optibootTimer);
// time-out every 50ms, except when programming to allow for 9600baud (133ms for 128 bytes) os_timer_arm(&optibootTimer, ms, 0);
os_timer_arm(&optibootTimer, progState==stateProg ? PGM_INTERVAL : SYNC_INTERVAL, 0);
} }
static int baudRates[] = { 0, 9600, 57600, 115200 }; static int baudRates[] = { 0, 9600, 57600, 115200 };
static void ICACHE_FLASH_ATTR setBaud() { static void ICACHE_FLASH_ATTR setBaud() {
baudRate = baudRates[(syncCnt / (BAUD_INTERVAL/SYNC_INTERVAL)) % 4]; baudRate = baudRates[(baudCnt++) % 4];
uart0_baud(baudRate); 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() { static void ICACHE_FLASH_ATTR initBaud() {
@ -511,45 +509,41 @@ static void ICACHE_FLASH_ATTR initBaud() {
static void ICACHE_FLASH_ATTR optibootTimerCB(void *arg) { 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 // see whether we've issued so many sync in a row that it's time to give up
syncCnt++;
switch (progState) { switch (progState) {
case stateSync: // we're trying to get sync, all we do here is send a sync request case stateInit: // initial delay expired, send sync chars
if (syncCnt >= SYNC_TIMEOUT/SYNC_INTERVAL) { 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 // 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(); optibootInit();
strcpy(errMessage, "sync abandoned after timeout"); strcpy(errMessage, "sync abandoned after 8 attempts");
return; return;
} }
if (syncCnt % (BAUD_INTERVAL/SYNC_INTERVAL) == 0) {
// time to switch baud rate and issue a reset // time to switch baud rate and issue a reset
DBG("OB no sync response @%ld baud\n", baudRate);
setBaud(); setBaud();
serbridgeReset(); serbridgeReset();
// no point sending chars if we just switched progState = stateInit;
} else { armTimer(INIT_DELAY);
//uart0_write_char(STK_GET_SYNC); return;
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;
case stateProg: // we're programming and we timed-out of inaction case stateProg: // we're programming and we timed-out of inaction
uart0_write_char(STK_GET_SYNC); uart0_write_char(STK_GET_SYNC);
uart0_write_char(CRC_EOP); uart0_write_char(CRC_EOP);
ackWait++; // we now expect an ACK 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! default: // we're trying to get some info from optiboot and it should have responded!
optibootInit(); // abort 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); DBG("OB %s\n", errMessage);
return; // do not re-arm timer return; // do not re-arm timer
} }
// we need to come back...
armTimer();
} }
// skip in-sync responses // skip in-sync responses
@ -575,8 +569,9 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) {
// dispatch based the current state // dispatch based the current state
switch (progState) { switch (progState) {
case stateInit: // we haven't sent anything, this must be garbage
break;
case stateSync: // we're trying to get a sync response case stateSync: // we're trying to get a sync response
case stateSync3: // we're trying to get a second sync response
// look for STK_INSYNC+STK_OK at end of buffer // look for STK_INSYNC+STK_OK at end of buffer
if (responseLen > 0 && responseBuf[responseLen-1] == STK_INSYNC) { if (responseLen > 0 && responseBuf[responseLen-1] == STK_INSYNC) {
// missing STK_OK after STK_INSYNC, shift stuff out and try again // 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; responseLen = 1;
} else if (responseLen > 1 && responseBuf[responseLen-2] == STK_INSYNC && } else if (responseLen > 1 && responseBuf[responseLen-2] == STK_INSYNC &&
responseBuf[responseLen-1] == STK_OK) { responseBuf[responseLen-1] == STK_OK) {
// got sync response, send more... // got sync response
os_memcpy(responseBuf, responseBuf+2, responseLen-2); os_memcpy(responseBuf, responseBuf+2, responseLen-2);
responseLen -= 2; responseLen -= 2;
if (progState==stateSync) { // send request to get signature
// 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(STK_READ_SIGN);
uart0_write_char(CRC_EOP); uart0_write_char(CRC_EOP);
}
progState++; progState++;
armTimer(); // reset timer armTimer(PGM_INTERVAL); // reset timer
} else { } else {
// nothing useful, keep at most half the buffer for error message purposes // nothing useful, keep at most half the buffer for error message purposes
if (responseLen > RESP_SZ/2) { if (responseLen > RESP_SZ/2) {
@ -606,18 +596,6 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) {
} }
} }
break; 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 case stateGetSig: // expecting signature
responseLen = skipInSync(responseBuf, responseLen); responseLen = skipInSync(responseBuf, responseLen);
if (responseLen >= 5 && responseBuf[0] == STK_INSYNC && responseBuf[4] == STK_OK) { 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(STK_GET_PARAMETER);
uart0_write_char(0x82); uart0_write_char(0x82);
uart0_write_char(CRC_EOP); uart0_write_char(CRC_EOP);
armTimer(); // reset timer armTimer(PGM_INTERVAL); // reset timer
} else { } else {
optibootInit(); // abort optibootInit(); // abort
os_sprintf(errMessage, "Bad programmer signature: 0x%02x 0x%02x 0x%02x\n", 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(STK_GET_PARAMETER);
uart0_write_char(0x81); uart0_write_char(0x81);
uart0_write_char(CRC_EOP); uart0_write_char(CRC_EOP);
armTimer(); // reset timer armTimer(PGM_INTERVAL); // reset timer
} }
break; break;
case stateGetVersHi: // expecting version case stateGetVersHi: // expecting version
@ -655,7 +633,7 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) {
progState++; progState++;
os_memcpy(responseBuf, responseBuf+3, responseLen-3); os_memcpy(responseBuf, responseBuf+3, responseLen-3);
responseLen -= 3; responseLen -= 3;
armTimer(); // reset timer armTimer(PGM_INTERVAL); // reset timer
ackWait = 0; ackWait = 0;
} }
break; break;
@ -666,7 +644,7 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) {
os_memmove(responseBuf, responseBuf+2, responseLen-2); os_memmove(responseBuf, responseBuf+2, responseLen-2);
responseLen -= 2; responseLen -= 2;
} }
armTimer(); // reset timer armTimer(PGM_INTERVAL); // reset timer
default: default:
break; break;
} }

@ -47,12 +47,20 @@ Cgi/template routines for the /wifi url.
} while(0) } while(0)
# define VERS_STR_STR(V) #V
# define VERS_STR(V) VERS_STR_STR(V)
bool mdns_started = false; bool mdns_started = false;
// ===== wifi status change callbacks // ===== wifi status change callbacks
static WifiStateChangeCb wifi_state_change_cb[4]; 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; uint8_t wifiState = wifiIsDisconnected;
// reasons for which a connection failed // reasons for which a connection failed
uint8_t wifiReason = 0; uint8_t wifiReason = 0;
@ -290,7 +298,14 @@ int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) {
if (connData->requestType == HTTPD_METHOD_GET) { if (connData->requestType == HTTPD_METHOD_GET) {
return cgiWiFiGetScan(connData); return cgiWiFiGetScan(connData);
}else if(connData->requestType == HTTPD_METHOD_POST) { }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); return cgiWiFiStartScan(connData);
}
}else{ }else{
jsonHeader(connData, 404); jsonHeader(connData, 404);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
@ -311,6 +326,7 @@ static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
int m = wifi_get_opmode() & 0x3; int m = wifi_get_opmode() & 0x3;
NOTICE("check: mode=%s status=%d", wifiMode[m], x); NOTICE("check: mode=%s status=%d", wifiMode[m], x);
if(m!=2){
if ( x == STATION_GOT_IP ) { if ( x == STATION_GOT_IP ) {
if (m != 1) { if (m != 1) {
#ifdef CHANGE_TO_STA #ifdef CHANGE_TO_STA
@ -326,15 +342,15 @@ static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
if (m != 3) { if (m != 3) {
NOTICE("connect failed. Going into STA+AP mode.."); NOTICE("connect failed. Going into STA+AP mode..");
wifi_set_opmode(3); wifi_set_opmode(3);
wifi_softap_set_config(&apconf);
} }
log_uart(true); log_uart(true);
INFO("Enabling/continuing uart log"); INFO("Enabling/continuing uart log");
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); 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 // Reassociate timer to delay change of association so the original request can finish
static ETSTimer reassTimer; 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 // This cgi uses the routines above to connect to a specific access point with the
// given ESSID using the given password. // given ESSID using the given password.
int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { 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 essid[128];
char passwd[128]; char passwd[128];
@ -504,24 +526,176 @@ int ICACHE_FLASH_ATTR cgiWiFiSpecial(HttpdConnData *connData) {
return HTTPD_CGI_DONE; 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 //This cgi changes the operating mode: STA / AP / STA+AP
int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) {
int len; int len;
char buff[1024]; char buff[1024];
int previous_mode = wifi_get_opmode();
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff));
int next_mode = atoi(buff);
if (len!=0) { if (len!=0) {
int m = atoi(buff); if (next_mode == 2){
NOTICE("switching to mode %d", m); // moving to AP mode, so disconnect before leave STA mode
wifi_set_opmode(m&3); wifi_station_disconnect();
if (m == 1) { }
// STA-only mode, reset into STA+AP after a timeout if we don't get an IP address
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_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL); os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); 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); jsonHeader(connData, 200);
} else { } else {
jsonHeader(connData, 400); jsonHeader(connData, 400);
@ -534,8 +708,16 @@ static char *connStatuses[] = { "idle", "connecting", "wrong password", "AP not
static char *wifiWarn[] = { 0, static char *wifiWarn[] = { 0,
"Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(3)\\\">STA+AP mode</a>", "Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(3)\\\">STA+AP mode</a>",
"<b>Can't scan in this mode!</b> Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(3)\\\">STA+AP mode</a>", "Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(3)\\\">STA+AP mode</a>",
"Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(1)\\\">STA mode</a>", "Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(1)\\\">STA mode</a>",
"Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(2)\\\">AP mode</a>",
};
static char *apAuthMode[] = { "OPEN",
"WEP",
"WPA_PSK",
"WPA2_PSK",
"WPA_WPA2_PSK",
}; };
#ifdef CHANGE_TO_STA #ifdef CHANGE_TO_STA
@ -547,9 +729,10 @@ static char *wifiWarn[] = { 0,
// print various Wifi information into json buffer // print various Wifi information into json buffer
int ICACHE_FLASH_ATTR printWifiInfo(char *buff) { int ICACHE_FLASH_ATTR printWifiInfo(char *buff) {
int len; int len;
//struct station_config stconf;
struct station_config stconf;
wifi_station_get_config(&stconf); wifi_station_get_config(&stconf);
//struct softap_config apconf;
wifi_softap_get_config(&apconf);
uint8_t op = wifi_get_opmode() & 0x3; uint8_t op = wifi_get_opmode() & 0x3;
char *mode = wifiMode[op]; char *mode = wifiMode[op];
@ -559,17 +742,23 @@ int ICACHE_FLASH_ATTR printWifiInfo(char *buff) {
int p = wifi_get_phy_mode(); int p = wifi_get_phy_mode();
char *phy = wifiPhy[p&3]; char *phy = wifiPhy[p&3];
char *warn = wifiWarn[op]; 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(); sint8 rssi = wifi_station_get_rssi();
if (rssi > 0) rssi = 0; if (rssi > 0) rssi = 0;
uint8 mac_addr[6]; uint8 mac_addr[6];
uint8 apmac_addr[6];
wifi_get_macaddr(0, mac_addr); wifi_get_macaddr(0, mac_addr);
wifi_get_macaddr(1, apmac_addr);
uint8_t chan = wifi_get_channel(); uint8_t chan = wifi_get_channel();
len = os_sprintf(buff, len = os_sprintf(buff,
"\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", " "\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", "
"\"rssi\": \"%ddB\", \"warn\": \"%s\", \"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":%d", "\"rssi\": \"%ddB\", \"warn\": \"%s\", \"apwarn\": \"%s\",\"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":\"%d\", \"apssid\": \"%s\", "
mode, MODECHANGE, (char*)stconf.ssid, status, phy, rssi, warn, "\"appass\": \"%s\", \"apchan\": \"%d\", \"apmaxc\": \"%d\", \"aphidd\": \"%s\", \"apbeac\": \"%d\", \"apauth\": \"%s\",\"apmac\":\"%02x:%02x:%02x:%02x:%02x:%02x\"",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], chan); 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; struct ip_info info;
if (wifi_get_ip_info(0, &info)) { if (wifi_get_ip_info(0, &info)) {
@ -636,13 +825,113 @@ int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) {
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
// Init the wireless, which consists of setting a timer if we expect to connect to an AP // Check string againt invalid characters
// so we can revert to STA+AP mode if we can't connect. 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() { void ICACHE_FLASH_ATTR wifiInit() {
// wifi_set_phy_mode(2); // limit to 802.11b/g 'cause n is flaky
// Check te wifi opmode
int x = wifi_get_opmode() & 0x3; int x = wifi_get_opmode() & 0x3;
x = x;
NOTICE("init, mode=%s", wifiMode[x]); // 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(); configWifiIP();
// The default sleep mode should be modem_sleep, but we set it here explicitly for good // The default sleep mode should be modem_sleep, but we set it here explicitly for good
@ -656,4 +945,3 @@ void ICACHE_FLASH_ATTR wifiInit() {
os_timer_setfn(&resetTimer, resetTimerCb, NULL); os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); os_timer_arm(&resetTimer, RESET_TIMEOUT, 0);
} }

@ -13,10 +13,13 @@ int cgiWiFiConnect(HttpdConnData *connData);
int cgiWiFiSetMode(HttpdConnData *connData); int cgiWiFiSetMode(HttpdConnData *connData);
int cgiWiFiConnStatus(HttpdConnData *connData); int cgiWiFiConnStatus(HttpdConnData *connData);
int cgiWiFiSpecial(HttpdConnData *connData); int cgiWiFiSpecial(HttpdConnData *connData);
int cgiApSettingsChange(HttpdConnData *connData);
int cgiApSettingsInfo(HttpdConnData *connData);
void configWifiIP(); void configWifiIP();
void wifiInit(void); void wifiInit(void);
void wifiAddStateChangeCb(WifiStateChangeCb cb); void wifiAddStateChangeCb(WifiStateChangeCb cb);
void wifiStartMDNS(struct ip_addr); void wifiStartMDNS(struct ip_addr);
int checkString(char *str);
extern uint8_t wifiState; extern uint8_t wifiState;
extern bool mdns_started; extern bool mdns_started;

@ -72,6 +72,8 @@ HttpdBuiltInUrl builtInUrls[] = {
{ "/wifi/connstatus", cgiWiFiConnStatus, NULL }, { "/wifi/connstatus", cgiWiFiConnStatus, NULL },
{ "/wifi/setmode", cgiWiFiSetMode, NULL }, { "/wifi/setmode", cgiWiFiSetMode, NULL },
{ "/wifi/special", cgiWiFiSpecial, NULL }, { "/wifi/special", cgiWiFiSpecial, NULL },
{ "/wifi/apinfo", cgiApSettingsInfo, NULL },
{ "/wifi/apchange", cgiApSettingsChange, NULL },
{ "/system/info", cgiSystemInfo, NULL }, { "/system/info", cgiSystemInfo, NULL },
{ "/system/update", cgiSystemSet, NULL }, { "/system/update", cgiSystemSet, NULL },
{ "/services/info", cgiServicesInfo, NULL }, { "/services/info", cgiServicesInfo, NULL },
@ -86,7 +88,6 @@ HttpdBuiltInUrl builtInUrls[] = {
#ifdef SHOW_HEAP_USE #ifdef SHOW_HEAP_USE
static ETSTimer prHeapTimer; static ETSTimer prHeapTimer;
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) { static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) {
os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size()); os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size());
} }
@ -112,45 +113,21 @@ void user_init(void) {
// get the flash config so we know how to init things // 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(); bool restoreOk = configRestore();
// init gpio pin registers // Init gpio pin registers
gpio_init(); gpio_init();
gpio_output_set(0, 0, 0, (1<<15)); // some people tie it to GND, gotta ensure it's disabled gpio_output_set(0, 0, 0, (1<<15)); // some people tie it to GND, gotta ensure it's disabled
// init UART // init UART
uart_init(flashConfig.baud_rate, 115200); uart_init(flashConfig.baud_rate, 115200);
logInit(); // must come after init of uart 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_delay_us(10000L);
os_printf("\n\n** %s\n", esp_link_version); os_printf("\n\n** %s\n", esp_link_version);
os_printf("Flash config restore %s\n", restoreOk ? "ok" : "*FAILED*"); 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 // Status LEDs
statusInit(); statusInit();
serledInit(); serledInit();
// Wifi // Wifi
wifiInit(); wifiInit();
// init the flash filesystem with the html stuff // init the flash filesystem with the html stuff
espFsInit(&_binary_espfs_img_start); espFsInit(&_binary_espfs_img_start);
//EspFsInitResult res = 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)); fid & 0xff, (fid&0xff00)|((fid>>16)&0xff));
NOTICE("** %s: ready, heap=%ld", esp_link_version, (unsigned long)system_get_free_heap_size()); NOTICE("** %s: ready, heap=%ld", esp_link_version, (unsigned long)system_get_free_heap_size());
// Init SNTP service
cgiServicesSNTPInit(); cgiServicesSNTPInit();
#ifdef MQTT #ifdef MQTT
NOTICE("initializing MQTT"); NOTICE("initializing MQTT");
mqtt_client_init(); mqtt_client_init();
#endif #endif
NOTICE("initializing user application"); NOTICE("initializing user application");
app_init(); app_init();
NOTICE("Waiting for work to do...");
NOTICE("waiting for work to do...");
} }

@ -18,34 +18,31 @@ static MqttCallback published_cb;
static MqttDataCallback data_cb; static MqttDataCallback data_cb;
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
mqttConnectedCb(uint32_t *args) { mqttConnectedCb(MQTT_Client* client) {
DBG("MQTT Client: Connected\n"); DBG("MQTT Client: Connected\n");
//MQTT_Client* client = (MQTT_Client*)args;
//MQTT_Subscribe(client, "system/time", 0); // handy for testing //MQTT_Subscribe(client, "system/time", 0); // handy for testing
if (connected_cb) if (connected_cb)
connected_cb(args); connected_cb(client);
} }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
mqttDisconnectedCb(uint32_t *args) { mqttDisconnectedCb(MQTT_Client* client) {
// MQTT_Client* client = (MQTT_Client*)args;
DBG("MQTT Client: Disconnected\n"); DBG("MQTT Client: Disconnected\n");
if (disconnected_cb) if (disconnected_cb)
disconnected_cb(args); disconnected_cb(client);
} }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
mqttPublishedCb(uint32_t *args) { mqttPublishedCb(MQTT_Client* client) {
// MQTT_Client* client = (MQTT_Client*)args;
DBG("MQTT Client: Published\n"); DBG("MQTT Client: Published\n");
if (published_cb) if (published_cb)
published_cb(args); published_cb(client);
} }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
mqttDataCb(uint32_t *args, const char* topic, uint32_t topic_len, const char *data, uint32_t data_len) { mqttDataCb(MQTT_Client* client, const char* topic, uint32_t topic_len,
// MQTT_Client* client = (MQTT_Client*)args; const char *data, uint32_t data_len)
{
#ifdef MQTTCLIENT_DBG #ifdef MQTTCLIENT_DBG
char *topicBuf = (char*)os_zalloc(topic_len + 1); char *topicBuf = (char*)os_zalloc(topic_len + 1);
char *dataBuf = (char*)os_zalloc(data_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 #endif
if (data_cb) 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 void ICACHE_FLASH_ATTR

@ -37,7 +37,7 @@ static void ICACHE_FLASH_ATTR mqttStatusCb(void *v) {
char buf[128]; char buf[128];
mqttStatusMsg(buf); 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);
} }

@ -22,8 +22,8 @@
</div> </div>
</td></tr> </td></tr>
<tr><td>Network SSID</td><td id="wifi-ssid"></td></tr> <tr><td>Network SSID</td><td id="wifi-ssid"></td></tr>
<tr><td>WiFI status</td><td id="wifi-status"></td></tr> <tr><td>WiFi status</td><td id="wifi-status"></td></tr>
<tr><td>WiFI address</td><td id="wifi-ip"></td></tr> <tr><td>WiFi address</td><td id="wifi-ip"></td></tr>
<tr><td>SLIP status</td><td class="system-slip"></td></tr> <tr><td>SLIP status</td><td class="system-slip"></td></tr>
<tr><td>MQTT status</td><td class="system-mqtt"></td></tr> <tr><td>MQTT status</td><td class="system-mqtt"></td></tr>
<tr><td>Serial baud</td><td class="system-baud"></td></tr> <tr><td>Serial baud</td><td class="system-baud"></td></tr>
@ -32,7 +32,7 @@
<div class="card"> <div class="card">
<h1>Info</h1> <h1>Info</h1>
<p style="margin-bottom:0;">The JeeLabs esp-link firmware bridges the ESP8266 <p style="margin-bottom:0;">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 program microcontrollers over the serial port, in particular Arduinos, AVRs, and
NXP's LPC800 and other ARM processors. Typical avrdude command line to NXP's LPC800 and other ARM processors. Typical avrdude command line to
program an Arduino:</p> program an Arduino:</p>
@ -79,7 +79,7 @@
<div class="pure-control-group"> <div class="pure-control-group">
<label for="pin-conn">Conn LED</label> <label for="pin-conn">Conn LED</label>
<select id="pin-conn"></select> <select id="pin-conn"></select>
<div class="popup">LED to show wifi connectivity</div> <div class="popup">LED to show WiFi connectivity</div>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="pin-ser">Serial LED</label> <label for="pin-ser">Serial LED</label>
@ -109,8 +109,8 @@
<h1>System details</h1> <h1>System details</h1>
<div id="system-spinner" class="spinner spinner-small"></div> <div id="system-spinner" class="spinner spinner-small"></div>
<table id="system-table" class="pure-table pure-table-horizontal" hidden><tbody> <table id="system-table" class="pure-table pure-table-horizontal" hidden><tbody>
<tr><td>WiFI mode</td><td id="wifi-mode"></td></tr> <tr><td>WiFi mode</td><td id="wifi-mode"></td></tr>
<tr><td>WiFI channel</td><td id="wifi-chan"></td></tr> <tr><td>WiFi channel</td><td id="wifi-chan"></td></tr>
<tr><td>Flash chip ID</td><td> <tr><td>Flash chip ID</td><td>
<div> <div>
<span class="system-id"></span> <span class="system-id"></span>
@ -126,7 +126,7 @@
<tr><td>Current partition</td><td class="system-partition"></td></tr> <tr><td>Current partition</td><td class="system-partition"></td></tr>
<tr><td colspan=2 class="popup-target">Description:<br> <tr><td colspan=2 class="popup-target">Description:<br>
<div class="click-to-edit system-description"> <div class="click-to-edit system-description">
<div class="edit-off"></div> <span class="edit-off" style="display:block; width:auto;"></span>
<textarea class="edit-on" rows=3 maxlength=127 hidden> </textarea> <textarea class="edit-on" rows=3 maxlength=127 hidden> </textarea>
<div class="popup">Click to edit!<br>A short description or memo for this esp-link <div class="popup">Click to edit!<br>A short description or memo for this esp-link
module, 128 chars max</div> module, 128 chars max</div>

@ -14,7 +14,7 @@
using parameters set below and stored in esp-link's flash settings. This allows using parameters set below and stored in esp-link's flash settings. This allows
esp-link to take care of connection parameters and disconnect/reconnect operations.</p> esp-link to take care of connection parameters and disconnect/reconnect operations.</p>
<p>The MQTT client also supports sending periodic status messages about esp-link itself, <p>The MQTT client also supports sending periodic status messages about esp-link itself,
including WiFI RSSI, and free heap memory.</p> including WiFi RSSI, and free heap memory.</p>
<div class="form-horizontal"> <div class="form-horizontal">
<input type="checkbox" name="slip-enable"/> <input type="checkbox" name="slip-enable"/>
<label>Enable SLIP on serial port</label> <label>Enable SLIP on serial port</label>
@ -28,11 +28,14 @@
<div id="mqtt-spinner" class="spinner spinner-small"></div> <div id="mqtt-spinner" class="spinner spinner-small"></div>
</h1> </h1>
<form action="#" id="mqtt-form" class="pure-form" hidden> <form action="#" id="mqtt-form" class="pure-form" hidden>
<div>
<input type="checkbox" name="mqtt-enable"/> <input type="checkbox" name="mqtt-enable"/>
<label>Enable MQTT client</label> <label>Enable MQTT client</label>
<br> </div>
<div>
<label>MQTT client state: </label> <label>MQTT client state: </label>
<b id="mqtt-state"></b> <b id="mqtt-state"></b>
</div>
<br> <br>
<legend>MQTT server settings</legend> <legend>MQTT server settings</legend>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
@ -68,11 +71,11 @@
<label>Enable status reporting via MQTT</label> <label>Enable status reporting via MQTT</label>
</div> </div>
<br> <br>
<legend>Status reporting settings</legend>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
<label>Topic</label> <label>Status topic</label>
<input type="text" name="mqtt-status-topic"/> <input type="text" name="mqtt-status-topic"/>
Message: <tt id="mqtt-status-value"></tt> Message: <tt id="mqtt-status-value"></tt>
<div class="popup">MQTT topic to which status message is sent</div>
</div> </div>
<button id="mqtt-status-button" type="submit" class="pure-button button-primary"> <button id="mqtt-status-button" type="submit" class="pure-button button-primary">
Update status settings! Update status settings!

@ -12,15 +12,19 @@
<div id="syslog-spinner" class="spinner spinner-small"></div> <div id="syslog-spinner" class="spinner spinner-small"></div>
</h1> </h1>
<form action="#" id="Syslog-form" class="pure-form" hidden> <form action="#" id="Syslog-form" class="pure-form" hidden>
<legend>Syslog settings</legend>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
<label>Syslog Host</label> <label>Syslog Host</label>
<input type="text" name="syslog_host" /> <input type="text" name="syslog_host" />
<div class="popup">Use server [hostname:port] as UDP Syslog server</div> <div class="popup">Esp-link sends event/debug info to this syslog host
(hostname:port). Leave empty to disable syslog.</div>
</div> </div>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
<label>Min Heap</label> <label>Min Heap</label>
<div>
<input type="text" name="syslog_minheap" /> <input type="text" name="syslog_minheap" />
<div class="popup">Stop sending syslog if free heap drops below this many bytes</div>
</div>
<div>
<label>Filter</label> <label>Filter</label>
<select name="syslog_filter" href="#"> <select name="syslog_filter" href="#">
<option value="0">EMERG</option> <option value="0">EMERG</option>
@ -32,15 +36,17 @@
<option value="6">INFO</option> <option value="6">INFO</option>
<option value="7">DEBUG</option> <option value="7">DEBUG</option>
</select> </select>
<div class="popup">Minimum severity to send</div>
</div> </div>
<div class="form-horizontal"> </div>
<label>Show ESP &#xb5;C Ticker in log message</label> <div>
<input type="checkbox" name="syslog_showtick" /> <input type="checkbox" name="syslog_showtick" />
<label>Include esp-link millisecond ticker</label>
</div> </div>
<div class="form-horizontal"> <div>
<label>Show date in log message</label>
<input type="checkbox" name="syslog_showdate" /> <input type="checkbox" name="syslog_showdate" />
<div class="popup">Synology does a log rotate if timestamp is in the past so disable to prevent this</div> <label>Include esp-link datetime</label>
<div class="popup">Some syslog servers rotate log if timestamp is in the past so disable to prevent this</div>
</div> </div>
<button id="Syslog-button" type="submit" class="pure-button button-primary"> <button id="Syslog-button" type="submit" class="pure-button button-primary">
Update Syslog settings! Update Syslog settings!
@ -56,12 +62,14 @@
<div class="form-horizontal"> <div class="form-horizontal">
<input type="checkbox" name="mdns_enable"/> <input type="checkbox" name="mdns_enable"/>
<label>Enable mDNS</label> <label>Enable mDNS</label>
<div class="popup">Esp-link can advertise its hostname and service name (both
with a .local suffix) via multicast DNS.</div>
</div> </div>
<br> <br>
<legend>mDNS server settings</legend>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
<label>Server Name</label> <label>Service Name</label>
<input type="text" name="mdns_servername"/> <input type="text" name="mdns_servername"/>
<div class="popup">The default service is http. For the arduino IDE use arduino</div>
</div> </div>
<button id="mDNS-button" type="submit" class="pure-button button-primary"> <button id="mDNS-button" type="submit" class="pure-button button-primary">
Update mDNS settings! Update mDNS settings!
@ -76,12 +84,18 @@
<div id="sntp-spinner" class="spinner spinner-small"></div> <div id="sntp-spinner" class="spinner spinner-small"></div>
</h1> </h1>
<form action="#" id="SNTP-form" class="pure-form" hidden> <form action="#" id="SNTP-form" class="pure-form" hidden>
<legend>SNTP server settings</legend>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
<label>Timezone Offset</label> <div>
<input type="text" name="timezone_offset" />
<label>SNTP Server</label> <label>SNTP Server</label>
<input type="text" name="sntp_server" /> <input type="text" name="sntp_server" />
<div class="popup">Simple Network Time Protocol server to query.
Leave empty to disable SNTP</div>
</div>
<div>
<label>Timezone Offset</label>
<input type="text" name="timezone_offset" />
<div class="popup">Offset hours to apply (no daylight savings support)</div>
</div>
</div> </div>
<button id="SNTP-button" type="submit" class="pure-button button-primary"> <button id="SNTP-button" type="submit" class="pure-button button-primary">
Update SNTP settings! Update SNTP settings!

@ -85,10 +85,10 @@ a:hover {
.popup, div.popup { .popup, div.popup {
position: absolute; position: absolute;
/*top: 100%;*/ /*top: 100%;*/
bottom: 100%; bottom: 125%;
background-color: #fff0b3; background-color: #fff0b3;
border-radius: 5px; border-radius: 5px;
border: 0px solid #000; border: 1px solid #e6b800;
color: #333; color: #333;
font-size: 80%; font-size: 80%;
line-height: 110%; line-height: 110%;

@ -0,0 +1,124 @@
<div id="main">
<div class="header">
<h1>WiFi Soft-AP Configuration</h1>
</div>
<div class="content">
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<div class="card">
<h1>Soft-AP State</h1>
<div id="wifi-spinner" class="spinner spinner-small">
</div>
<table id="wifi-table" class="pure-table pure-table-horizontal" hidden><tbody>
<tr><td>WiFi mode</td><td id="wifi-mode"></td></tr>
<tr><td>Soft-AP SSID</td><td id="wifi-apssid"></td></tr>
<tr><td>Soft-AP Password</td><td id="wifi-appass"></td></tr>
<tr><td>Soft-AP Channel</td><td id="wifi-apchan"></td></tr>
<tr><td>Soft-AP Max Conn</td><td id="wifi-apmaxc"></td></tr>
<tr><td>Soft-AP Hidden</td><td id="wifi-aphidd"></td></tr>
<tr><td>Soft-AP Beacon Int</td><td id="wifi-apbeac"></td></tr>
<tr><td>Soft-AP Auth Mode</td><td id="wifi-apauth"></td></tr>
<tr><td>Soft-AP MAC</td><td id="wifi-apmac"></td></tr>
<tr><td colspan="2" id="wifi-apwarn"></td></tr>
</tbody> </table>
</div><!-- card-->
</div><!-- pure-u-1 -->
<div class="pure-u-1 pure-u-md-1-2">
<div class="card">
<h1>Soft-AP Settings</h1>
<div id="AP_Settings-spinner" class="spinner spinner-small"></div>
<form action="#" id="AP_Settings-form" class="pure-form" hidden>
<!-- <input type="text" id="conn_check" name="ap_connex" value="0" hidden>-->
<legend>Soft-AP main settings, use with care!</legend>
<div class="pure-form-stacked">
<label>Soft-AP SSID</label>
<input type="text" name="ap_ssid" />
<div class="popup">Change the name of your AP!</div>
</div>
<div class="pure-form-stacked">
<label>Soft-AP Password</label>
<input type="text" name="ap_password" />
<div class="popup">Password must be at least 8 chars long!</div>
</div>
<div class="pure-form-stacked">
<legend>Soft-AP Advanced Settings</legend>
</div>
<div class="form-horizontal">
<label for="AP_Settings-ron" style="margin-right:1em">
<input type="radio" name="ap" value="on" id="AP_Settings-ron"/>
Show </label>
<label for="AP_Settings-roff" style="margin-right:1em">
<input type="radio" name="ap" value="off" id="AP_Settings-roff"/>
Hide </label>
</div>
<div id="AP_Settings-off" class="pure-form-stacked"></div>
<div id="AP_Settings-on" class="pure-form-stacked">
<div class="pure-form-stacked">
<label>Soft-AP Max Connections</label>
<input type="text" name="ap_maxconn" />
<div class="popup">Max 4 ( default 4 )</div>
</div>
<div class="pure-form-stacked">
<label>Soft-AP Beacon Interval</label>
<input type="text" name="ap_beacon" />
<div class="popup">Between 100 - 60000 ms ( default 100ms )</div>
</div>
<div class="pure-form-stacked">
<label>Soft-AP Auth Mode</label>
<select name="ap_authmode" href="#">
<option value="0">OPEN</option>
<option value="1">WEP</option>
<option value="2">WPA_PSK</option>
<option value="3">WPA2_PSK</option>
<option value="4">WPA_WPA2_PSK</option>
</select>
<div class="popup">Default WPA_WPA2_PSK</div>
</div>
<div class="form-horizontal">
<label><input type="checkbox" name="ap_hidden" />Soft-AP SSID hidden</label>
<div class="popup">Check this box to hide you Soft-AP SSID ( default Not Hidden )</div>
</div>
</div>
<button id="AP_Settings-button" type="submit" class="pure-button button-primary">
Change Soft-AP settings!
</button>
</form>
</div>
</div><!-- pure-u-1 -->
</div><!-- pure-g -->
</div><!-- content -->
</div><!-- main -->
</div><!-- layout -->
<script type="text/javascript">
</script>
<script src="wifiAp.js"></script>
<script type="text/javascript">
onLoad(function() {
// Show info about AP
getWifiInfo();
// Fetch actual settings
fetchApSettings();
// Hide advanced settings
undoApAdvanced();
bnd($("#AP_Settings-ron"), "click", doApAdvanced);
bnd($("#AP_Settings-roff"), "click", undoApAdvanced);
bnd($("#AP_Settings-form"), "submit", changeApSettings);
});
</script>
</body></html>

@ -0,0 +1,98 @@
var specials = [];
specials["ap_ssid"] = "SSID name";
specials["ap_password"] = "PASSWORD";
specials["ap_maxconn"] = "Max Connections number";
specials["ap_beacon"] = "Beacon Interval";
function changeWifiMode(m) {
blockScan = 1;
hideWarning();
ajaxSpin("POST", "setmode?mode=" + m, function(resp) {
showNotification("Mode changed");
window.setTimeout(getWifiInfo, 100);
blockScan = 0;
}, function(s, st) {
showWarning("Error changing mode: " + st);
window.setTimeout(getWifiInfo, 100);
blockScan = 0;
});
}
function changeApSettings(e) {
e.preventDefault();
var url = "/wifi/apchange?100=1";
var i, inputs = document.querySelectorAll("#" + e.target.id + " input,select");
for (i = 0; i < inputs.length; i++) {
if (inputs[i].type == "checkbox") {
var val = (inputs[i].checked) ? 1 : 0;
url += "&" + inputs[i].name + "=" + val;
}
else{
var clean = inputs[i].value.replace(/[^\w]/gi, "");
var comp = clean.localeCompare(inputs[i].value);
if ( comp != 0 ){
showWarning("Invalid characters in " + specials[inputs[i].name]);
return;
}
url += "&" + inputs[i].name + "=" + clean;
}
};
hideWarning();
var n = e.target.id.replace("-form", "");
var cb = $("#" + n + "-button");
addClass(cb, "pure-button-disabled");
ajaxSpin("POST", url, function (resp) {
showNotification(n + " updated");
removeClass(cb, "pure-button-disabled");
window.setTimeout(getWifiInfo, 100);
}, function (s, st) {
showWarning(st);
removeClass(cb, "pure-button-disabled");
window.setTimeout(fetchApSettings, 2000);
});
}
function displayApSettings(data) {
Object.keys(data).forEach(function (v) {
el = $("#" + v);
if (el != null) {
if (el.nodeName === "INPUT") el.value = data[v];
else el.innerHTML = data[v];
return;
}
el = document.querySelector('input[name="' + v + '"]');
if (el == null)
el = document.querySelector('select[name="' + v + '"]');
if (el != null) {
if (el.type == "checkbox") {
el.checked = data[v] == "enabled";
} else el.value = data[v];
}
});
$("#AP_Settings-spinner").setAttribute("hidden", "");
$("#AP_Settings-form").removeAttribute("hidden");
showWarning("Don't modify SOFTAP parameters with active connections");
window.setTimeout(hideWarning(), 2000);
}
function fetchApSettings() {
ajaxJson("GET", "/wifi/apinfo", displayApSettings, function () {
window.setTimeout(fetchApSettings, 1000);
});
}
function doApAdvanced() {
$('#AP_Settings-on').removeAttribute('hidden');
$("#AP_Settings-off").setAttribute("hidden", "");
$("#AP_Settings-roff").removeAttribute("checked");
}
function undoApAdvanced(){
$("#AP_Settings-on").setAttribute("hidden", "");
$("#AP_Settings-off").removeAttribute("hidden");
$("#AP_Settings-roff").setAttribute("checked", "");
}

@ -1,6 +1,6 @@
<div id="main"> <div id="main">
<div class="header"> <div class="header">
<h1>WiFi Configuration</h1> <h1>WiFi Station Configuration</h1>
</div> </div>
<div class="content"> <div class="content">
@ -19,7 +19,9 @@
<tr><td>WiFi MAC</td><td id="wifi-mac"></td></tr> <tr><td>WiFi MAC</td><td id="wifi-mac"></td></tr>
<tr><td colspan="2" id="wifi-warn"></td></tr> <tr><td colspan="2" id="wifi-warn"></td></tr>
</tbody> </table> </tbody> </table>
</div></div> </div>
</div>
<div class="pure-u-1 pure-u-md-1-2"><div class="card"> <div class="pure-u-1 pure-u-md-1-2"><div class="card">
<h1>WiFi Association</h1> <h1>WiFi Association</h1>
<p id="reconnect" style="color: #600" hidden></p> <p id="reconnect" style="color: #600" hidden></p>
@ -70,7 +72,7 @@
<script type="text/javascript"> <script type="text/javascript">
</script> </script>
<script src="wifi.js"></script> <script src="wifiSta.js"></script>
<script type="text/javascript"> <script type="text/javascript">
onLoad(function() { onLoad(function() {
getWifiInfo(); getWifiInfo();

@ -27,7 +27,7 @@
#define RESTCMD_DBG #define RESTCMD_DBG
#define SERBR_DBG #define SERBR_DBG
#define SERLED_DBG #define SERLED_DBG
#undef SLIP_DBG #define SLIP_DBG
#define UART_DBG #define UART_DBG
#define MDNS_DBG #define MDNS_DBG
#define OPTIBOOT_DBG #define OPTIBOOT_DBG

@ -89,9 +89,9 @@ deliver_publish(MQTT_Client* client, uint8_t* message, uint16_t length) {
// callback to client // callback to client
if (client->dataCb) if (client->dataCb)
client->dataCb((uint32_t*)client, topic, topic_length, data, data_length); client->dataCb(client, topic, topic_length, data, data_length);
if (client->cmdDataCb) if (client->cmdDataCb)
client->cmdDataCb((uint32_t*)client, topic, topic_length, data, data_length); client->cmdDataCb(client, topic, topic_length, data, data_length);
} }
/** /**
@ -164,8 +164,8 @@ mqtt_tcpclient_recv(void* arg, char* pdata, unsigned short len) {
case MQTT_MSG_TYPE_CONNACK: case MQTT_MSG_TYPE_CONNACK:
//DBG("MQTT: Connect successful\n"); //DBG("MQTT: Connect successful\n");
// callbacks for internal and external clients // callbacks for internal and external clients
if (client->connectedCb) client->connectedCb((uint32_t*)client); if (client->connectedCb) client->connectedCb(client);
if (client->cmdConnectedCb) client->cmdConnectedCb((uint32_t*)client); if (client->cmdConnectedCb) client->cmdConnectedCb(client);
client->reconTimeout = 1; // reset the reconnect backoff client->reconTimeout = 1; // reset the reconnect backoff
break; break;
@ -357,8 +357,8 @@ mqtt_tcpclient_discon_cb(void* arg) {
// if this is an aborted connection we're done // if this is an aborted connection we're done
if (client == NULL) return; if (client == NULL) return;
DBG("MQTT: Disconnected from %s:%d\n", client->host, client->port); DBG("MQTT: Disconnected from %s:%d\n", client->host, client->port);
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client); if (client->disconnectedCb) client->disconnectedCb(client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client); if (client->cmdDisconnectedCb) client->cmdDisconnectedCb(client);
// reconnect unless we're in a permanently disconnected state // reconnect unless we're in a permanently disconnected state
if (client->connState == MQTT_DISCONNECTED) return; if (client->connState == MQTT_DISCONNECTED) return;
@ -380,8 +380,8 @@ mqtt_tcpclient_recon_cb(void* arg, int8_t err) {
if (pespconn->proto.tcp) os_free(pespconn->proto.tcp); if (pespconn->proto.tcp) os_free(pespconn->proto.tcp);
os_free(pespconn); os_free(pespconn);
os_printf("MQTT: Connection reset from %s:%d\n", client->host, client->port); os_printf("MQTT: Connection reset from %s:%d\n", client->host, client->port);
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client); if (client->disconnectedCb) client->disconnectedCb(client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client); if (client->cmdDisconnectedCb) client->cmdDisconnectedCb(client);
// reconnect unless we're in a permanently disconnected state // reconnect unless we're in a permanently disconnected state
if (client->connState == MQTT_DISCONNECTED) return; if (client->connState == MQTT_DISCONNECTED) return;
@ -547,10 +547,11 @@ msg_conn_init(mqtt_connection_t *new_msg, mqtt_connection_t *old_msg,
* @retval TRUE if success queue * @retval TRUE if success queue
*/ */
bool ICACHE_FLASH_ATTR bool ICACHE_FLASH_ATTR
MQTT_Publish(MQTT_Client* client, const char* topic, const char* data, uint8_t qos, uint8_t retain) { MQTT_Publish(MQTT_Client* client, const char* topic, const char* data, uint16_t data_length,
uint8_t qos, uint8_t retain)
{
// estimate the packet size to allocate a buffer // estimate the packet size to allocate a buffer
uint16_t topic_length = os_strlen(topic); uint16_t topic_length = os_strlen(topic);
uint16_t data_length = os_strlen(data);
// estimate: fixed hdr, pkt-id, topic length, topic, data, fudge // estimate: fixed hdr, pkt-id, topic length, topic, data, fudge
uint16_t buf_len = 3 + 2 + 2 + topic_length + data_length + 16; uint16_t buf_len = 3 + 2 + 2 + topic_length + data_length + 16;
PktBuf *buf = PktBuf_New(buf_len); PktBuf *buf = PktBuf_New(buf_len);
@ -738,8 +739,8 @@ mqtt_doAbort(MQTT_Client* client) {
else else
espconn_disconnect(client->pCon); espconn_disconnect(client->pCon);
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client); if (client->disconnectedCb) client->disconnectedCb(client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client); if (client->cmdDisconnectedCb) client->cmdDisconnectedCb(client);
if (client->sending_buffer != NULL) { if (client->sending_buffer != NULL) {
os_free(client->sending_buffer); os_free(client->sending_buffer);

@ -44,14 +44,16 @@ typedef enum {
MQTT_CONNECTED, // conneted (or connecting) MQTT_CONNECTED, // conneted (or connecting)
} tConnState; } tConnState;
typedef struct MQTT_Client MQTT_Client; // forward definition
// Simple notification callback // Simple notification callback
typedef void (*MqttCallback)(uint32_t* args); typedef void (*MqttCallback)(MQTT_Client *client);
// Callback with data messge // Callback with data messge
typedef void (*MqttDataCallback)(uint32_t* args, const char* topic, uint32_t topic_len, typedef void (*MqttDataCallback)(MQTT_Client *client, const char* topic, uint32_t topic_len,
const char* data, uint32_t data_len); const char* data, uint32_t data_len);
// MQTTY client data structure // MQTTY client data structure
typedef struct { struct MQTT_Client {
struct espconn* pCon; // socket struct espconn* pCon; // socket
// connection information // connection information
char* host; // MQTT server char* host; // MQTT server
@ -89,7 +91,7 @@ typedef struct {
MqttDataCallback cmdDataCb; MqttDataCallback cmdDataCb;
// misc // misc
void* user_data; void* user_data;
} MQTT_Client; };
// Initialize client data structure // Initialize client data structure
void MQTT_Init(MQTT_Client* mqttClient, char* host, uint32 port, void MQTT_Init(MQTT_Client* mqttClient, char* host, uint32 port,
@ -119,7 +121,7 @@ void MQTT_Disconnect(MQTT_Client* mqttClient);
bool MQTT_Subscribe(MQTT_Client* client, char* topic, uint8_t qos); bool MQTT_Subscribe(MQTT_Client* client, char* topic, uint8_t qos);
// Publish a message // Publish a message
bool MQTT_Publish(MQTT_Client* client, const char* topic, const char* data, bool MQTT_Publish(MQTT_Client* client, const char* topic, const char* data, uint16_t data_len,
uint8_t qos, uint8_t retain); uint8_t qos, uint8_t retain);
// Callback when connected // Callback when connected

@ -13,74 +13,51 @@
#define DBG(format, ...) do { } while(0) #define DBG(format, ...) do { } while(0)
#endif #endif
// if MQTT_1_CLIENT is defined we only support the one client that is built into esp-link.
// this keeps everything simpler. Undefining it brings back old code that supports creating
// a new client and setting all its params. Most likely that old code no longer works...
#define MQTT_1_CLIENT
// callbacks to the attached uC
uint32_t connectedCb = 0, disconnectCb = 0, publishedCb = 0, dataCb = 0;
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
cmdMqttConnectedCb(uint32_t* args) { cmdMqttConnectedCb(MQTT_Client* client) {
MQTT_Client* client = (MQTT_Client*)args;
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Connected connectedCb=%p, disconnectedCb=%p, publishedCb=%p, dataCb=%p\n", DBG("MQTT: Connected Cb=%p\n", (void*)cb->connectedCb);
(void*)cb->connectedCb, cmdResponseStart(CMD_RESP_CB, cb->connectedCb, 0);
(void*)cb->disconnectedCb, cmdResponseEnd();
(void*)cb->publishedCb,
(void*)cb->dataCb);
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->connectedCb, 0, 0);
CMD_ResponseEnd(crc);
} }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
cmdMqttDisconnectedCb(uint32_t* args) { cmdMqttDisconnectedCb(MQTT_Client* client) {
MQTT_Client* client = (MQTT_Client*)args;
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Disconnected\n"); DBG("MQTT: Disconnected cb=%p\n", (void*)cb->disconnectedCb);
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->disconnectedCb, 0, 0); cmdResponseStart(CMD_RESP_CB, cb->disconnectedCb, 0);
CMD_ResponseEnd(crc); cmdResponseEnd();
} }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
cmdMqttPublishedCb(uint32_t* args) { cmdMqttPublishedCb(MQTT_Client* client) {
MQTT_Client* client = (MQTT_Client*)args;
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Published\n"); DBG("MQTT: Published cb=%p\n", (void*)cb->publishedCb);
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->publishedCb, 0, 0); cmdResponseStart(CMD_RESP_CB, cb->publishedCb, 0);
CMD_ResponseEnd(crc); cmdResponseEnd();
} }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
cmdMqttDataCb(uint32_t* args, const char* topic, uint32_t topic_len, const char* data, uint32_t data_len) { cmdMqttDataCb(MQTT_Client* client, const char* topic, uint32_t topic_len,
uint16_t crc = 0; const char* data, uint32_t data_len)
MQTT_Client* client = (MQTT_Client*)args; {
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Data cb=%p topic=%s len=%ld\n", (void*)cb->dataCb, topic, data_len);
crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->dataCb, 0, 2); cmdResponseStart(CMD_RESP_CB, cb->dataCb, 2);
crc = CMD_ResponseBody(crc, (uint8_t*)topic, topic_len); cmdResponseBody(topic, topic_len);
crc = CMD_ResponseBody(crc, (uint8_t*)data, data_len); cmdResponseBody(data, data_len);
CMD_ResponseEnd(crc); cmdResponseEnd();
} }
uint32_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
MQTTCMD_Lwt(CmdPacket *cmd) { MQTTCMD_Lwt(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
if (CMD_GetArgc(&req) != 5) if (cmdGetArgc(&req) != 4) return;
return 0;
// get mqtt client
uint32_t client_ptr;
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4);
#ifdef MQTT_1_CLIENT
MQTT_Client* client = &mqttClient; MQTT_Client* client = &mqttClient;
#else
MQTT_Client* client = (MQTT_Client*)client_ptr;
DBG("MQTT: MQTTCMD_Lwt client ptr=%p\n", (void*)client_ptr);
#endif
// free old topic & message // free old topic & message
if (client->connect_info.will_topic) if (client->connect_info.will_topic)
@ -91,24 +68,24 @@ MQTTCMD_Lwt(CmdPacket *cmd) {
uint16_t len; uint16_t len;
// get topic // get topic
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 128) return 0; // safety check if (len > 128) return; // safety check
client->connect_info.will_topic = (char*)os_zalloc(len + 1); client->connect_info.will_topic = (char*)os_zalloc(len + 1);
CMD_PopArg(&req, client->connect_info.will_topic, len); cmdPopArg(&req, client->connect_info.will_topic, len);
client->connect_info.will_topic[len] = 0; client->connect_info.will_topic[len] = 0;
// get message // get message
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 128) return 0; // safety check if (len > 128) return; // safety check
client->connect_info.will_message = (char*)os_zalloc(len + 1); client->connect_info.will_message = (char*)os_zalloc(len + 1);
CMD_PopArg(&req, client->connect_info.will_message, len); cmdPopArg(&req, client->connect_info.will_message, len);
client->connect_info.will_message[len] = 0; client->connect_info.will_message[len] = 0;
// get qos // get qos
CMD_PopArg(&req, (uint8_t*)&client->connect_info.will_qos, 4); cmdPopArg(&req, (uint8_t*)&client->connect_info.will_qos, 4);
// get retain // get retain
CMD_PopArg(&req, (uint8_t*)&client->connect_info.will_retain, 4); cmdPopArg(&req, (uint8_t*)&client->connect_info.will_retain, 4);
DBG("MQTT: MQTTCMD_Lwt topic=%s, message=%s, qos=%d, retain=%d\n", DBG("MQTT: MQTTCMD_Lwt topic=%s, message=%s, qos=%d, retain=%d\n",
client->connect_info.will_topic, client->connect_info.will_topic,
@ -118,122 +95,97 @@ MQTTCMD_Lwt(CmdPacket *cmd) {
// trigger a reconnect to set the LWT // trigger a reconnect to set the LWT
MQTT_Reconnect(client); MQTT_Reconnect(client);
return 1;
} }
uint32_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
MQTTCMD_Publish(CmdPacket *cmd) { MQTTCMD_Publish(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
if (CMD_GetArgc(&req) != 6) if (cmdGetArgc(&req) != 5) return;
return 0;
// get mqtt client
uint32_t client_ptr;
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4);
#ifdef MQTT_1_CLIENT
MQTT_Client* client = &mqttClient; MQTT_Client* client = &mqttClient;
#else
MQTT_Client* client = (MQTT_Client*)client_ptr;
DBG("MQTT: MQTTCMD_Publish client ptr=%p\n", (void*)client_ptr);
#endif
uint16_t len; uint16_t len;
// get topic // get topic
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 128) return 0; // safety check if (len > 128) return; // safety check
uint8_t *topic = (uint8_t*)os_zalloc(len + 1); uint8_t *topic = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, topic, len); cmdPopArg(&req, topic, len);
topic[len] = 0; topic[len] = 0;
// get data // get data
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
uint8_t *data = (uint8_t*)os_zalloc(len+1); uint8_t *data = (uint8_t*)os_zalloc(len+1);
if (!data) { // safety check if (!data) { // safety check
os_free(topic); os_free(topic);
return 0; return;
} }
CMD_PopArg(&req, data, len); cmdPopArg(&req, data, len);
data[len] = 0; data[len] = 0;
uint32_t qos, retain, data_len; uint16_t data_len;
uint8_t qos, retain;
// get data length // get data length
// this isn't used but we have to pull it off the stack cmdPopArg(&req, &data_len, sizeof(data_len));
CMD_PopArg(&req, (uint8_t*)&data_len, 4);
// get qos // get qos
CMD_PopArg(&req, (uint8_t*)&qos, 4); cmdPopArg(&req, &qos, sizeof(qos));
// get retain // get retain
CMD_PopArg(&req, (uint8_t*)&retain, 4); cmdPopArg(&req, &retain, sizeof(retain));
DBG("MQTT: MQTTCMD_Publish topic=%s, data_len=%d, qos=%ld, retain=%ld\n", DBG("MQTT: MQTTCMD_Publish topic=%s, data_len=%d, qos=%d, retain=%d\n",
topic, topic, data_len, qos, retain);
os_strlen((char*)data),
qos,
retain);
MQTT_Publish(client, (char*)topic, (char*)data, (uint8_t)qos, (uint8_t)retain); MQTT_Publish(client, (char*)topic, (char*)data, data_len, qos%3, retain&1);
os_free(topic); os_free(topic);
os_free(data); os_free(data);
return 1; return;
} }
uint32_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
MQTTCMD_Subscribe(CmdPacket *cmd) { MQTTCMD_Subscribe(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
if (CMD_GetArgc(&req) != 3) if (cmdGetArgc(&req) != 2) return;
return 0;
// get mqtt client
uint32_t client_ptr;
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4);
#ifdef MQTT_1_CLIENT
MQTT_Client* client = &mqttClient; MQTT_Client* client = &mqttClient;
#else
MQTT_Client* client = (MQTT_Client*)client_ptr;
DBG("MQTT: MQTTCMD_Subscribe client ptr=%p\n", (void*)client_ptr);
#endif
uint16_t len; uint16_t len;
// get topic // get topic
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 128) return 0; // safety check if (len > 128) return; // safety check
uint8_t* topic = (uint8_t*)os_zalloc(len + 1); uint8_t* topic = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, topic, len); cmdPopArg(&req, topic, len);
topic[len] = 0; topic[len] = 0;
// get qos // get qos
uint32_t qos = 0; uint32_t qos = 0;
CMD_PopArg(&req, (uint8_t*)&qos, 4); cmdPopArg(&req, (uint8_t*)&qos, 4);
DBG("MQTT: MQTTCMD_Subscribe topic=%s, qos=%ld\n", topic, qos); DBG("MQTT: MQTTCMD_Subscribe topic=%s, qos=%ld\n", topic, qos);
MQTT_Subscribe(client, (char*)topic, (uint8_t)qos); MQTT_Subscribe(client, (char*)topic, (uint8_t)qos);
os_free(topic); os_free(topic);
return 1; return;
} }
uint32_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
MQTTCMD_Setup(CmdPacket *cmd) { MQTTCMD_Setup(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
#ifdef MQTT_1_CLIENT
MQTT_Client* client = &mqttClient; MQTT_Client* client = &mqttClient;
CMD_SkipArg(&req);
CMD_SkipArg(&req); if (cmdGetArgc(&req) != 4) return;
CMD_SkipArg(&req);
CMD_SkipArg(&req); #if 0
CMD_SkipArg(&req); if (cmdGetArgc(&req) != 9)
#else
if (CMD_GetArgc(&req) != 9)
return 0; return 0;
// create mqtt client // create mqtt client
@ -247,31 +199,31 @@ MQTTCMD_Setup(CmdPacket *cmd) {
uint32_t keepalive, clean_session; uint32_t keepalive, clean_session;
// get client id // get client id
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 32) return 0; // safety check if (len > 32) return 0; // safety check
client_id = (uint8_t*)os_zalloc(len + 1); client_id = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, client_id, len); cmdPopArg(&req, client_id, len);
client_id[len] = 0; client_id[len] = 0;
// get username // get username
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 32) return 0; // safety check if (len > 32) return 0; // safety check
user_data = (uint8_t*)os_zalloc(len + 1); user_data = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, user_data, len); cmdPopArg(&req, user_data, len);
user_data[len] = 0; user_data[len] = 0;
// get password // get password
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 32) return 0; // safety check if (len > 32) return 0; // safety check
pass_data = (uint8_t*)os_zalloc(len + 1); pass_data = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, pass_data, len); cmdPopArg(&req, pass_data, len);
pass_data[len] = 0; pass_data[len] = 0;
// get keepalive // get keepalive
CMD_PopArg(&req, (uint8_t*)&keepalive, 4); cmdPopArg(&req, (uint8_t*)&keepalive, 4);
// get clean session // get clean session
CMD_PopArg(&req, (uint8_t*)&clean_session, 4); cmdPopArg(&req, (uint8_t*)&clean_session, 4);
#ifdef MQTTCMD_DBG #ifdef MQTTCMD_DBG
DBG("MQTT: MQTTCMD_Setup clientid=%s, user=%s, pw=%s, keepalive=%ld, clean_session=%ld\n", client_id, user_data, pass_data, keepalive, clean_session); DBG("MQTT: MQTTCMD_Setup clientid=%s, user=%s, pw=%s, keepalive=%ld, clean_session=%ld\n", client_id, user_data, pass_data, keepalive, clean_session);
#endif #endif
@ -287,31 +239,32 @@ MQTTCMD_Setup(CmdPacket *cmd) {
// create callback // create callback
MqttCmdCb* callback = (MqttCmdCb*)os_zalloc(sizeof(MqttCmdCb)); MqttCmdCb* callback = (MqttCmdCb*)os_zalloc(sizeof(MqttCmdCb));
uint32_t cb_data; cmdPopArg(&req, &callback->connectedCb, 4);
cmdPopArg(&req, &callback->disconnectedCb, 4);
CMD_PopArg(&req, (uint8_t*)&cb_data, 4); cmdPopArg(&req, &callback->publishedCb, 4);
callback->connectedCb = cb_data; cmdPopArg(&req, &callback->dataCb, 4);
CMD_PopArg(&req, (uint8_t*)&cb_data, 4);
callback->disconnectedCb = cb_data;
CMD_PopArg(&req, (uint8_t*)&cb_data, 4);
callback->publishedCb = cb_data;
CMD_PopArg(&req, (uint8_t*)&cb_data, 4);
callback->dataCb = cb_data;
client->user_data = callback; client->user_data = callback;
DBG("MQTT connectedCb=%lx\n", callback->connectedCb);
client->cmdConnectedCb = cmdMqttConnectedCb; client->cmdConnectedCb = cmdMqttConnectedCb;
client->cmdDisconnectedCb = cmdMqttDisconnectedCb; client->cmdDisconnectedCb = cmdMqttDisconnectedCb;
client->cmdPublishedCb = cmdMqttPublishedCb; client->cmdPublishedCb = cmdMqttPublishedCb;
client->cmdDataCb = cmdMqttDataCb; client->cmdDataCb = cmdMqttDataCb;
return 0xf00df00d; //(uint32_t)client; if (client->connState == MQTT_CONNECTED) {
if (callback->connectedCb)
cmdMqttConnectedCb(client);
} else if (callback->disconnectedCb) {
cmdMqttDisconnectedCb(client);
}
} }
#if 0
uint32_t ICACHE_FLASH_ATTR uint32_t ICACHE_FLASH_ATTR
MQTTCMD_Connect(CmdPacket *cmd) { MQTTCMD_Connect(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
#ifdef MQTT_1_CLIENT #ifdef MQTT_1_CLIENT
@ -325,12 +278,12 @@ MQTTCMD_Connect(CmdPacket *cmd) {
return 1; return 1;
#else #else
if (CMD_GetArgc(&req) != 4) if (cmdGetArgc(&req) != 4)
return 0; return 0;
// get mqtt client // get mqtt client
uint32_t client_ptr; uint32_t client_ptr;
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); cmdPopArg(&req, (uint8_t*)&client_ptr, 4);
MQTT_Client* client = (MQTT_Client*)client_ptr; MQTT_Client* client = (MQTT_Client*)client_ptr;
DBG("MQTT: MQTTCMD_Connect client ptr=%p\n", (void*)client_ptr); DBG("MQTT: MQTTCMD_Connect client ptr=%p\n", (void*)client_ptr);
@ -339,17 +292,17 @@ MQTTCMD_Connect(CmdPacket *cmd) {
// get host // get host
if (client->host) if (client->host)
os_free(client->host); os_free(client->host);
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 128) return 0; // safety check if (len > 128) return 0; // safety check
client->host = (char*)os_zalloc(len + 1); client->host = (char*)os_zalloc(len + 1);
CMD_PopArg(&req, client->host, len); cmdPopArg(&req, client->host, len);
client->host[len] = 0; client->host[len] = 0;
// get port // get port
CMD_PopArg(&req, (uint8_t*)&client->port, 4); cmdPopArg(&req, (uint8_t*)&client->port, 4);
// get security // get security
CMD_PopArg(&req, (uint8_t*)&client->security, 4); cmdPopArg(&req, (uint8_t*)&client->security, 4);
DBG("MQTT: MQTTCMD_Connect host=%s, port=%d, security=%d\n", DBG("MQTT: MQTTCMD_Connect host=%s, port=%d, security=%d\n",
client->host, client->host,
client->port, client->port,
@ -363,18 +316,18 @@ MQTTCMD_Connect(CmdPacket *cmd) {
uint32_t ICACHE_FLASH_ATTR uint32_t ICACHE_FLASH_ATTR
MQTTCMD_Disconnect(CmdPacket *cmd) { MQTTCMD_Disconnect(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
#ifdef MQTT_1_CLIENT #ifdef MQTT_1_CLIENT
return 1; return 1;
#else #else
if (CMD_GetArgc(&req) != 1) if (cmdGetArgc(&req) != 1)
return 0; return 0;
// get mqtt client // get mqtt client
uint32_t client_ptr; uint32_t client_ptr;
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); cmdPopArg(&req, (uint8_t*)&client_ptr, 4);
MQTT_Client* client = (MQTT_Client*)client_ptr; MQTT_Client* client = (MQTT_Client*)client_ptr;
DBG("MQTT: MQTTCMD_Disconnect client ptr=%p\n", (void*)client_ptr); DBG("MQTT: MQTTCMD_Disconnect client ptr=%p\n", (void*)client_ptr);
@ -383,3 +336,4 @@ MQTTCMD_Disconnect(CmdPacket *cmd) {
return 1; return 1;
#endif #endif
} }
#endif

@ -10,11 +10,11 @@ typedef struct {
uint32_t dataCb; uint32_t dataCb;
} MqttCmdCb; } MqttCmdCb;
uint32_t MQTTCMD_Connect(CmdPacket *cmd); void MQTTCMD_Connect(CmdPacket *cmd);
uint32_t MQTTCMD_Disconnect(CmdPacket *cmd); void MQTTCMD_Disconnect(CmdPacket *cmd);
uint32_t MQTTCMD_Setup(CmdPacket *cmd); void MQTTCMD_Setup(CmdPacket *cmd);
uint32_t MQTTCMD_Publish(CmdPacket *cmd); void MQTTCMD_Publish(CmdPacket *cmd);
uint32_t MQTTCMD_Subscribe(CmdPacket *cmd); void MQTTCMD_Subscribe(CmdPacket *cmd);
uint32_t MQTTCMD_Lwt(CmdPacket *cmd); void MQTTCMD_Lwt(CmdPacket *cmd);
#endif /* MODULES_MQTT_CMD_H_ */ #endif /* MODULES_MQTT_CMD_H_ */

@ -3,6 +3,8 @@
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh // Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh
#include "esp8266.h" #include "esp8266.h"
#include "c_types.h"
#include "ip_addr.h"
#include "rest.h" #include "rest.h"
#include "cmd.h" #include "cmd.h"
@ -12,6 +14,27 @@
#define DBG(format, ...) do { } while(0) #define DBG(format, ...) do { } while(0)
#endif #endif
typedef enum {
HEADER_GENERIC = 0,
HEADER_CONTENT_TYPE,
HEADER_USER_AGENT
} HEADER_TYPE;
typedef struct {
char *host;
uint32_t port;
uint32_t security;
ip_addr_t ip;
struct espconn *pCon;
char *header;
char *data;
uint16_t data_len;
uint16_t data_sent;
char *content_type;
char *user_agent;
uint32_t resp_cb;
} RestClient;
// Connection pool for REST clients. Attached MCU's just call REST_setup and this allocates // Connection pool for REST clients. Attached MCU's just call REST_setup and this allocates
// a connection, They never call any 'free' and given that the attached MCU could restart at // a connection, They never call any 'free' and given that the attached MCU could restart at
@ -32,7 +55,7 @@ tcpclient_recv(void *arg, char *pdata, unsigned short len) {
// parse status line // parse status line
int pi = 0; int pi = 0;
int32_t code = -1; int16_t code = -1;
char statusCode[4] = "\0\0\0\0"; char statusCode[4] = "\0\0\0\0";
int statusLen = 0; int statusLen = 0;
bool inStatus = false; bool inStatus = false;
@ -69,15 +92,17 @@ tcpclient_recv(void *arg, char *pdata, unsigned short len) {
//if (pi < len && pdata[pi] == '\r') pi++; // hacky! //if (pi < len && pdata[pi] == '\r') pi++; // hacky!
// collect body and send it // collect body and send it
uint16_t crc;
int body_len = len-pi; int body_len = len-pi;
DBG("REST: status=%ld, body=%d\n", code, body_len); DBG("REST: status=%ld, body=%d\n", code, body_len);
if (pi == len) { if (pi == len) {
crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 0); cmdResponseStart(CMD_RESP_CB, client->resp_cb, 1);
cmdResponseBody(&code, sizeof(code));
cmdResponseEnd();
} else { } else {
crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 1); cmdResponseStart(CMD_RESP_CB, client->resp_cb, 2);
crc = CMD_ResponseBody(crc, (uint8_t*)(pdata+pi), body_len); cmdResponseBody(&code, sizeof(code));
CMD_ResponseEnd(crc); cmdResponseBody(pdata+pi, body_len>100?100:body_len);
cmdResponseEnd();
#if 0 #if 0
os_printf("REST: body="); os_printf("REST: body=");
for (int j=pi; j<len; j++) os_printf(" %02x", pdata[j]); for (int j=pi; j<len; j++) os_printf(" %02x", pdata[j]);
@ -89,7 +114,6 @@ tcpclient_recv(void *arg, char *pdata, unsigned short len) {
// espconn_secure_disconnect(client->pCon); // espconn_secure_disconnect(client->pCon);
//else //else
espconn_disconnect(client->pCon); espconn_disconnect(client->pCon);
} }
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
@ -173,33 +197,39 @@ rest_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) {
} }
} }
uint32_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
REST_Setup(CmdPacket *cmd) { REST_Setup(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
uint32_t port, security; uint32_t port, security;
int32_t err = -1; // error code in case of failure
// start parsing the command // start parsing the command
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
if(CMD_GetArgc(&req) != 3) return 0; if(cmdGetArgc(&req) != 3) goto fail;
err--;
// get the hostname // get the hostname
uint16_t len = CMD_ArgLen(&req); uint16_t len = cmdArgLen(&req);
if (len > 128) return 0; // safety check if (len > 128) goto fail; // safety check
err--;
uint8_t *rest_host = (uint8_t*)os_zalloc(len + 1); uint8_t *rest_host = (uint8_t*)os_zalloc(len + 1);
if (CMD_PopArg(&req, rest_host, len)) return 0; if (cmdPopArg(&req, rest_host, len)) goto fail;
err--;
rest_host[len] = 0; rest_host[len] = 0;
// get the port // get the port
if (CMD_PopArg(&req, (uint8_t*)&port, 4)) { if (cmdPopArg(&req, (uint8_t*)&port, 2)) {
os_free(rest_host); os_free(rest_host);
return 0; goto fail;
} }
err--;
// get the security mode // get the security mode
if (CMD_PopArg(&req, (uint8_t*)&security, 4)) { if (cmdPopArg(&req, (uint8_t*)&security, 1)) {
os_free(rest_host); os_free(rest_host);
return 0; goto fail;
} }
err--;
// clear connection structures the first time // clear connection structures the first time
if (restNum == 0xff) { if (restNum == 0xff) {
@ -224,7 +254,7 @@ REST_Setup(CmdPacket *cmd) {
os_memset(client, 0, sizeof(RestClient)); os_memset(client, 0, sizeof(RestClient));
DBG("REST: setup #%d host=%s port=%ld security=%ld\n", clientNum, rest_host, port, security); DBG("REST: setup #%d host=%s port=%ld security=%ld\n", clientNum, rest_host, port, security);
client->resp_cb = cmd->callback; client->resp_cb = cmd->value;
client->host = (char *)rest_host; client->host = (char *)rest_host;
client->port = port; client->port = port;
@ -249,35 +279,39 @@ REST_Setup(CmdPacket *cmd) {
client->pCon->reverse = client; client->pCon->reverse = client;
return REST_CB | (uint32_t)clientNum; cmdResponseStart(CMD_RESP_V, clientNum, 0);
cmdResponseEnd();
return;
fail:
cmdResponseStart(CMD_RESP_V, err, 0);
cmdResponseEnd();
return;
} }
uint32_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
REST_SetHeader(CmdPacket *cmd) { REST_SetHeader(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
if(CMD_GetArgc(&req) != 3) if(cmdGetArgc(&req) != 2) return;
return 0;
// Get client // Get client
uint32_t clientNum; uint32_t clientNum = cmd->value;
if (CMD_PopArg(&req, (uint8_t*)&clientNum, 4)) return 0; RestClient *client = restClient + (clientNum % MAX_REST);
if ((clientNum & 0xffff0000) != REST_CB) return 0;
RestClient *client = restClient + ((clientNum & 0xffff) % MAX_REST);
// Get header selector // Get header selector
uint32_t header_index; uint32_t header_index;
if (CMD_PopArg(&req, (uint8_t*)&header_index, 4)) return 0; if (cmdPopArg(&req, (uint8_t*)&header_index, 4)) return;
// Get header value // Get header value
uint16_t len = CMD_ArgLen(&req); uint16_t len = cmdArgLen(&req);
if (len > 256) return 0; //safety check if (len > 256) return; //safety check
switch(header_index) { switch(header_index) {
case HEADER_GENERIC: case HEADER_GENERIC:
if(client->header) os_free(client->header); if(client->header) os_free(client->header);
client->header = (char*)os_zalloc(len + 3); client->header = (char*)os_zalloc(len + 3);
CMD_PopArg(&req, (uint8_t*)client->header, len); cmdPopArg(&req, (uint8_t*)client->header, len);
client->header[len] = '\r'; client->header[len] = '\r';
client->header[len+1] = '\n'; client->header[len+1] = '\n';
client->header[len+2] = 0; client->header[len+2] = 0;
@ -286,7 +320,7 @@ REST_SetHeader(CmdPacket *cmd) {
case HEADER_CONTENT_TYPE: case HEADER_CONTENT_TYPE:
if(client->content_type) os_free(client->content_type); if(client->content_type) os_free(client->content_type);
client->content_type = (char*)os_zalloc(len + 3); client->content_type = (char*)os_zalloc(len + 3);
CMD_PopArg(&req, (uint8_t*)client->content_type, len); cmdPopArg(&req, (uint8_t*)client->content_type, len);
client->content_type[len] = '\r'; client->content_type[len] = '\r';
client->content_type[len+1] = '\n'; client->content_type[len+1] = '\n';
client->content_type[len+2] = 0; client->content_type[len+2] = 0;
@ -295,52 +329,50 @@ REST_SetHeader(CmdPacket *cmd) {
case HEADER_USER_AGENT: case HEADER_USER_AGENT:
if(client->user_agent) os_free(client->user_agent); if(client->user_agent) os_free(client->user_agent);
client->user_agent = (char*)os_zalloc(len + 3); client->user_agent = (char*)os_zalloc(len + 3);
CMD_PopArg(&req, (uint8_t*)client->user_agent, len); cmdPopArg(&req, (uint8_t*)client->user_agent, len);
client->user_agent[len] = '\r'; client->user_agent[len] = '\r';
client->user_agent[len+1] = '\n'; client->user_agent[len+1] = '\n';
client->user_agent[len+2] = 0; client->user_agent[len+2] = 0;
DBG("REST: Set user_agent: %s\r\n", client->user_agent); DBG("REST: Set user_agent: %s\r\n", client->user_agent);
break; break;
} }
return 1;
} }
uint32_t ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
REST_Request(CmdPacket *cmd) { REST_Request(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
CMD_Request(&req, cmd); cmdRequest(&req, cmd);
DBG("REST: request"); DBG("REST: request");
if (cmd->argc != 2 && cmd->argc != 3) return;
// Get client // Get client
uint32_t clientNum; uint32_t clientNum = cmd->value;
if (CMD_PopArg(&req, (uint8_t*)&clientNum, 4)) goto fail; RestClient *client = restClient + (clientNum % MAX_REST);
if ((clientNum & 0xffff0000) != REST_CB) goto fail;
clientNum &= 0xffff;
RestClient *client = restClient + clientNum % MAX_REST;
DBG(" #%ld", clientNum); DBG(" #%ld", clientNum);
// Get HTTP method // Get HTTP method
uint16_t len = CMD_ArgLen(&req); uint16_t len = cmdArgLen(&req);
if (len > 15) goto fail; if (len > 15) goto fail;
char method[16]; char method[16];
CMD_PopArg(&req, method, len); cmdPopArg(&req, method, len);
method[len] = 0; method[len] = 0;
DBG(" method=%s", method); DBG(" method=%s", method);
// Get HTTP path // Get HTTP path
len = CMD_ArgLen(&req); len = cmdArgLen(&req);
if (len > 1023) goto fail; if (len > 1023) goto fail;
char path[1024]; char path[1024];
CMD_PopArg(&req, path, len); cmdPopArg(&req, path, len);
path[len] = 0; path[len] = 0;
DBG(" path=%s", path); DBG(" path=%s", path);
// Get HTTP body // Get HTTP body
uint32_t realLen = 0; uint32_t realLen = 0;
if (CMD_GetArgc(&req) == 3) { if (cmdGetArgc(&req) == 2) {
realLen = 0; realLen = 0;
len = 0;
} else { } else {
CMD_PopArg(&req, (uint8_t*)&realLen, 4); realLen = cmdArgLen(&req);
if (realLen > 2048) goto fail;
len = CMD_ArgLen(&req);
if (len > 2048 || realLen > len) goto fail;
} }
DBG(" bodyLen=%ld", realLen); DBG(" bodyLen=%ld", realLen);
@ -367,14 +399,14 @@ REST_Request(CmdPacket *cmd) {
DBG(" hdrLen=%d", client->data_len); DBG(" hdrLen=%d", client->data_len);
if (realLen > 0) { if (realLen > 0) {
CMD_PopArg(&req, client->data + client->data_len, realLen); cmdPopArg(&req, client->data + client->data_len, realLen);
client->data_len += realLen; client->data_len += realLen;
} }
DBG("\n"); DBG("\n");
//DBG("REST request: %s", (char*)client->data); //DBG("REST request: %s", (char*)client->data);
DBG("REST: pCon state=%d\n", client->pCon->state); //DBG("REST: pCon state=%d\n", client->pCon->state);
client->pCon->state = ESPCONN_NONE; client->pCon->state = ESPCONN_NONE;
espconn_regist_connectcb(client->pCon, tcpclient_connect_cb); espconn_regist_connectcb(client->pCon, tcpclient_connect_cb);
espconn_regist_reconcb(client->pCon, tcpclient_recon_cb); espconn_regist_reconcb(client->pCon, tcpclient_recon_cb);
@ -392,9 +424,8 @@ REST_Request(CmdPacket *cmd) {
espconn_gethostbyname(client->pCon, (char *)client->host, &client->ip, rest_dns_found); espconn_gethostbyname(client->pCon, (char *)client->host, &client->ip, rest_dns_found);
} }
return 1; return;
fail: fail:
DBG("\n"); DBG("\n");
return 0;
} }

@ -8,33 +8,10 @@
#ifndef MODULES_API_H_ #ifndef MODULES_API_H_
#define MODULES_API_H_ #define MODULES_API_H_
#include "c_types.h"
#include "ip_addr.h"
#include "cmd.h" #include "cmd.h"
typedef enum { void REST_Setup(CmdPacket *cmd);
HEADER_GENERIC = 0, void REST_Request(CmdPacket *cmd);
HEADER_CONTENT_TYPE, void REST_SetHeader(CmdPacket *cmd);
HEADER_USER_AGENT
} HEADER_TYPE;
typedef struct {
char *host;
uint32_t port;
uint32_t security;
ip_addr_t ip;
struct espconn *pCon;
char *header;
char *data;
uint16_t data_len;
uint16_t data_sent;
char *content_type;
char *user_agent;
uint32_t resp_cb;
} RestClient;
uint32_t REST_Setup(CmdPacket *cmd);
uint32_t REST_Request(CmdPacket *cmd);
uint32_t REST_SetHeader(CmdPacket *cmd);
#endif /* MODULES_INCLUDE_API_H_ */ #endif /* MODULES_INCLUDE_API_H_ */

@ -445,11 +445,11 @@ serbridgeInitPins()
#endif #endif
if (flashConfig.swap_uart) { if (flashConfig.swap_uart) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4); PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4); // RX
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4); PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4); // TX
PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U); PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U);
if (flashConfig.rx_pullup) PIN_PULLUP_EN(PERIPHS_IO_MUX_MTDO_U); if (flashConfig.rx_pullup) PIN_PULLUP_EN(PERIPHS_IO_MUX_MTCK_U);
else PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U); else PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U);
system_uart_swap(); system_uart_swap();
} else { } else {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0); PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0);

@ -17,8 +17,7 @@ uint8_t slip_disabled; // temporarily disable slip to allow flashing of attach
extern void ICACHE_FLASH_ATTR console_process(char *buf, short len); extern void ICACHE_FLASH_ATTR console_process(char *buf, short len);
// This SLIP parser does not conform to RFC 1055 https://tools.ietf.org/html/rfc1055, // This SLIP parser tries to conform to RFC 1055 https://tools.ietf.org/html/rfc1055.
// instead, it implements the framing implemented in https://github.com/tuanpmt/esp_bridge
// It accumulates each packet into a static buffer and calls cmd_parse() when the end // It accumulates each packet into a static buffer and calls cmd_parse() when the end
// of a packet is reached. It expects cmd_parse() to copy anything it needs from the // of a packet is reached. It expects cmd_parse() to copy anything it needs from the
// buffer elsewhere as the buffer is immediately reused. // buffer elsewhere as the buffer is immediately reused.
@ -38,27 +37,20 @@ static short slip_len; // accumulated length in slip_buf
// SLIP process a packet or a bunch of debug console chars // SLIP process a packet or a bunch of debug console chars
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
slip_process() { slip_process() {
if (slip_len < 1) return; if (slip_len > 2) {
if (!slip_inpkt) {
// debug console stuff
console_process(slip_buf, slip_len);
} else {
// proper SLIP packet, invoke command processor after checking CRC // proper SLIP packet, invoke command processor after checking CRC
//os_printf("SLIP: rcv %d\n", slip_len); //os_printf("SLIP: rcv %d\n", slip_len);
if (slip_len > 2) {
uint16_t crc = crc16_data((uint8_t*)slip_buf, slip_len-2, 0); uint16_t crc = crc16_data((uint8_t*)slip_buf, slip_len-2, 0);
uint16_t rcv = ((uint16_t)slip_buf[slip_len-2]) | ((uint16_t)slip_buf[slip_len-1] << 8); uint16_t rcv = ((uint16_t)slip_buf[slip_len-2]) | ((uint16_t)slip_buf[slip_len-1] << 8);
if (crc == rcv) { if (crc == rcv) {
CMD_parse_packet((uint8_t*)slip_buf, slip_len-2); cmdParsePacket((uint8_t*)slip_buf, slip_len-2);
} else { } else {
os_printf("SLIP: bad CRC, crc=%x rcv=%x\n", crc, rcv); os_printf("SLIP: bad CRC, crc=%04x rcv=%04x len=%d\n", crc, rcv, slip_len);
for (short i=0; i<slip_len; i++) { for (short i=0; i<slip_len; i++) {
if (slip_buf[i] >= ' ' && slip_buf[i] <= '~') { if (slip_buf[i] >= ' ' && slip_buf[i] <= '~') {
DBG("%c", slip_buf[i]); DBG("%c", slip_buf[i]);
} } else {
else {
DBG("\\%02X", slip_buf[i]); DBG("\\%02X", slip_buf[i]);
} }
} }
@ -66,20 +58,17 @@ slip_process() {
} }
} }
} }
}
#if 0
// determine whether a character is printable or not (or \r \n) // determine whether a character is printable or not (or \r \n)
static bool ICACHE_FLASH_ATTR static bool ICACHE_FLASH_ATTR
slip_printable(char c) { slip_printable(char c) {
return (c >= ' ' && c <= '~') || c == '\n' || c == '\r'; return (c >= ' ' && c <= '~') || c == '\n' || c == '\r';
} }
#endif
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
slip_reset() { slip_reset() {
//os_printf("SLIP: reset\n"); //os_printf("SLIP: reset\n");
slip_inpkt = false; slip_inpkt = true;
slip_escaped = false; slip_escaped = false;
slip_len = 0; slip_len = 0;
} }
@ -87,39 +76,30 @@ slip_reset() {
// SLIP parse a single character // SLIP parse a single character
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
slip_parse_char(char c) { slip_parse_char(char c) {
if (!slip_inpkt) { if (c == SLIP_END) {
if (c == SLIP_START) { // either start or end of packet, process whatever we may have accumulated
if (slip_len > 0) console_process(slip_buf, slip_len); DBG("SLIP: start or end len=%d inpkt=%d\n", slip_len, slip_inpkt);
slip_reset(); if (slip_len > 0) {
slip_inpkt = true; if (slip_len > 2 && slip_inpkt) slip_process();
DBG("SLIP: start\n"); else console_process(slip_buf, slip_len);
return;
} }
slip_reset();
} else if (slip_escaped) { } else if (slip_escaped) {
// prev char was SLIP_REPL // prev char was SLIP_ESC
c = SLIP_ESC(c); if (c == SLIP_ESC_END) c = SLIP_END;
if (c == SLIP_ESC_ESC) c = SLIP_ESC;
if (slip_len < SLIP_MAX) slip_buf[slip_len++] = c;
slip_escaped = false; slip_escaped = false;
} else { } else if (slip_inpkt && c == SLIP_ESC) {
switch (c) {
case SLIP_REPL:
slip_escaped = true; slip_escaped = true;
return; } else {
case SLIP_END: if (slip_len == 1 && slip_printable(slip_buf[0]) && slip_printable(c)) {
// end of packet, process it and get ready for next one // start of packet and it's a printable character, we're gonna assume that this is console text
if (slip_len > 0) slip_process(); slip_inpkt = false;
slip_reset();
return;
case SLIP_START:
os_printf("SLIP: got SLIP_START while in packet?\n");
//os_printf("SLIP: rcv %d:", slip_len);
//for (int i=0; i<slip_len; i++) os_printf(" %02x", slip_buf[i]);
//os_printf("\n");
slip_reset();
return;
}
} }
if (slip_len < SLIP_MAX) slip_buf[slip_len++] = c; if (slip_len < SLIP_MAX) slip_buf[slip_len++] = c;
} }
}
// callback with a buffer of characters that have arrived on the uart // callback with a buffer of characters that have arrived on the uart
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
@ -130,8 +110,8 @@ slip_parse_buf(char *buf, short length) {
// if we're in-between packets (debug console) then print it now // if we're in-between packets (debug console) then print it now
if (!slip_inpkt && length > 0) { if (!slip_inpkt && length > 0) {
slip_process(); console_process(slip_buf, slip_len);
slip_reset(); slip_len = 0;
} }
} }

Loading…
Cancel
Save