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. 78
      Makefile
  2. 252
      README.adoc
  3. 130
      cmd/cmd.c
  4. 79
      cmd/cmd.h
  5. 163
      cmd/handlers.c
  6. 3
      esp-link/cgi.c
  7. 132
      esp-link/cgioptiboot.c
  8. 458
      esp-link/cgiwifi.c
  9. 3
      esp-link/cgiwifi.h
  10. 39
      esp-link/main.c
  11. 23
      esp-link/mqtt_client.c
  12. 2
      esp-link/status.c
  13. 14
      html/home.html
  14. 19
      html/mqtt.html
  15. 68
      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. 88
      serial/slip.c

@ -23,7 +23,33 @@ CFLAGS=
# mode trying to connect to the specified AP *only* if the flash wireless settings are empty!
# This happens on a full serial flash and avoids having to hunt for the AP...
# STA_SSID ?=
# STA_PASS ?=
# STA_PASS ?=
# The SOFTAP configuration can be hard-coded here, the minimum parameters to set are AP_SSID && AP_PASS
# The AP SSID has to be at least 8 characters long, same for AP PASSWORD
# The AP AUTH MODE can be set to:
# 0 = AUTH_OPEN,
# 1 = AUTH_WEP,
# 2 = AUTH_WPA_PSK,
# 3 = AUTH_WPA2_PSK,
# 4 = AUTH_WPA_WPA2_PSK
# SSID hidden default 0, ( 0 | 1 )
# Max connections default 4, ( 1 ~ 4 )
# Beacon interval default 100, ( 100 ~ 60000ms )
#
# AP_SSID ?=esp_link_test
# AP_PASS ?=esp_link_test
# AP_AUTH_MODE ?=4
# AP_SSID_HIDDEN ?=0
# AP_MAX_CONN ?=4
# AP_BEACON_INTERVAL ?=100
# If CHANGE_TO_STA is set to "yes" the esp-link module will switch to station mode
# once successfully connected to an access point. Else it will stay in STA+AP mode.
CHANGE_TO_STA ?= yes
# hostname or IP address for wifi flashing
ESP_HOSTNAME ?= esp-link
# --------------- toolchain configuration ---------------
@ -43,15 +69,6 @@ ESPTOOL ?= $(abspath ../esp-open-sdk/esptool/esptool.py)
ESPPORT ?= /dev/ttyUSB0
ESPBAUD ?= 460800
# The Wifi station configuration can be hard-coded here, which makes esp-link come up in STA+AP
# mode trying to connect to the specified AP *only* if the flash wireless settings are empty!
# This happens on a full serial flash and avoids having to hunt for the AP...
# STA_SSID ?=
# STA_PASS ?=
# hostname or IP address for wifi flashing
ESP_HOSTNAME ?= esp-link
# --------------- chipset configuration ---------------
# Pick your flash size: "512KB", "1MB", or "4MB"
@ -68,14 +85,9 @@ LED_CONN_PIN ?= 0
# GPIO pin used for "serial activity" LED, active low
LED_SERIAL_PIN ?= 14
# --------------- esp-link config options ---------------
# If CHANGE_TO_STA is set to "yes" the esp-link module will switch to station mode
# once successfully connected to an access point. Else it will stay in AP+STA mode.
# --------------- esp-link modules config options ---------------
CHANGE_TO_STA ?= yes
# Optional Modules
# Optional Modules mqtt
MODULES ?= mqtt rest syslog
# --------------- esphttpd config options ---------------
@ -270,6 +282,30 @@ ifneq ($(strip $(STA_PASS)),)
CFLAGS += -DSTA_PASS="$(STA_PASS)"
endif
ifneq ($(strip $(AP_SSID)),)
CFLAGS += -DAP_SSID="$(AP_SSID)"
endif
ifneq ($(strip $(AP_PASS)),)
CFLAGS += -DAP_PASS="$(AP_PASS)"
endif
ifneq ($(strip $(AP_AUTH_MODE)),)
CFLAGS += -DAP_AUTH_MODE="$(AP_AUTH_MODE)"
endif
ifneq ($(strip $(AP_SSID_HIDDEN)),)
CFLAGS += -DAP_SSID_HIDDEN="$(AP_SSID_HIDDEN)"
endif
ifneq ($(strip $(AP_MAX_CONN)),)
CFLAGS += -DAP_MAX_CONN="$(AP_MAX_CONN)"
endif
ifneq ($(strip $(AP_BEACON_INTERVAL)),)
CFLAGS += -DAP_BEACON_INTERVAL="$(AP_BEACON_INTERVAL)"
endif
ifeq ("$(GZIP_COMPRESSION)","yes")
CFLAGS += -DGZIP_COMPRESSION
endif
@ -352,15 +388,17 @@ flash: all
0x00000 "$(BOOTFILE)" 0x01000 $(FW_BASE)/user1.bin \
$(ET_BLANK) $(SDK_BASE)/bin/blank.bin
ifeq ($(OS),Windows_NT)
tools/$(HTML_COMPRESSOR):
$(Q) mkdir -p tools
ifeq ($(OS),Windows_NT)
cd tools; wget --no-check-certificate https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR) -O $(YUI_COMPRESSOR)
cd tools; wget --no-check-certificate https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) -O $(HTML_COMPRESSOR)
else
else
tools/$(HTML_COMPRESSOR):
$(Q) mkdir -p tools
cd tools; wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR)
cd tools; wget https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR)
endif
endif
ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes")
$(BUILD_BASE)/espfs_img.o: tools/$(HTML_COMPRESSOR)

@ -1,36 +1,75 @@
ESP-LINK
========
ESP-LINK: Wifi-Serial Bridge w/REST&MQTT
========================================
Thorsten von Eicken
:toc:
:toc-title!:
:toc-placement!:
This firmware connects an attached micro-controller to the internet using a ESP8266 Wifi module.
It implements a number of features:
[options="compact"]
- transparent bridge between Wifi and serial, useful for debugging or inputting into a uC
- flash-programming attached Arduino/AVR microcontrollers, esp8266 modules, as well as
LPC800-series and other ARM microcontrollers via Wifi
- built-in stk500v1 programmer for AVR uC's with optiboot: program using HTTP upload of hex file
- outbound TCP (and thus HTTP) connections from the attached micro-controller to the internet
- outbound REST HTTP requests from the attached micro-controller to the internet, protocol
based on espduino and compatible with [tuanpmt/espduino](https://github.com/tuanpmt/espduino)
- built-in stk500v1 programmer for AVR uC's: program using HTTP upload of hex file
- outbound REST HTTP requests from the attached micro-controller to the internet
- MQTT client pub/sub from the attached micro-controller to the internet
The firmware includes a tiny HTTP server based on
[esphttpd](http://www.esp8266.com/viewforum.php?f=34)
http://www.esp8266.com/viewforum.php?f=34[esphttpd]
with a simple web interface, many thanks to Jeroen Domburg for making it available!
The REST and MQTT functionality are loosely based on https://github.com/tuanpmt/espduino
but significantly reqritten and no longer protocol compatible, thanks to tuanpmt for the
inspiration!
Many thanks to https://github.com/brunnels for contributions in particular around the espduino
functionality. Thank you also to https://github.com/susisstrolch and https://github.com/bc547 for
additional contributions!
###[Releases & Downloads](https://github.com/jeelabs/esp-link/releases)
[float]
Table of Contents
-----------------
toc::[]
Releases & Downloads
--------------------
- [V2.1.7](https://github.com/jeelabs/esp-link/releases/tag/v2.1.7) is the most recent release.
- https://github.com/jeelabs/esp-link/releases/tag/v2.1.7[V2.1.7] is the most recent release.
It has the new built-in stk500v1 programmer and works on all modules (esp-01 through esp-12).
- [V2.2.beta1](https://github.com/jeelabs/esp-link/releases/tag/v2.2.beta1) will be coming
- https://github.com/jeelabs/esp-link/releases/tag/v2.2.beta2[V2.2.beta2] will be coming
up shortly with mDNS, sNTP, and syslog support, stay tuned...
- See https://github.com/jeelabs/esp-link/releases[all releases].
For quick support and questions chat at
image:https://badges.gitter.im/Join%20Chat.svg[link="https://gitter.im/jeelabs/esp-link"]
Intro
-----
### Esp-link goals
For quick support and questions:
[![Chat at https://gitter.im/jeelabs/esp-link](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jeelabs/esp-link?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
The goal of the esp-link project is to create an advanced Wifi co-processor. Esp-link assumes that
there is a "main processor" (also referred to as "attached uController") and that esp-link's role
is to facilitate communication over Wifi. Where esp-link is a bit unusual is that it's not really
just a Wifi interface or a slave co-processor. In some sense it's the master, because the main
processor can be reset, controlled and reprogrammed through esp-link. The three main areas of
functionality in esp-link are:
- reprogramming and debugging the attached uC
- letting the attached uC make outbound communication and offloading the protocol processing
- forwarding inbound communication and offloading the protocol processing (this part is the
least developed)
The goal of the project is also to remain focused on the above mission. In particular, esp-link
is not a platform for stand-alone applications and it does not support connecting sensors or
actuators directly to it. A few users have taken esp-link as a starting point for doing these
things and that's great, but there's also value in keeping the mainline esp-link project
focused on a clear mission.
### Esp-link uses
Esp-link uses
-------------
The simplest use of esp-link is as a transparent serial to wifi bridge. You can flash an attached
uC over wifi and you can watch the uC's serial debug output by connecting to port 23 or looking
at the uC Console web page.
@ -51,26 +90,31 @@ the attached uC registers callbacks at start-up such that the code in the esp do
know which exact sensors/actuators the attached uC has, it learns that through the initial
callback registration.
Eye Candy
---------
### Eye Candy
These screen shots show the Home page, the Wifi configuration page, the console for the
attached microcontroller, and the pin assignments card:
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/8261425/6ca395a6-167f-11e5-8e92-77150371135a.png">
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/8261427/6caf7326-167f-11e5-8085-bc8b20159b2b.png">
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/8261426/6ca7f75e-167f-11e5-827d-9a1c582ad05d.png">
<img width="30%" src="https://cloud.githubusercontent.com/assets/39480/8261658/11e6c64a-1681-11e5-82d0-ea5ec90a6ddb.png">
image:https://cloud.githubusercontent.com/assets/39480/8261425/6ca395a6-167f-11e5-8e92-77150371135a.png[width="45%"]
image:https://cloud.githubusercontent.com/assets/39480/8261427/6caf7326-167f-11e5-8085-bc8b20159b2b.png[width="45%"]
image:https://cloud.githubusercontent.com/assets/39480/8261426/6ca7f75e-167f-11e5-827d-9a1c582ad05d.png[width="45%"]
image:https://cloud.githubusercontent.com/assets/39480/8261658/11e6c64a-1681-11e5-82d0-ea5ec90a6ddb.png[width="45%"]
Getting Started
---------------
### Hardware configuration
Hardware info
-------------
This firmware is designed for any esp8266 module.
The recommended connections for an esp-01 module are:
- URXD: connect to TX of microcontroller
- UTXD: connect to RX of microcontroller
- GPIO0: connect to RESET of microcontroller
- GPIO2: optionally connect green LED to 3.3V (indicates wifi status)
The recommended connections for an esp-12 module are:
- URXD: connect to TX of microcontroller
- UTXD: connect to RX of microcontroller
- GPIO12: connect to RESET of microcontroller
@ -82,6 +126,7 @@ The recommended connections for an esp-12 module are:
If your application has problems with the boot message that is output at ~74600 baud by the ROM
at boot time you can connect an esp-12 module as follows and choose the "swap_uart" pin assignment
in the esp-link web interface:
- GPIO13: connect to TX of microcontroller
- GPIO15: connect to RX of microcontroller
- GPIO1/UTXD: connect to RESET of microcontroller
@ -95,10 +140,10 @@ seen both used, sigh).
The GPIO pin assignments can be changed dynamically in the web UI and are saved in flash.
Initial flashing
----------------
### Initial flashing
If you want to simply flash a pre-built firmware binary, you can download the latest
[release](https://github.com/jeelabs/esp-link/releases) and use your favorite
https://github.com/jeelabs/esp-link/releases[release] and use your favorite
ESP8266 flashing tool to flash the bootloader, the firmware, and blank settings.
Detailed instructions are provided in the release notes.
@ -107,16 +152,17 @@ stored in the boot sector (address 0). This is the standard way that the esp8266
the flash size. What this means is that you need to set this properly when you flash the bootloader.
If you use esptool.py you can do it using the -ff and -fs options.
Wifi configuration overview
------------------
### Wifi configuration overview
For proper operation the end state that esp-link needs to arrive at is to have it
join your pre-existing wifi network as a pure station.
However, in order to get there esp-link will start out as an access point and you'll have
to join its network to configure it. The short version is:
1. esp-link creates a wifi access point with an SSID of the form `ESP_012ABC` (some modules
use a different SSID form, such as `ai-thinker-012ABC`)
2. you join your laptop or phone to esp-link's network as a station and you configure
esp-link wifi with your network info by pointing your browser at http://192.168.4.1/
esp-link wifi with your network info by pointing your browser at `http://192.168.4.1/`
3. you set a hostname for esp-link on the "home" page, or leave the default ("esp-link")
4. esp-link starts to connect to your network while continuing to also be an access point
("AP+STA"), the esp-link may show up with a `${hostname}.local` hostname
@ -125,10 +171,11 @@ to join its network to configure it. The short version is:
you reconnect your laptop/phone to your normal network and access esp-link via its hostname
or IP address
LED indicators
--------------
### LED indicators
Assuming appropriate hardware attached to GPIO pins, the green "conn" LED will show the wifi
status as follows:
- Very short flash once a second: not connected to a network and running as AP+STA, i.e.
trying to connect to the configured network
- Very short flash once every two seconds: not connected to a network and running as AP-only
@ -138,8 +185,31 @@ status as follows:
The yellow "ser" LED will blink briefly every time serial data is sent or received by the esp-link.
Wifi configuration details
--------------------------
### Troubleshooting
- verify that you have sufficient power, borderline power can cause the esp module to seemingly
function until it tries to transmit and the power rail collapses
- if you just cannot flash your esp8266 module (some people call it the zombie mode) make sure you
have gpio0 and gpio15 pulled to gnd with a 1K resistor, gpio2 tied to 3.3V with 1K resistor, and
RX/TX connected without anything in series. If you need to level shift the signal going into the
esp8266's RX use a 1K resistor. Use 115200 baud in the flasher.
(For a permanent set-up I would use higher resistor values but
when nothing seems to work these are the ones I try.)
- if the flashing succeeded, check the "conn" LED to see which mode esp-link is in (see LED info above)
- reset or power-cycle the esp-link to force it to become an access-point if it can't
connect to your network within 15-20 seconds
- if the LED says that esp-link is on your network but you can't get to it, make sure your
laptop is on the same network (and no longer on the esp's network)
- if you do not know the esp-link's IP address on your network, try `esp-link.local`, try to find
the lease in your DHCP server; if all fails, you may have to turn off your access point (or walk
far enough away) and reset/power-cycle esp-link, it will then fail to connect and start its
own AP after 15-20 seconds
Configuration details
---------------------
### Wifi
After you have serially flashed the module it will create a wifi access point (AP) with an
SSID of the form `ESP_012ABC` where 012ABC is a piece of the module's MAC address.
Using a laptop, phone, or tablet connect to this SSID and then open a browser pointed at
@ -173,11 +243,12 @@ used to configure the ssid/password info. The problem is that the initial STA+AP
channel 1 and you configure it to connect to an AP on channel 6. This requires the ESP8266's AP
to also switch to channel 6 disconnecting you in the meantime.
Hostname, description, DHCP, mDNS
---------------------------------
### Hostname, description, DHCP, mDNS
You can set a hostname on the "home" page, this should be just the hostname and not a domain
name, i.e., something like "test-module-1" and not "test-module-1.mydomain.com".
This has a number of effects:
- you will see the first 12 chars of the hostname in the menu bar (top left of the page) so
if you have multiple modules you can distinguish them visually
- esp-link will use the hostname in its DHCP request, which allows you to identify the module's
@ -192,49 +263,47 @@ You can also enter a description of up to 128 characters on the home page (botto
allows you to leave a memo for yourself, such as "installed in basement to control the heating
system". This descritpion is not used anywhere else.
Troubleshooting
---------------
- verify that you have sufficient power, borderline power can cause the esp module to seemingly
function until it tries to transmit and the power rail collapses
- if you just cannot flash your esp8266 module (some people call it the zombie mode) make sure you
have gpio0 and gpio15 pulled to gnd with a 1K resistor, gpio2 tied to 3.3V with 1K resistor, and
RX/TX connected without anything in series. If you need to level shift the signal going into the
esp8266's RX use a 1K resistor. Use 115200 baud in the flasher.
(For a permanent set-up I would use higher resistor values but
when nothing seems to work these are the ones I try.)
- if the flashing succeeded, check the "conn" LED to see which mode esp-link is in (see LED info above)
- reset or power-cycle the esp-link to force it to become an access-point if it can't
connect to your network within 15-20 seconds
- if the LED says that esp-link is on your network but you can't get to it, make sure your
laptop is on the same network (and no longer on the esp's network)
- if you do not know the esp-link's IP address on your network, try `esp-link.local`, try to find
the lease in your DHCP server; if all fails, you may have to turn off your access point (or walk
far enough away) and reset/power-cycle esp-link, it will then fail to connect and start its
own AP after 15-20 seconds
Building the firmware
---------------------
The firmware has been built using the [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk)
on a Linux system. Create an esp8266 directory, install the esp-open-sdk into a sub-directory.
### Linux
The firmware has been built using the https://github.com/pfalcon/esp-open-sdk[esp-open-sdk]
on a Linux system. Create an esp8266 directory, install the esp-open-sdk into a sub-directory
using the *non-standalone* install (i.e., there should not be an sdk directory in the esp-open-sdk
dir when done installing, if you use the standalone install you will get compilation errors
with std types, such as `uint32_t`).
Download the Espressif SDK (use the version mentioned in the release notes) from their
[download forum](http://bbs.espressif.com/viewforum.php?f=5) and also expand it into a
sub-directory. Then clone the esp-link repository into a third sub-directory.
http://bbs.espressif.com/viewforum.php?f=5[download forum] and also expand it into a
sub-directory.
Clone the esp-link repository into a third sub-directory and check out the tag you would like,
such as `git checkout v2.1.7`.
This way the relative paths in the Makefile will work.
If you choose a different directory structure look at the Makefile for the appropriate environment
variables to define.
Do not use the source tarballs from the release page on github,
these will give you trouble compiling because the Makefile uses git to determine the esp-link
version being built.
In order to OTA-update the esp8266 you should `export ESP_HOSTNAME=...` with the hostname or
IP address of your module.
Now, build the code: `make` in the top-level of esp-link.
Now, build the code: `make` in the top-level of esp-link. If you want to se the commands being
issued, use `VERBOSE=1 make`.
A few notes from others (I can't fully verify these):
- You may need to install `zlib1g-dev` and `python-serial`
- Make sure you have the correct version of the esp_iot_sdk
- Make sure the paths at the beginning of the makefile are correct
- Make sure `esp-open-sdk/xtensa-lx106-elf/bin` is in the PATH set in the Makefile
### Windows
It is possible to build esp-link on Windows, but it requires a gaggle of software to be installed:
- Install the unofficial sdk, mingw, SourceTree (gui git client), python 2.7, git cli, Java
- Use SourceTree to checkout under C:\espressif or wherever you installed the unofficial sdk,
(see this thread for the unofficial sdk http://www.esp8266.com/viewtopic.php?t=820)
@ -242,8 +311,8 @@ It is possible to build esp-link on Windows, but it requires a gaggle of softwar
the java bin directory under program files.
- ...
Updating the firmware over-the-air
---------------------
### Updating the firmware over-the-air
This firmware supports over-the-air (OTA) flashing, so you do not have to deal with serial
flashing again after the initial one! The recommended way to flash is to use `make wiflash`
if you are also building the firmware.
@ -272,6 +341,7 @@ The flash configuration and the OTA upgrade process is described in more detail
Serial bridge and connections to Arduino, AVR, ARM, LPC microcontrollers
------------------------------------------------------------------------
In order to connect through the esp-link to a microcontroller use port 23. For example,
on linux you can use `nc esp-hostname 23` or `telnet esp-hostname 23`.
@ -282,6 +352,7 @@ broadcast incoming characters from the serial RX to all connections. Use with ca
### Flashing an attached AVR/Arduino
There are three options for reprogramming an attached AVR/Arduino microcontroller:
- Use avrdude and point it at port 23 of esp-link. Esp-link automatically detects the programming
sequence and issues a reset to the AVR.
- Use avrdude and point it at port 2323 of esp-link. This is the same as port 23 except that the
@ -319,11 +390,13 @@ If your AVR doesn't use optiboot then use port 2323 since esp-link may not recog
sequence and not issue a reset if you use port 23.
If you are having trouble with the built-in programmer and see something like this:
```
--------------------
# ./avrflash 192.168.3.104 blink.hex
Error checking sync: FAILED to SYNC: abandoned after timeout, got:
:\xF/\x00\xCj\xCz\xCJ\xCZ\xC\xAÜ\xC\xAä\xC\xAÜ\xC\xAä\xC\xBì\xC\xBô\xC\xBì\xC\xBô\xC\xAÜ\xC\xAä\xC
```
--------------------
the most likely cause is a baud rate mismatch and/or a bad connection from the esp8266 to the
AVRs reset line.
The baud rate used by esp-link is set on the uC Console web page and, as mentioned above, it will
@ -335,9 +408,11 @@ correct baud rate configured but reset isn't functioning, or reset may be functi
baud rate may be incorrect.
The output of a successful flash using the built-in programmer looks like this:
```
--------------------
Success. 3098 bytes at 57600 baud in 0.8s, 3674B/s 63% efficient
```
--------------------
This says that the sketch comprises 3098 bytes of flash, was written in 0.8 seconds
(excludes the initial sync time) at 57600 baud,
and the 3098 bytes were flashed at a rate of 3674 bytes per second.
@ -347,6 +422,35 @@ The efficiency is not 100% because there is protocol overhead (such as sync, rec
length characters)
and there is dead time waiting for an ack or preparing the next record to be sent.
### Details of built-in AVR flash algorithm
The built-in flashing algorithm differs a bit from what avrdude does. The programming protocol
states that STK_GET_SYNC+CRC_EOP (0x30 0x20) should be sent to synchronize, but that works poorly
because the AVR's UART only buffers one character. This means that if STK_GET_SYNC+CRC_EOP is
sent twice there is a high chance that only the last character (CRC_EOP) is actually
received. If that is followed by another STK_GET_SYNC+CRC_EOP sequence then optiboot receives
CRC_EOP+STK_GET_SYNC+CRC_EOP which causes it to abort and run the old sketch. Ending up in that
situation is quite likely because optiboot initializes the UART as one of the first things, but
then goes off an flashes an LED for ~300ms during which it doesn't empty the UART.
Looking at the optiboot code, the good news is that CRC_EOP+CRC_EOP can be used to get an initial
response without the overrun danger of the normal sync sequence and this is what esp-link does.
The programming sequence runs as follows:
- esp-link sends a brief reset pulse (1ms)
- esp-link sends CRC_EOP+CRC_EOP ~50ms later
- esp-link sends CRC_EOP+CRC_EOP every ~70-80ms
- eventually optiboot responds with STK_INSYNC+STK_OK (0x14;0x10)
- esp-link sends one CRC_EOP to sort out the even/odd issue
- either optiboot responds with STK_INSYNC+STK_OK or nothing happens for 70-80ms, in which case
esp-link sends another CRC_EOP
- esp-link sends STK_GET_SYNC+CRC_EOP and optiboot responds with STK_INSYNC+STK_OK and we're in
sync now
- esp-link sends the next command (starts with 'u') and programming starts...
If no sync is achieved, esp-link changes baud rate and the whole thing starts over with a reset
pulse about 600ms, esp-link gives up after about 5 seconds and reports an error.
### Flashing an attached ARM processor
You can reprogram NXP's LPC800-series and many other ARM processors as well by pointing your
@ -376,7 +480,7 @@ baud rate to 115200 is recommended.)
Another option is to use a serial-to-tcp port forwarding driver and point that to port 2323
of esp-link. On windows users have reported success with
[HW Virtual Serial Port](http://www.hw-group.com/products/hw_vsp/hw_vsp2_en.html)
http://www.hw-group.com/products/hw_vsp/hw_vsp2_en.html[HW Virtual Serial Port]
Now to the interference problem: once the attached esp8266 is reset it
starts outputting its 26Mhz clock on gpio0, which needs to be attached to
@ -393,12 +497,13 @@ add a series 100ohm resistor and 100pf capacitor to ground as close to
the gpio0 pin as possible (basically a low pass filter); and/or pass
the cable connecting the two esp8266's through a ferrite bead.
Debug log
---------
### Debug log
The esp-link web UI can display the esp-link debug log (os_printf statements in the code). This
is handy but sometimes not sufficient. Esp-link also prints the debug info to the UART where
it is sometimes more convenient and sometimes less... For this reason three UART debug log
modes are supported that can be set in the web UI (and the mode is saved in flash):
- auto: the UART log starts enabled at boot using uart0 and disables itself when esp-link
associates with an AP. It re-enables itself if the association is lost.
- off: the UART log is always off
@ -410,7 +515,8 @@ esp8266 comes out of reset. This cannot be disabled.
Outbound HTTP REST requests and MQTT client
-------------------------------------------
The V2 versions of esp-link support the espduino SLIP protocol that supports simple outbound
The V2 versions of esp-link use the SLIP protocol over the serial link to support simple outbound
HTTP REST requests as well as an MQTT client. The SLIP protocol consists of commands with
binary arguments sent from the
attached microcontroller to the esp8266, which then performs the command and responds back.
@ -419,13 +525,11 @@ command sent by the uC contains a callback address and the response from the esp
with that callback address. This enables asynchronous communication where esp-link can notify the
uC when requests complete or when other actions happen, such as wifi connectivity status changes.
You can find a demo sketch in a fork of the espduino library at
https://github.com/tve/espduino in the
[examples/demo folder](https://github.com/tve/espduino/tree/master/espduino/examples/demo).
More docs forthcoming...
You can find REST and MQTT libraries as well as demo sketches in the
https://github.com/jeelabs/el-client[el-client] repository.
Contact
-------
If you find problems with esp-link, please create a github issue. If you have a question, please
use the gitter chat link at the top of this page.

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

@ -6,19 +6,11 @@
#define CMD_H
#include <esp8266.h>
// Escape chars used by tuanpmt, dunno why he didn't use std ones...
#define SLIP_START 0x7E
#define SLIP_END 0x7F
#define SLIP_REPL 0x7D
#define SLIP_ESC(x) (x ^ 0x20)
#if 0
// Proper SLIP escape chars from RFC
// Standard SLIP escape chars from RFC
#define SLIP_END 0300 // indicates end of packet
#define SLIP_ESC 0333 // indicates byte stuffing
#define SLIP_ESC_END 0334 // ESC ESC_END means END data byte
#define SLIP_ESC_ESC 0335 // ESC ESC_ESC means ESC data byte
#endif
typedef struct __attribute__((__packed__)) {
uint16_t len; // length of data
@ -27,9 +19,8 @@ typedef struct __attribute__((__packed__)) {
typedef struct __attribute__((__packed__)) {
uint16_t cmd; // command to perform, from CmdName enum
uint32_t callback; // callback pointer to embed in response
uint32_t _return; // return value to embed in response (?)
uint16_t argc; // number of arguments to command
uint32_t value; // callback pointer for response or first argument
CmdArg args[0]; // really args[argc]
} CmdPacket;
@ -41,66 +32,70 @@ typedef struct {
typedef enum {
CMD_NULL = 0,
CMD_RESET, // reset esp (not honored in this implementation)
CMD_IS_READY, // health-check
CMD_WIFI_CONNECT, // (3) connect to AP (not honored in this implementation)
CMD_MQTT_SETUP,
CMD_MQTT_CONNECT,
CMD_MQTT_DISCONNECT,
CMD_MQTT_PUBLISH,
CMD_MQTT_SUBSCRIBE,
CMD_MQTT_LWT,
CMD_MQTT_EVENTS,
CMD_REST_SETUP, // (11)
CMD_SYNC, // synchronize and clear
CMD_RESP_V, // response with a value
CMD_RESP_CB, // response with a callback
CMD_WIFI_STATUS, // get the current wifi status
CMD_CB_ADD,
CMD_CB_EVENTS,
CMD_GET_TIME, // get current time in seconds since the unix epoch
CMD_MQTT_SETUP = 10, // set-up callbacks
CMD_MQTT_PUBLISH, // publish a message
CMD_MQTT_SUBSCRIBE, // subscribe to a topic
CMD_MQTT_LWT, // set the last-will-topic and messge
CMD_REST_SETUP = 20,
CMD_REST_REQUEST,
CMD_REST_SETHEADER,
CMD_REST_EVENTS,
CMD_CB_ADD, // 15
CMD_CB_EVENTS
} CmdName;
typedef uint32_t (*cmdfunc_t)(CmdPacket *cmd);
typedef void (*cmdfunc_t)(CmdPacket *cmd);
typedef struct {
CmdName sc_name;
cmdfunc_t sc_function;
CmdName sc_name; // name as CmdName enum
char *sc_text; // name as string
cmdfunc_t sc_function; // pointer to function
} CmdList;
#define CMD_CBNLEN 16
typedef struct {
char name[CMD_CBNLEN];
uint32_t callback;
} cmdCallback;
} CmdCallback;
// Used by slip protocol to cause parsing of a received packet
void CMD_parse_packet(uint8_t *buf, short len);
void cmdParsePacket(uint8_t *buf, short len);
// Return the info about a callback to the attached uC by name, these are callbacks that the
// attached uC registers using the ADD_SENSOR command
cmdCallback* CMD_GetCbByName(char* name);
CmdCallback* cmdGetCbByName(char* name);
// Add a callback
uint32_t cmdAddCb(char *name, uint32_t callback);
// Responses
// Start a response, returns the partial CRC
uint16_t CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc);
// Adds data to a response, returns the partial CRC
uint16_t CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len);
// Start a response
void cmdResponseStart(uint16_t cmd, uint32_t value, uint16_t argc);
// Adds data to a response
void cmdResponseBody(const void* data, uint16_t len);
// Ends a response
void CMD_ResponseEnd(uint16_t crc);
void cmdResponseEnd();
//void CMD_Response(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc, CmdArg* args[]);
//void cmdResponse(uint16_t cmd, uint32_t callback, uint32_t value, uint16_t argc, CmdArg* args[]);
// Requests
// Fill out a CmdRequest struct given a CmdPacket
void CMD_Request(CmdRequest *req, CmdPacket* cmd);
void cmdRequest(CmdRequest *req, CmdPacket* cmd);
// Return the number of arguments given a request
uint32_t CMD_GetArgc(CmdRequest *req);
uint32_t cmdGetArgc(CmdRequest *req);
// Return the length of the next argument
uint16_t CMD_ArgLen(CmdRequest *req);
uint16_t cmdArgLen(CmdRequest *req);
// Copy next arg from request into the data pointer, returns 0 on success, -1 on error
int32_t CMD_PopArg(CmdRequest *req, void *data, uint16_t len);
int32_t cmdPopArg(CmdRequest *req, void *data, uint16_t len);
// Skip next arg
void CMD_SkipArg(CmdRequest *req);
void cmdSkipArg(CmdRequest *req);
#endif

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

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

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

@ -47,12 +47,20 @@ Cgi/template routines for the /wifi url.
} while(0)
# define VERS_STR_STR(V) #V
# define VERS_STR(V) VERS_STR_STR(V)
bool mdns_started = false;
// ===== wifi status change callbacks
static WifiStateChangeCb wifi_state_change_cb[4];
// Temp store for new station config
struct station_config stconf;
// Temp store for new ap config
struct softap_config apconf;
uint8_t wifiState = wifiIsDisconnected;
// reasons for which a connection failed
uint8_t wifiReason = 0;
@ -287,14 +295,21 @@ static int ICACHE_FLASH_ATTR cgiWiFiGetScan(HttpdConnData *connData) {
}
int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) {
if (connData->requestType == HTTPD_METHOD_GET) {
return cgiWiFiGetScan(connData);
} else if (connData->requestType == HTTPD_METHOD_POST) {
return cgiWiFiStartScan(connData);
} else {
jsonHeader(connData, 404);
return HTTPD_CGI_DONE;
}
if (connData->requestType == HTTPD_METHOD_GET) {
return cgiWiFiGetScan(connData);
}else if(connData->requestType == HTTPD_METHOD_POST) {
// DO NOT start APs scan in AP mode
int mode = wifi_get_opmode();
if(mode==2){
jsonHeader(connData, 400);
return HTTPD_CGI_DONE;
}else{
return cgiWiFiStartScan(connData);
}
}else{
jsonHeader(connData, 404);
return HTTPD_CGI_DONE;
}
}
// ===== timers to change state and rescue from failed associations
@ -311,8 +326,9 @@ static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
int m = wifi_get_opmode() & 0x3;
NOTICE("check: mode=%s status=%d", wifiMode[m], x);
if (x == STATION_GOT_IP) {
if (m != 1) {
if(m!=2){
if ( x == STATION_GOT_IP ) {
if (m != 1) {
#ifdef CHANGE_TO_STA
// We're happily connected, go to STA mode
NOTICE("got IP. Going into STA mode..");
@ -326,15 +342,15 @@ static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
if (m != 3) {
NOTICE("connect failed. Going into STA+AP mode..");
wifi_set_opmode(3);
wifi_softap_set_config(&apconf);
}
log_uart(true);
INFO("Enabling/continuing uart log");
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0);
}
}
}
// Temp store for new ap info.
static struct station_config stconf;
// Reassociate timer to delay change of association so the original request can finish
static ETSTimer reassTimer;
@ -356,6 +372,12 @@ static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) {
// This cgi uses the routines above to connect to a specific access point with the
// given ESSID using the given password.
int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) {
int mode = wifi_get_opmode();
if(mode == 2){
jsonHeader(connData, 400);
httpdSend(connData, "Can't associate to an AP en SoftAP mode", -1);
return HTTPD_CGI_DONE;
}
char essid[128];
char passwd[128];
@ -504,38 +526,198 @@ int ICACHE_FLASH_ATTR cgiWiFiSpecial(HttpdConnData *connData) {
return HTTPD_CGI_DONE;
}
// ==== Soft-AP related functions
// Change Soft-AP main settings
int ICACHE_FLASH_ATTR cgiApSettingsChange(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
// No changes for Soft-AP in STA mode
int mode = wifi_get_opmode();
if ( mode == 1 ){
jsonHeader(connData, 400);
httpdSend(connData, "No changes allowed in STA mode", -1);
return HTTPD_CGI_DONE;
}
char buff[96];
int len;
// Check extra security measure, this must be 1
len=httpdFindArg(connData->getArgs, "100", buff, sizeof(buff));
if(len>0){
if(atoi(buff)!=1){
jsonHeader(connData, 400);
return HTTPD_CGI_DONE;
}
}
// Set new SSID
len=httpdFindArg(connData->getArgs, "ap_ssid", buff, sizeof(buff));
if(checkString(buff) && len>7 && len<32){
// STRING PREPROCESSING DONE IN CLIENT SIDE
os_memset(apconf.ssid, 0, 32);
os_memcpy(apconf.ssid, buff, len);
apconf.ssid_len = len;
}else{
jsonHeader(connData, 400);
httpdSend(connData, "SSID not valid or out of range", -1);
return HTTPD_CGI_DONE;
}
// Set new PASSWORD
len=httpdFindArg(connData->getArgs, "ap_password", buff, sizeof(buff));
if(checkString(buff) && len>7 && len<64){
// String preprocessing done in client side, wifiap.js line 31
os_memset(apconf.password, 0, 64);
os_memcpy(apconf.password, buff, len);
}else if (len == 0){
os_memset(apconf.password, 0, 64);
}else{
jsonHeader(connData, 400);
httpdSend(connData, "PASSWORD not valid or out of range", -1);
return HTTPD_CGI_DONE;
}
// Set auth mode
if(len != 0){
// Set authentication mode, before password to check open settings
len=httpdFindArg(connData->getArgs, "ap_authmode", buff, sizeof(buff));
if(len>0){
int value = atoi(buff);
if(value >= 0 && value <= 4){
apconf.authmode = value;
}else{
// If out of range set by default
apconf.authmode = 4;
}
}else{
// Valid password but wrong auth mode, default 4
apconf.authmode = 4;
}
}else{
apconf.authmode = 0;
}
// Set max connection number
len=httpdFindArg(connData->getArgs, "ap_maxconn", buff, sizeof(buff));
if(len>0){
int value = atoi(buff);
if(value > 0 && value <= 4){
apconf.max_connection = value;
}else{
// If out of range, set by default
apconf.max_connection = 4;
}
}
// Set beacon interval value
len=httpdFindArg(connData->getArgs, "ap_beacon", buff, sizeof(buff));
if(len>0){
int value = atoi(buff);
if(value >= 100 && value <= 60000){
apconf.beacon_interval = value;
}else{
// If out of range, set by default
apconf.beacon_interval = 100;
}
}
// Set ssid to be hidden or not
len=httpdFindArg(connData->getArgs, "ap_hidden", buff, sizeof(buff));
if(len>0){
int value = atoi(buff);
if(value == 0 || value == 1){
apconf.ssid_hidden = value;
}else{
// If out of range, set by default
apconf.ssid_hidden = 0;
}
}
// Store new configuration
wifi_softap_set_config(&apconf);
jsonHeader(connData, 200);
return HTTPD_CGI_DONE;
}
// Get current Soft-AP settings
int ICACHE_FLASH_ATTR cgiApSettingsInfo(HttpdConnData *connData) {
char buff[1024];
if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
os_sprintf(buff,
"{ "
"\"ap_ssid\": \"%s\", "
"\"ap_password\": \"%s\", "
"\"ap_authmode\": %d, "
"\"ap_maxconn\": %d, "
"\"ap_beacon\": %d, "
"\"ap_hidden\": \"%s\" "
" }",
apconf.ssid,
apconf.password,
apconf.authmode,
apconf.max_connection,
apconf.beacon_interval,
apconf.ssid_hidden ? "enabled" : "disabled"
);
jsonHeader(connData, 200);
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
//This cgi changes the operating mode: STA / AP / STA+AP
int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) {
int len;
char buff[1024];
int previous_mode = wifi_get_opmode();
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff));
if (len!=0) {
int m = atoi(buff);
NOTICE("switching to mode %d", m);
wifi_set_opmode(m&3);
if (m == 1) {
// STA-only mode, reset into STA+AP after a timeout if we don't get an IP address
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0);
int next_mode = atoi(buff);
if (len!=0) {
if (next_mode == 2){
// moving to AP mode, so disconnect before leave STA mode
wifi_station_disconnect();
}
DBG("Wifi switching to mode %d\n", next_mode);
wifi_set_opmode(next_mode&3);
if (previous_mode == 2) {
// moving to STA or STA+AP mode from AP, try to connect and set timer
stconf.bssid_set = 0;
wifi_station_set_config(&stconf);
wifi_station_connect();
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0);
}
if(previous_mode == 1){
// moving to AP or STA+AP from STA, so softap config call needed
wifi_softap_set_config(&apconf);
}
jsonHeader(connData, 200);
} else {
jsonHeader(connData, 400);
}
jsonHeader(connData, 200);
} else {
jsonHeader(connData, 400);
}
return HTTPD_CGI_DONE;
return HTTPD_CGI_DONE;
}
static char *connStatuses[] = { "idle", "connecting", "wrong password", "AP not found",
"failed", "got IP address" };
static char *wifiWarn[] = { 0,
"Switch to <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(1)\\\">STA mode</a>",
"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(2)\\\">AP mode</a>",
};
static char *apAuthMode[] = { "OPEN",
"WEP",
"WPA_PSK",
"WPA2_PSK",
"WPA_WPA2_PSK",
};
#ifdef CHANGE_TO_STA
@ -547,43 +729,50 @@ static char *wifiWarn[] = { 0,
// print various Wifi information into json buffer
int ICACHE_FLASH_ATTR printWifiInfo(char *buff) {
int len;
//struct station_config stconf;
wifi_station_get_config(&stconf);
//struct softap_config apconf;
wifi_softap_get_config(&apconf);
uint8_t op = wifi_get_opmode() & 0x3;
char *mode = wifiMode[op];
char *status = "unknown";
int st = wifi_station_get_connect_status();
if (st >= 0 && st < sizeof(connStatuses)) status = connStatuses[st];
int p = wifi_get_phy_mode();
char *phy = wifiPhy[p&3];
char *warn = wifiWarn[op];
if (op == 3) op = 4; // Done to let user switch to AP only mode from Soft-AP settings page, using only one set of warnings
char *apwarn = wifiWarn[op];
char *apauth = apAuthMode[apconf.authmode];
sint8 rssi = wifi_station_get_rssi();
if (rssi > 0) rssi = 0;
uint8 mac_addr[6];
uint8 apmac_addr[6];
wifi_get_macaddr(0, mac_addr);
wifi_get_macaddr(1, apmac_addr);
uint8_t chan = wifi_get_channel();
len = os_sprintf(buff,
"\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", "
"\"rssi\": \"%ddB\", \"warn\": \"%s\", \"apwarn\": \"%s\",\"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":\"%d\", \"apssid\": \"%s\", "
"\"appass\": \"%s\", \"apchan\": \"%d\", \"apmaxc\": \"%d\", \"aphidd\": \"%s\", \"apbeac\": \"%d\", \"apauth\": \"%s\",\"apmac\":\"%02x:%02x:%02x:%02x:%02x:%02x\"",
mode, MODECHANGE, (char*)stconf.ssid, status, phy, rssi, warn, apwarn,
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], chan, (char*)apconf.ssid,(char*)apconf.password,apconf.channel,apconf.max_connection,apconf.ssid_hidden?"enabled":"disabled",apconf.beacon_interval, apauth,apmac_addr[0], apmac_addr[1], apmac_addr[2], apmac_addr[3], apmac_addr[4], apmac_addr[5]);
struct ip_info info;
if (wifi_get_ip_info(0, &info)) {
len += os_sprintf(buff+len, ", \"ip\": \"%d.%d.%d.%d\"", IP2STR(&info.ip.addr));
len += os_sprintf(buff+len, ", \"netmask\": \"%d.%d.%d.%d\"", IP2STR(&info.netmask.addr));
len += os_sprintf(buff+len, ", \"gateway\": \"%d.%d.%d.%d\"", IP2STR(&info.gw.addr));
len += os_sprintf(buff+len, ", \"hostname\": \"%s\"", flashConfig.hostname);
} else {
len += os_sprintf(buff+len, ", \"ip\": \"-none-\"");
}
len += os_sprintf(buff+len, ", \"staticip\": \"%d.%d.%d.%d\"", IP2STR(&flashConfig.staticip));
len += os_sprintf(buff+len, ", \"dhcp\": \"%s\"", flashConfig.staticip > 0 ? "off" : "on");
struct station_config stconf;
wifi_station_get_config(&stconf);
uint8_t op = wifi_get_opmode() & 0x3;
char *mode = wifiMode[op];
char *status = "unknown";
int st = wifi_station_get_connect_status();
if (st >= 0 && st < sizeof(connStatuses)) status = connStatuses[st];
int p = wifi_get_phy_mode();
char *phy = wifiPhy[p&3];
char *warn = wifiWarn[op];
sint8 rssi = wifi_station_get_rssi();
if (rssi > 0) rssi = 0;
uint8 mac_addr[6];
wifi_get_macaddr(0, mac_addr);
uint8_t chan = wifi_get_channel();
len = os_sprintf(buff,
"\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", "
"\"rssi\": \"%ddB\", \"warn\": \"%s\", \"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":%d",
mode, MODECHANGE, (char*)stconf.ssid, status, phy, rssi, warn,
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], chan);
struct ip_info info;
if (wifi_get_ip_info(0, &info)) {
len += os_sprintf(buff+len, ", \"ip\": \"%d.%d.%d.%d\"", IP2STR(&info.ip.addr));
len += os_sprintf(buff+len, ", \"netmask\": \"%d.%d.%d.%d\"", IP2STR(&info.netmask.addr));
len += os_sprintf(buff+len, ", \"gateway\": \"%d.%d.%d.%d\"", IP2STR(&info.gw.addr));
len += os_sprintf(buff+len, ", \"hostname\": \"%s\"", flashConfig.hostname);
} else {
len += os_sprintf(buff+len, ", \"ip\": \"-none-\"");
}
len += os_sprintf(buff+len, ", \"staticip\": \"%d.%d.%d.%d\"", IP2STR(&flashConfig.staticip));
len += os_sprintf(buff+len, ", \"dhcp\": \"%s\"", flashConfig.staticip > 0 ? "off" : "on");
return len;
return len;
}
int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) {
@ -636,24 +825,123 @@ int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) {
return HTTPD_CGI_DONE;
}
// Init the wireless, which consists of setting a timer if we expect to connect to an AP
// so we can revert to STA+AP mode if we can't connect.
void ICACHE_FLASH_ATTR wifiInit() {
// wifi_set_phy_mode(2); // limit to 802.11b/g 'cause n is flaky
int x = wifi_get_opmode() & 0x3;
x = x;
NOTICE("init, mode=%s", wifiMode[x]);
configWifiIP();
// The default sleep mode should be modem_sleep, but we set it here explicitly for good
// measure. We can't use light_sleep because that powers off everthing and we would loose
// all connections.
wifi_set_sleep_type(MODEM_SLEEP_T);
wifi_set_event_handler_cb(wifiHandleEventCb);
// check on the wifi in a few seconds to see whether we need to switch mode
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0);
// Check string againt invalid characters
int ICACHE_FLASH_ATTR checkString(char *str){
int i = 0;
for(; i < os_strlen(str); i++)
{
// Alphanumeric and underscore allowed
if (!(isalnum((unsigned char)str[i]) || str[i] == '_'))
{
DBG("Error: String has non alphanumeric chars\n");
return 0;
}
}
return 1;
}
/* Init the wireless
*
* Call both Soft-AP and Station default config
* Change values according to Makefile hard-coded variables
* Anyway set wifi opmode to STA+AP, it will change to STA if CHANGE_TO_STA is set to yes in Makefile
* Call a timer to check the STA connection
*/
void ICACHE_FLASH_ATTR wifiInit() {
// Check te wifi opmode
int x = wifi_get_opmode() & 0x3;
// Set opmode to 3 to let system scan aps, otherwise it won't scan
wifi_set_opmode(3);
// Call both STATION and SOFTAP default config
wifi_station_get_config_default(&stconf);
wifi_softap_get_config_default(&apconf);
DBG("Wifi init, mode=%s\n",wifiMode[x]);
// STATION parameters
#if defined(STA_SSID) && defined(STA_PASS)
// Set parameters
if (os_strlen((char*)stconf.ssid) == 0 && os_strlen((char*)stconf.password) == 0) {
os_strncpy((char*)stconf.ssid, VERS_STR(STA_SSID), 32);
os_strncpy((char*)stconf.password, VERS_STR(STA_PASS), 64);
DBG("Wifi pre-config trying to connect to AP %s pw %s\n",(char*)stconf.ssid, (char*)stconf.password);
// wifi_set_phy_mode(2); // limit to 802.11b/g 'cause n is flaky
stconf.bssid_set = 0;
wifi_station_set_config(&stconf);
}
#endif
// Change SOFT_AP settings if defined
#if defined(AP_SSID)
// Check if ssid and pass are alphanumeric values
int ssidlen = os_strlen(VERS_STR(AP_SSID));
if(checkString(VERS_STR(AP_SSID)) && ssidlen > 7 && ssidlen < 32){
// Clean memory and set the value of SSID
os_memset(apconf.ssid, 0, 32);
os_memcpy(apconf.ssid, VERS_STR(AP_SSID), os_strlen(VERS_STR(AP_SSID)));
// Specify the length of ssid
apconf.ssid_len= ssidlen;
#if defined(AP_PASS)
// If pass is at least 8 and less than 64
int passlen = os_strlen(VERS_STR(AP_PASS));
if( checkString(VERS_STR(AP_PASS)) && passlen > 7 && passlen < 64 ){
// Clean memory and set the value of PASS
os_memset(apconf.password, 0, 64);
os_memcpy(apconf.password, VERS_STR(AP_PASS), passlen);
// Can't choose auth mode without a valid ssid and password
#ifdef AP_AUTH_MODE
// If set, use specified auth mode
if(AP_AUTH_MODE >= 0 && AP_AUTH_MODE <=4)
apconf.authmode = AP_AUTH_MODE;
#else
// If not, use WPA2
apconf.authmode = AUTH_WPA_WPA2_PSK;
#endif
}else if ( passlen == 0){
// If ssid is ok and no pass, set auth open
apconf.authmode = AUTH_OPEN;
// Remove stored password
os_memset(apconf.password, 0, 64);
}
#endif
}// end of ssid and pass check
#ifdef AP_SSID_HIDDEN
// If set, use specified ssid hidden parameter
if(AP_SSID_HIDDEN == 0 || AP_SSID_HIDDEN ==1)
apconf.ssid_hidden = AP_SSID_HIDDEN;
#endif
#ifdef AP_MAX_CONN
// If set, use specified max conn number
if(AP_MAX_CONN > 0 && AP_MAX_CONN <5)
apconf.max_connection = AP_MAX_CONN;
#endif
#ifdef AP_BEACON_INTERVAL
// If set use specified beacon interval
if(AP_BEACON_INTERVAL >= 100 && AP_BEACON_INTERVAL <= 60000)
apconf.beacon_interval = AP_BEACON_INTERVAL;
#endif
// Check softap config
bool softap_set_conf = wifi_softap_set_config(&apconf);
// Debug info
DBG("Wifi Soft-AP parameters change: %s\n",softap_set_conf? "success":"fail");
#endif // AP_SSID && AP_PASS
configWifiIP();
// The default sleep mode should be modem_sleep, but we set it here explicitly for good
// measure. We can't use light_sleep because that powers off everthing and we would loose
// all connections.
wifi_set_sleep_type(MODEM_SLEEP_T);
wifi_set_event_handler_cb(wifiHandleEventCb);
// check on the wifi in a few seconds to see whether we need to switch mode
os_timer_disarm(&resetTimer);
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0);
}

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

@ -72,6 +72,8 @@ HttpdBuiltInUrl builtInUrls[] = {
{ "/wifi/connstatus", cgiWiFiConnStatus, NULL },
{ "/wifi/setmode", cgiWiFiSetMode, NULL },
{ "/wifi/special", cgiWiFiSpecial, NULL },
{ "/wifi/apinfo", cgiApSettingsInfo, NULL },
{ "/wifi/apchange", cgiApSettingsChange, NULL },
{ "/system/info", cgiSystemInfo, NULL },
{ "/system/update", cgiSystemSet, NULL },
{ "/services/info", cgiServicesInfo, NULL },
@ -86,7 +88,6 @@ HttpdBuiltInUrl builtInUrls[] = {
#ifdef SHOW_HEAP_USE
static ETSTimer prHeapTimer;
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) {
os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size());
}
@ -110,47 +111,23 @@ void user_rf_pre_init(void) {
// Main routine to initialize esp-link.
void user_init(void) {
// get the flash config so we know how to init things
// configWipe(); // uncomment to reset the config for testing purposes
//configWipe(); // uncomment to reset the config for testing purposes
bool restoreOk = configRestore();
// init gpio pin registers
// Init gpio pin registers
gpio_init();
gpio_output_set(0, 0, 0, (1<<15)); // some people tie it to GND, gotta ensure it's disabled
// init UART
uart_init(flashConfig.baud_rate, 115200);
logInit(); // must come after init of uart
// say hello (leave some time to cause break in TX after boot loader's msg
// Say hello (leave some time to cause break in TX after boot loader's msg
os_delay_us(10000L);
os_printf("\n\n** %s\n", esp_link_version);
os_printf("Flash config restore %s\n", restoreOk ? "ok" : "*FAILED*");
#if defined(STA_SSID) && defined(STA_PASS)
int x = wifi_get_opmode() & 0x3;
if (x == 2) {
// we only force the STA settings when a full flash of the module has been made, which
// resets the wifi settings not to have anything configured
struct station_config stconf;
wifi_station_get_config(&stconf);
if (os_strlen((char*)stconf.ssid) == 0 && os_strlen((char*)stconf.password) == 0) {
os_strncpy((char*)stconf.ssid, VERS_STR(STA_SSID), 32);
os_strncpy((char*)stconf.password, VERS_STR(STA_PASS), 64);
#ifdef CGIWIFI_DBG
os_printf("Wifi pre-config trying to connect to AP %s pw %s\n",
(char*)stconf.ssid, (char*)stconf.password);
#endif
wifi_set_opmode(3); // sta+ap, will switch to sta-only 15 secs after connecting
stconf.bssid_set = 0;
wifi_station_set_config(&stconf);
}
}
#endif
// Status LEDs
statusInit();
serledInit();
// Wifi
wifiInit();
// init the flash filesystem with the html stuff
espFsInit(&_binary_espfs_img_start);
//EspFsInitResult res = espFsInit(&_binary_espfs_img_start);
@ -176,15 +153,13 @@ void user_init(void) {
fid & 0xff, (fid&0xff00)|((fid>>16)&0xff));
NOTICE("** %s: ready, heap=%ld", esp_link_version, (unsigned long)system_get_free_heap_size());
// Init SNTP service
cgiServicesSNTPInit();
#ifdef MQTT
NOTICE("initializing MQTT");
mqtt_client_init();
#endif
NOTICE("initializing user application");
app_init();
NOTICE("waiting for work to do...");
NOTICE("Waiting for work to do...");
}

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

@ -37,7 +37,7 @@ static void ICACHE_FLASH_ATTR mqttStatusCb(void *v) {
char buf[128];
mqttStatusMsg(buf);
MQTT_Publish(&mqttClient, flashConfig.mqtt_status_topic, buf, 1, 0);
MQTT_Publish(&mqttClient, flashConfig.mqtt_status_topic, buf, os_strlen(buf), 1, 0);
}

@ -22,8 +22,8 @@
</div>
</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 address</td><td id="wifi-ip"></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>SLIP status</td><td class="system-slip"></td></tr>
<tr><td>MQTT status</td><td class="system-mqtt"></td></tr>
<tr><td>Serial baud</td><td class="system-baud"></td></tr>
@ -32,7 +32,7 @@
<div class="card">
<h1>Info</h1>
<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
NXP's LPC800 and other ARM processors. Typical avrdude command line to
program an Arduino:</p>
@ -79,7 +79,7 @@
<div class="pure-control-group">
<label for="pin-conn">Conn LED</label>
<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 class="pure-control-group">
<label for="pin-ser">Serial LED</label>
@ -109,8 +109,8 @@
<h1>System details</h1>
<div id="system-spinner" class="spinner spinner-small"></div>
<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 channel</td><td id="wifi-chan"></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>Flash chip ID</td><td>
<div>
<span class="system-id"></span>
@ -126,7 +126,7 @@
<tr><td>Current partition</td><td class="system-partition"></td></tr>
<tr><td colspan=2 class="popup-target">Description:<br>
<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>
<div class="popup">Click to edit!<br>A short description or memo for this esp-link
module, 128 chars max</div>

@ -14,7 +14,7 @@
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>
<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">
<input type="checkbox" name="slip-enable"/>
<label>Enable SLIP on serial port</label>
@ -28,11 +28,14 @@
<div id="mqtt-spinner" class="spinner spinner-small"></div>
</h1>
<form action="#" id="mqtt-form" class="pure-form" hidden>
<input type="checkbox" name="mqtt-enable"/>
<label>Enable MQTT client</label>
<br>
<label>MQTT client state: </label>
<b id="mqtt-state"></b>
<div>
<input type="checkbox" name="mqtt-enable"/>
<label>Enable MQTT client</label>
</div>
<div>
<label>MQTT client state: </label>
<b id="mqtt-state"></b>
</div>
<br>
<legend>MQTT server settings</legend>
<div class="pure-form-stacked">
@ -68,11 +71,11 @@
<label>Enable status reporting via MQTT</label>
</div>
<br>
<legend>Status reporting settings</legend>
<div class="pure-form-stacked">
<label>Topic</label>
<label>Status topic</label>
<input type="text" name="mqtt-status-topic"/>
Message: <tt id="mqtt-status-value"></tt>
<div class="popup">MQTT topic to which status message is sent</div>
</div>
<button id="mqtt-status-button" type="submit" class="pure-button button-primary">
Update status settings!

@ -12,35 +12,41 @@
<div id="syslog-spinner" class="spinner spinner-small"></div>
</h1>
<form action="#" id="Syslog-form" class="pure-form" hidden>
<legend>Syslog settings</legend>
<div class="pure-form-stacked">
<label>Syslog Host</label>
<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 class="pure-form-stacked">
<label>Min Heap</label>
<input type="text" name="syslog_minheap" />
<label>Filter</label>
<select name="syslog_filter" href="#">
<option value="0">EMERG</option>
<option value="1">ALERT</option>
<option value="2">CRIT</option>
<option value="3">ERR</option>
<option value="4">WARNING</option>
<option value="5">NOTICE</option>
<option value="6">INFO</option>
<option value="7">DEBUG</option>
</select>
<div>
<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>
<select name="syslog_filter" href="#">
<option value="0">EMERG</option>
<option value="1">ALERT</option>
<option value="2">CRIT</option>
<option value="3">ERR</option>
<option value="4">WARNING</option>
<option value="5">NOTICE</option>
<option value="6">INFO</option>
<option value="7">DEBUG</option>
</select>
<div class="popup">Minimum severity to send</div>
</div>
</div>
<div class="form-horizontal">
<label>Show ESP &#xb5;C Ticker in log message</label>
<div>
<input type="checkbox" name="syslog_showtick" />
<label>Include esp-link millisecond ticker</label>
</div>
<div class="form-horizontal">
<label>Show date in log message</label>
<div>
<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>
<button id="Syslog-button" type="submit" class="pure-button button-primary">
Update Syslog settings!
@ -56,12 +62,14 @@
<div class="form-horizontal">
<input type="checkbox" name="mdns_enable"/>
<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>
<br>
<legend>mDNS server settings</legend>
<div class="pure-form-stacked">
<label>Server Name</label>
<label>Service Name</label>
<input type="text" name="mdns_servername"/>
<div class="popup">The default service is http. For the arduino IDE use arduino</div>
</div>
<button id="mDNS-button" type="submit" class="pure-button button-primary">
Update mDNS settings!
@ -76,12 +84,18 @@
<div id="sntp-spinner" class="spinner spinner-small"></div>
</h1>
<form action="#" id="SNTP-form" class="pure-form" hidden>
<legend>SNTP server settings</legend>
<div class="pure-form-stacked">
<label>Timezone Offset</label>
<input type="text" name="timezone_offset" />
<label>SNTP Server</label>
<input type="text" name="sntp_server" />
<div>
<label>SNTP Server</label>
<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>
<button id="SNTP-button" type="submit" class="pure-button button-primary">
Update SNTP settings!
@ -103,4 +117,4 @@ onLoad(function() {
bnd($("#mDNS-form"), "submit", changeServices);
});
</script>
</body></html>
</body></html>

@ -85,10 +85,10 @@ a:hover {
.popup, div.popup {
position: absolute;
/*top: 100%;*/
bottom: 100%;
bottom: 125%;
background-color: #fff0b3;
border-radius: 5px;
border: 0px solid #000;
border: 1px solid #e6b800;
color: #333;
font-size: 80%;
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 class="header">
<h1>WiFi Configuration</h1>
<h1>WiFi Station Configuration</h1>
</div>
<div class="content">
@ -19,7 +19,9 @@
<tr><td>WiFi MAC</td><td id="wifi-mac"></td></tr>
<tr><td colspan="2" id="wifi-warn"></td></tr>
</tbody> </table>
</div></div>
</div>
</div>
<div class="pure-u-1 pure-u-md-1-2"><div class="card">
<h1>WiFi Association</h1>
<p id="reconnect" style="color: #600" hidden></p>
@ -70,7 +72,7 @@
<script type="text/javascript">
</script>
<script src="wifi.js"></script>
<script src="wifiSta.js"></script>
<script type="text/javascript">
onLoad(function() {
getWifiInfo();

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

@ -89,9 +89,9 @@ deliver_publish(MQTT_Client* client, uint8_t* message, uint16_t length) {
// callback to client
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)
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:
//DBG("MQTT: Connect successful\n");
// callbacks for internal and external clients
if (client->connectedCb) client->connectedCb((uint32_t*)client);
if (client->cmdConnectedCb) client->cmdConnectedCb((uint32_t*)client);
if (client->connectedCb) client->connectedCb(client);
if (client->cmdConnectedCb) client->cmdConnectedCb(client);
client->reconTimeout = 1; // reset the reconnect backoff
break;
@ -357,8 +357,8 @@ mqtt_tcpclient_discon_cb(void* arg) {
// if this is an aborted connection we're done
if (client == NULL) return;
DBG("MQTT: Disconnected from %s:%d\n", client->host, client->port);
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client);
if (client->disconnectedCb) client->disconnectedCb(client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb(client);
// reconnect unless we're in a permanently disconnected state
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);
os_free(pespconn);
os_printf("MQTT: Connection reset from %s:%d\n", client->host, client->port);
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client);
if (client->disconnectedCb) client->disconnectedCb(client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb(client);
// reconnect unless we're in a permanently disconnected state
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
*/
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
uint16_t topic_length = os_strlen(topic);
uint16_t data_length = os_strlen(data);
// estimate: fixed hdr, pkt-id, topic length, topic, data, fudge
uint16_t buf_len = 3 + 2 + 2 + topic_length + data_length + 16;
PktBuf *buf = PktBuf_New(buf_len);
@ -738,8 +739,8 @@ mqtt_doAbort(MQTT_Client* client) {
else
espconn_disconnect(client->pCon);
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client);
if (client->disconnectedCb) client->disconnectedCb(client);
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb(client);
if (client->sending_buffer != NULL) {
os_free(client->sending_buffer);

@ -44,14 +44,16 @@ typedef enum {
MQTT_CONNECTED, // conneted (or connecting)
} tConnState;
typedef struct MQTT_Client MQTT_Client; // forward definition
// Simple notification callback
typedef void (*MqttCallback)(uint32_t* args);
typedef void (*MqttCallback)(MQTT_Client *client);
// 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);
// MQTTY client data structure
typedef struct {
struct MQTT_Client {
struct espconn* pCon; // socket
// connection information
char* host; // MQTT server
@ -89,7 +91,7 @@ typedef struct {
MqttDataCallback cmdDataCb;
// misc
void* user_data;
} MQTT_Client;
};
// Initialize client data structure
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);
// 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);
// Callback when connected

@ -13,74 +13,51 @@
#define DBG(format, ...) do { } while(0)
#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
cmdMqttConnectedCb(uint32_t* args) {
MQTT_Client* client = (MQTT_Client*)args;
cmdMqttConnectedCb(MQTT_Client* client) {
MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Connected connectedCb=%p, disconnectedCb=%p, publishedCb=%p, dataCb=%p\n",
(void*)cb->connectedCb,
(void*)cb->disconnectedCb,
(void*)cb->publishedCb,
(void*)cb->dataCb);
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->connectedCb, 0, 0);
CMD_ResponseEnd(crc);
DBG("MQTT: Connected Cb=%p\n", (void*)cb->connectedCb);
cmdResponseStart(CMD_RESP_CB, cb->connectedCb, 0);
cmdResponseEnd();
}
void ICACHE_FLASH_ATTR
cmdMqttDisconnectedCb(uint32_t* args) {
MQTT_Client* client = (MQTT_Client*)args;
cmdMqttDisconnectedCb(MQTT_Client* client) {
MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Disconnected\n");
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->disconnectedCb, 0, 0);
CMD_ResponseEnd(crc);
DBG("MQTT: Disconnected cb=%p\n", (void*)cb->disconnectedCb);
cmdResponseStart(CMD_RESP_CB, cb->disconnectedCb, 0);
cmdResponseEnd();
}
void ICACHE_FLASH_ATTR
cmdMqttPublishedCb(uint32_t* args) {
MQTT_Client* client = (MQTT_Client*)args;
cmdMqttPublishedCb(MQTT_Client* client) {
MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Published\n");
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->publishedCb, 0, 0);
CMD_ResponseEnd(crc);
DBG("MQTT: Published cb=%p\n", (void*)cb->publishedCb);
cmdResponseStart(CMD_RESP_CB, cb->publishedCb, 0);
cmdResponseEnd();
}
void ICACHE_FLASH_ATTR
cmdMqttDataCb(uint32_t* args, const char* topic, uint32_t topic_len, const char* data, uint32_t data_len) {
uint16_t crc = 0;
MQTT_Client* client = (MQTT_Client*)args;
cmdMqttDataCb(MQTT_Client* client, const char* topic, uint32_t topic_len,
const char* data, uint32_t data_len)
{
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);
crc = CMD_ResponseBody(crc, (uint8_t*)topic, topic_len);
crc = CMD_ResponseBody(crc, (uint8_t*)data, data_len);
CMD_ResponseEnd(crc);
cmdResponseStart(CMD_RESP_CB, cb->dataCb, 2);
cmdResponseBody(topic, topic_len);
cmdResponseBody(data, data_len);
cmdResponseEnd();
}
uint32_t ICACHE_FLASH_ATTR
void ICACHE_FLASH_ATTR
MQTTCMD_Lwt(CmdPacket *cmd) {
CmdRequest req;
CMD_Request(&req, cmd);
cmdRequest(&req, cmd);
if (CMD_GetArgc(&req) != 5)
return 0;
if (cmdGetArgc(&req) != 4) return;
// get mqtt client
uint32_t client_ptr;
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4);
#ifdef MQTT_1_CLIENT
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
if (client->connect_info.will_topic)
@ -91,24 +68,24 @@ MQTTCMD_Lwt(CmdPacket *cmd) {
uint16_t len;
// get topic
len = CMD_ArgLen(&req);
if (len > 128) return 0; // safety check
len = cmdArgLen(&req);
if (len > 128) return; // safety check
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;
// get message
len = CMD_ArgLen(&req);
if (len > 128) return 0; // safety check
len = cmdArgLen(&req);
if (len > 128) return; // safety check
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;
// 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
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",
client->connect_info.will_topic,
@ -118,122 +95,97 @@ MQTTCMD_Lwt(CmdPacket *cmd) {
// trigger a reconnect to set the LWT
MQTT_Reconnect(client);
return 1;
}
uint32_t ICACHE_FLASH_ATTR
void ICACHE_FLASH_ATTR
MQTTCMD_Publish(CmdPacket *cmd) {
CmdRequest req;
CMD_Request(&req, cmd);
cmdRequest(&req, cmd);
if (CMD_GetArgc(&req) != 6)
return 0;
if (cmdGetArgc(&req) != 5) return;
// get mqtt client
uint32_t client_ptr;
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4);
#ifdef MQTT_1_CLIENT
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;
// get topic
len = CMD_ArgLen(&req);
if (len > 128) return 0; // safety check
len = cmdArgLen(&req);
if (len > 128) return; // safety check
uint8_t *topic = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, topic, len);
cmdPopArg(&req, topic, len);
topic[len] = 0;
// get data
len = CMD_ArgLen(&req);
len = cmdArgLen(&req);
uint8_t *data = (uint8_t*)os_zalloc(len+1);
if (!data) { // safety check
os_free(topic);
return 0;
return;
}
CMD_PopArg(&req, data, len);
cmdPopArg(&req, data, len);
data[len] = 0;
uint32_t qos, retain, data_len;
uint16_t data_len;
uint8_t qos, retain;
// get data length
// this isn't used but we have to pull it off the stack
CMD_PopArg(&req, (uint8_t*)&data_len, 4);
cmdPopArg(&req, &data_len, sizeof(data_len));
// get qos
CMD_PopArg(&req, (uint8_t*)&qos, 4);
cmdPopArg(&req, &qos, sizeof(qos));
// 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",
topic,
os_strlen((char*)data),
qos,
retain);
DBG("MQTT: MQTTCMD_Publish topic=%s, data_len=%d, qos=%d, retain=%d\n",
topic, data_len, 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(data);
return 1;
return;
}
uint32_t ICACHE_FLASH_ATTR
void ICACHE_FLASH_ATTR
MQTTCMD_Subscribe(CmdPacket *cmd) {
CmdRequest req;
CMD_Request(&req, cmd);
cmdRequest(&req, cmd);
if (CMD_GetArgc(&req) != 3)
return 0;
if (cmdGetArgc(&req) != 2) return;
// get mqtt client
uint32_t client_ptr;
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4);
#ifdef MQTT_1_CLIENT
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;
// get topic
len = CMD_ArgLen(&req);
if (len > 128) return 0; // safety check
len = cmdArgLen(&req);
if (len > 128) return; // safety check
uint8_t* topic = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, topic, len);
cmdPopArg(&req, topic, len);
topic[len] = 0;
// get qos
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);
MQTT_Subscribe(client, (char*)topic, (uint8_t)qos);
os_free(topic);
return 1;
return;
}
uint32_t ICACHE_FLASH_ATTR
void ICACHE_FLASH_ATTR
MQTTCMD_Setup(CmdPacket *cmd) {
CmdRequest req;
CMD_Request(&req, cmd);
cmdRequest(&req, cmd);
#ifdef MQTT_1_CLIENT
MQTT_Client* client = &mqttClient;
CMD_SkipArg(&req);
CMD_SkipArg(&req);
CMD_SkipArg(&req);
CMD_SkipArg(&req);
CMD_SkipArg(&req);
#else
if (CMD_GetArgc(&req) != 9)
if (cmdGetArgc(&req) != 4) return;
#if 0
if (cmdGetArgc(&req) != 9)
return 0;
// create mqtt client
@ -247,31 +199,31 @@ MQTTCMD_Setup(CmdPacket *cmd) {
uint32_t keepalive, clean_session;
// get client id
len = CMD_ArgLen(&req);
len = cmdArgLen(&req);
if (len > 32) return 0; // safety check
client_id = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, client_id, len);
cmdPopArg(&req, client_id, len);
client_id[len] = 0;
// get username
len = CMD_ArgLen(&req);
len = cmdArgLen(&req);
if (len > 32) return 0; // safety check
user_data = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, user_data, len);
cmdPopArg(&req, user_data, len);
user_data[len] = 0;
// get password
len = CMD_ArgLen(&req);
len = cmdArgLen(&req);
if (len > 32) return 0; // safety check
pass_data = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, pass_data, len);
cmdPopArg(&req, pass_data, len);
pass_data[len] = 0;
// get keepalive
CMD_PopArg(&req, (uint8_t*)&keepalive, 4);
cmdPopArg(&req, (uint8_t*)&keepalive, 4);
// get clean session
CMD_PopArg(&req, (uint8_t*)&clean_session, 4);
cmdPopArg(&req, (uint8_t*)&clean_session, 4);
#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);
#endif
@ -287,31 +239,32 @@ MQTTCMD_Setup(CmdPacket *cmd) {
// create callback
MqttCmdCb* callback = (MqttCmdCb*)os_zalloc(sizeof(MqttCmdCb));
uint32_t cb_data;
CMD_PopArg(&req, (uint8_t*)&cb_data, 4);
callback->connectedCb = cb_data;
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;
cmdPopArg(&req, &callback->connectedCb, 4);
cmdPopArg(&req, &callback->disconnectedCb, 4);
cmdPopArg(&req, &callback->publishedCb, 4);
cmdPopArg(&req, &callback->dataCb, 4);
client->user_data = callback;
DBG("MQTT connectedCb=%lx\n", callback->connectedCb);
client->cmdConnectedCb = cmdMqttConnectedCb;
client->cmdDisconnectedCb = cmdMqttDisconnectedCb;
client->cmdPublishedCb = cmdMqttPublishedCb;
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
MQTTCMD_Connect(CmdPacket *cmd) {
CmdRequest req;
CMD_Request(&req, cmd);
cmdRequest(&req, cmd);
#ifdef MQTT_1_CLIENT
@ -325,12 +278,12 @@ MQTTCMD_Connect(CmdPacket *cmd) {
return 1;
#else
if (CMD_GetArgc(&req) != 4)
if (cmdGetArgc(&req) != 4)
return 0;
// get mqtt client
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;
DBG("MQTT: MQTTCMD_Connect client ptr=%p\n", (void*)client_ptr);
@ -339,17 +292,17 @@ MQTTCMD_Connect(CmdPacket *cmd) {
// get host
if (client->host)
os_free(client->host);
len = CMD_ArgLen(&req);
len = cmdArgLen(&req);
if (len > 128) return 0; // safety check
client->host = (char*)os_zalloc(len + 1);
CMD_PopArg(&req, client->host, len);
cmdPopArg(&req, client->host, len);
client->host[len] = 0;
// get port
CMD_PopArg(&req, (uint8_t*)&client->port, 4);
cmdPopArg(&req, (uint8_t*)&client->port, 4);
// 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",
client->host,
client->port,
@ -363,18 +316,18 @@ MQTTCMD_Connect(CmdPacket *cmd) {
uint32_t ICACHE_FLASH_ATTR
MQTTCMD_Disconnect(CmdPacket *cmd) {
CmdRequest req;
CMD_Request(&req, cmd);
cmdRequest(&req, cmd);
#ifdef MQTT_1_CLIENT
return 1;
#else
if (CMD_GetArgc(&req) != 1)
if (cmdGetArgc(&req) != 1)
return 0;
// get mqtt client
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;
DBG("MQTT: MQTTCMD_Disconnect client ptr=%p\n", (void*)client_ptr);
@ -383,3 +336,4 @@ MQTTCMD_Disconnect(CmdPacket *cmd) {
return 1;
#endif
}
#endif

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

@ -3,6 +3,8 @@
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh
#include "esp8266.h"
#include "c_types.h"
#include "ip_addr.h"
#include "rest.h"
#include "cmd.h"
@ -12,6 +14,27 @@
#define DBG(format, ...) do { } while(0)
#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
// 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
int pi = 0;
int32_t code = -1;
int16_t code = -1;
char statusCode[4] = "\0\0\0\0";
int statusLen = 0;
bool inStatus = false;
@ -69,15 +92,17 @@ tcpclient_recv(void *arg, char *pdata, unsigned short len) {
//if (pi < len && pdata[pi] == '\r') pi++; // hacky!
// collect body and send it
uint16_t crc;
int body_len = len-pi;
DBG("REST: status=%ld, body=%d\n", code, body_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 {
crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 1);
crc = CMD_ResponseBody(crc, (uint8_t*)(pdata+pi), body_len);
CMD_ResponseEnd(crc);
cmdResponseStart(CMD_RESP_CB, client->resp_cb, 2);
cmdResponseBody(&code, sizeof(code));
cmdResponseBody(pdata+pi, body_len>100?100:body_len);
cmdResponseEnd();
#if 0
os_printf("REST: body=");
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);
//else
espconn_disconnect(client->pCon);
}
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) {
CmdRequest req;
uint32_t port, security;
int32_t err = -1; // error code in case of failure
// start parsing the command
CMD_Request(&req, cmd);
if(CMD_GetArgc(&req) != 3) return 0;
cmdRequest(&req, cmd);
if(cmdGetArgc(&req) != 3) goto fail;
err--;
// get the hostname
uint16_t len = CMD_ArgLen(&req);
if (len > 128) return 0; // safety check
uint16_t len = cmdArgLen(&req);
if (len > 128) goto fail; // safety check
err--;
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;
// get the port
if (CMD_PopArg(&req, (uint8_t*)&port, 4)) {
if (cmdPopArg(&req, (uint8_t*)&port, 2)) {
os_free(rest_host);
return 0;
goto fail;
}
err--;
// get the security mode
if (CMD_PopArg(&req, (uint8_t*)&security, 4)) {
if (cmdPopArg(&req, (uint8_t*)&security, 1)) {
os_free(rest_host);
return 0;
goto fail;
}
err--;
// clear connection structures the first time
if (restNum == 0xff) {
@ -224,7 +254,7 @@ REST_Setup(CmdPacket *cmd) {
os_memset(client, 0, sizeof(RestClient));
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->port = port;
@ -249,35 +279,39 @@ REST_Setup(CmdPacket *cmd) {
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) {
CmdRequest req;
CMD_Request(&req, cmd);
cmdRequest(&req, cmd);
if(CMD_GetArgc(&req) != 3)
return 0;
if(cmdGetArgc(&req) != 2) return;
// Get client
uint32_t clientNum;
if (CMD_PopArg(&req, (uint8_t*)&clientNum, 4)) return 0;
if ((clientNum & 0xffff0000) != REST_CB) return 0;
RestClient *client = restClient + ((clientNum & 0xffff) % MAX_REST);
uint32_t clientNum = cmd->value;
RestClient *client = restClient + (clientNum % MAX_REST);
// Get header selector
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
uint16_t len = CMD_ArgLen(&req);
if (len > 256) return 0; //safety check
uint16_t len = cmdArgLen(&req);
if (len > 256) return; //safety check
switch(header_index) {
case HEADER_GENERIC:
if(client->header) os_free(client->header);
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+1] = '\n';
client->header[len+2] = 0;
@ -286,7 +320,7 @@ REST_SetHeader(CmdPacket *cmd) {
case HEADER_CONTENT_TYPE:
if(client->content_type) os_free(client->content_type);
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+1] = '\n';
client->content_type[len+2] = 0;
@ -295,52 +329,50 @@ REST_SetHeader(CmdPacket *cmd) {
case HEADER_USER_AGENT:
if(client->user_agent) os_free(client->user_agent);
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+1] = '\n';
client->user_agent[len+2] = 0;
DBG("REST: Set user_agent: %s\r\n", client->user_agent);
break;
}
return 1;
}
uint32_t ICACHE_FLASH_ATTR
void ICACHE_FLASH_ATTR
REST_Request(CmdPacket *cmd) {
CmdRequest req;
CMD_Request(&req, cmd);
cmdRequest(&req, cmd);
DBG("REST: request");
if (cmd->argc != 2 && cmd->argc != 3) return;
// Get client
uint32_t clientNum;
if (CMD_PopArg(&req, (uint8_t*)&clientNum, 4)) goto fail;
if ((clientNum & 0xffff0000) != REST_CB) goto fail;
clientNum &= 0xffff;
RestClient *client = restClient + clientNum % MAX_REST;
uint32_t clientNum = cmd->value;
RestClient *client = restClient + (clientNum % MAX_REST);
DBG(" #%ld", clientNum);
// Get HTTP method
uint16_t len = CMD_ArgLen(&req);
uint16_t len = cmdArgLen(&req);
if (len > 15) goto fail;
char method[16];
CMD_PopArg(&req, method, len);
cmdPopArg(&req, method, len);
method[len] = 0;
DBG(" method=%s", method);
// Get HTTP path
len = CMD_ArgLen(&req);
len = cmdArgLen(&req);
if (len > 1023) goto fail;
char path[1024];
CMD_PopArg(&req, path, len);
cmdPopArg(&req, path, len);
path[len] = 0;
DBG(" path=%s", path);
// Get HTTP body
uint32_t realLen = 0;
if (CMD_GetArgc(&req) == 3) {
if (cmdGetArgc(&req) == 2) {
realLen = 0;
len = 0;
} else {
CMD_PopArg(&req, (uint8_t*)&realLen, 4);
len = CMD_ArgLen(&req);
if (len > 2048 || realLen > len) goto fail;
realLen = cmdArgLen(&req);
if (realLen > 2048) goto fail;
}
DBG(" bodyLen=%ld", realLen);
@ -367,14 +399,14 @@ REST_Request(CmdPacket *cmd) {
DBG(" hdrLen=%d", client->data_len);
if (realLen > 0) {
CMD_PopArg(&req, client->data + client->data_len, realLen);
cmdPopArg(&req, client->data + client->data_len, realLen);
client->data_len += realLen;
}
DBG("\n");
//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;
espconn_regist_connectcb(client->pCon, tcpclient_connect_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);
}
return 1;
return;
fail:
DBG("\n");
return 0;
}

@ -8,33 +8,10 @@
#ifndef MODULES_API_H_
#define MODULES_API_H_
#include "c_types.h"
#include "ip_addr.h"
#include "cmd.h"
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;
uint32_t REST_Setup(CmdPacket *cmd);
uint32_t REST_Request(CmdPacket *cmd);
uint32_t REST_SetHeader(CmdPacket *cmd);
void REST_Setup(CmdPacket *cmd);
void REST_Request(CmdPacket *cmd);
void REST_SetHeader(CmdPacket *cmd);
#endif /* MODULES_INCLUDE_API_H_ */

@ -445,11 +445,11 @@ serbridgeInitPins()
#endif
if (flashConfig.swap_uart) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4);
PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U);
if (flashConfig.rx_pullup) PIN_PULLUP_EN(PERIPHS_IO_MUX_MTDO_U);
else PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4); // RX
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4); // TX
PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U);
if (flashConfig.rx_pullup) PIN_PULLUP_EN(PERIPHS_IO_MUX_MTCK_U);
else PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U);
system_uart_swap();
} else {
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);
// This SLIP parser does not conform to RFC 1055 https://tools.ietf.org/html/rfc1055,
// instead, it implements the framing implemented in https://github.com/tuanpmt/esp_bridge
// This SLIP parser tries to conform to RFC 1055 https://tools.ietf.org/html/rfc1055.
// 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
// buffer elsewhere as the buffer is immediately reused.
@ -38,48 +37,38 @@ static short slip_len; // accumulated length in slip_buf
// SLIP process a packet or a bunch of debug console chars
static void ICACHE_FLASH_ATTR
slip_process() {
if (slip_len < 1) return;
if (!slip_inpkt) {
// debug console stuff
console_process(slip_buf, slip_len);
} else {
if (slip_len > 2) {
// proper SLIP packet, invoke command processor after checking CRC
//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 rcv = ((uint16_t)slip_buf[slip_len-2]) | ((uint16_t)slip_buf[slip_len-1] << 8);
if (crc == rcv) {
CMD_parse_packet((uint8_t*)slip_buf, slip_len-2);
} else {
os_printf("SLIP: bad CRC, crc=%x rcv=%x\n", crc, rcv);
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);
if (crc == rcv) {
cmdParsePacket((uint8_t*)slip_buf, slip_len-2);
} else {
os_printf("SLIP: bad CRC, crc=%04x rcv=%04x len=%d\n", crc, rcv, slip_len);
for (short i=0; i<slip_len; i++) {
if (slip_buf[i] >= ' ' && slip_buf[i] <= '~') {
DBG("%c", slip_buf[i]);
}
else {
DBG("\\%02X", slip_buf[i]);
}
for (short i=0; i<slip_len; i++) {
if (slip_buf[i] >= ' ' && slip_buf[i] <= '~') {
DBG("%c", slip_buf[i]);
} else {
DBG("\\%02X", slip_buf[i]);
}
DBG("\n");
}
DBG("\n");
}
}
}
#if 0
// determine whether a character is printable or not (or \r \n)
static bool ICACHE_FLASH_ATTR
slip_printable(char c) {
return (c >= ' ' && c <= '~') || c == '\n' || c == '\r';
}
#endif
static void ICACHE_FLASH_ATTR
slip_reset() {
//os_printf("SLIP: reset\n");
slip_inpkt = false;
slip_inpkt = true;
slip_escaped = false;
slip_len = 0;
}
@ -87,38 +76,29 @@ slip_reset() {
// SLIP parse a single character
static void ICACHE_FLASH_ATTR
slip_parse_char(char c) {
if (!slip_inpkt) {
if (c == SLIP_START) {
if (slip_len > 0) console_process(slip_buf, slip_len);
slip_reset();
slip_inpkt = true;
DBG("SLIP: start\n");
return;
if (c == SLIP_END) {
// either start or end of packet, process whatever we may have accumulated
DBG("SLIP: start or end len=%d inpkt=%d\n", slip_len, slip_inpkt);
if (slip_len > 0) {
if (slip_len > 2 && slip_inpkt) slip_process();
else console_process(slip_buf, slip_len);
}
slip_reset();
} else if (slip_escaped) {
// prev char was SLIP_REPL
c = SLIP_ESC(c);
// prev char was SLIP_ESC
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;
} else if (slip_inpkt && c == SLIP_ESC) {
slip_escaped = true;
} else {
switch (c) {
case SLIP_REPL:
slip_escaped = true;
return;
case SLIP_END:
// end of packet, process it and get ready for next one
if (slip_len > 0) slip_process();
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 == 1 && slip_printable(slip_buf[0]) && slip_printable(c)) {
// start of packet and it's a printable character, we're gonna assume that this is console text
slip_inpkt = false;
}
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
@ -130,8 +110,8 @@ slip_parse_buf(char *buf, short length) {
// if we're in-between packets (debug console) then print it now
if (!slip_inpkt && length > 0) {
slip_process();
slip_reset();
console_process(slip_buf, slip_len);
slip_len = 0;
}
}

Loading…
Cancel
Save