diff --git a/.gitignore b/.gitignore index 7b7ee90..a541909 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,9 @@ html_compressed/ esp-link.tgz tve-patch/ yui +espfs/mkespfsimage/mman-win32/mman.o +esp-link.opensdf +esp-link.sdf +espfs/mkespfsimage/mman-win32/libmman.a +.localhistory/ +tools/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 772d424..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "lib/heatshrink"] - path = lib/heatshrink - url = https://github.com/atomicobject/heatshrink.git diff --git a/BOARDS.md b/BOARDS.md index 2d37933..cdb25f5 100644 --- a/BOARDS.md +++ b/BOARDS.md @@ -112,6 +112,66 @@ hook-up a USB-BUB. I recommend jumpering the flash pin (next to GND) to GND and hook the reset pin (6) to the USB-BUB's DTR (should happen automatically). RX&TX also go straight through). +Wifi-link-12 +------------ + +The wifi-link has an esp-12 modulde, an FTDI connector, and a 2-pin power connector. +The underside is marked "Wifi-link-12-v2 Jeelabs TvE2015". +It comes preloaded with the latest version of esp-link V2. + +The ftdi connector has the following pin-out: + - 1: GND (marked top&bottom) + - 2: CTS (used as ISP to program ARM processors, also esp's TX when flashing the esp) + - 3: 5V (marked on bottom) + - 4: TX (from the esp) + - 5: RX (to the esp) + - 6: DTR (used ass reset to program AVR and ARM processors, also esp's RX when flashing the esp) + +Power: the on-board LM3671 switching regulator can provide 600mA while staying cool and is good from about 3.6v to 5.5v! +Connect power to the marked power connector or to the FTDI connector (GND marked, 5V on 3rd pin). Do not exceed 5.5v!! + +It is possible to bypass the on-board regulator and power the wifi-link directly with 3.3v: connect the 3.3v to the FTDI connector ("5v" pin) and switch the jumper on the bottom from 5v to 3v3 (you need to cut the tiny trace on the 5v side and jumper the two pads on the 3v3 side). + +On power-up you should see the green LED on for ~1 second (the yellow should go on too, but +the firmware may not be configured correctly). After that the green should blink according to the +patterns described in the README's LED indicators section. Follow the Wifi configuration details +section thereafter. + +The bottom also has a "sleep" jumper which connects the esp's reset pin with its gpio16 to enable deep-sleep +mode. This jumper is open and must be bridged to use the timed-wake-up feature of the esp's deep-sleep mode. + +To connect a JeeNode to the esp-bridge to flash the AVR or debug it, plug it into the FTDI +port straight, i.e. the component side of the JeeNode and of the wifi-link will be on the top. + +To connect an arduino, jumper gnd, tx, rx, and dtr for reset. The wifi-link-12-v2 has a 2.2K resistor on rx (serial going from AVR to esp) in order to protect it from 5v signals. + +To program the JeeNode or AVR, having set-up the Wifi through the web pages, run avrdude with an +option like "-Pnet:esp-link:23" (you can use an IP address instead of `esp-link`). My test command +line is as follows: +``` +/home/arduino/arduino-1.0.5/hardware/tools/avrdude \ + -C /home/arduino/arduino-1.0.5/hardware/tools/avrdude.conf -DV -patmega328p \ + -Pnet:esp-link:23 -carduino -b115200 -U flash:w:greenhouse.hex:i +``` +If you're using "edam's Arduino makefile" then you can simply set `SERIALDEV=net:esp-link:23` in your +sketch's Makefile. You can also use port 2323 which forces programming mode (no real benefit with avrdude, but can +enable programming PICs). + +Serially reflashing the wifi-link itself (as opposed to the attached uController): +_you should not need to do this!_, in general use the over-the-air reflashing by downloading the latest release. +If you cannot reflash over-the-air and need to reflash serially, follow this process: +- connect TX of the programmer (such as USB-BUB or a FDTI-friend) to DTR (FTDI pin 6) +- connect RX of the programmer to CTS (FTDI pin 2) +- short the flash jumper on the top of the board (push tweezers or a piece of wire into the jumper gap), + the green LED will be on solid when you have contact +- briefly interrupt the power to reset the esp +- it will now be in flash mode: the green LED should be off (assuming you release the flash jumper), + if you see the green LED come on it has rebooted into esp-link and you need to reset it again + (the whole proess take some fiddling, you can solder a real jumper or switch to the flash pads and there's a similar + reset jumper pad on the bottom of the PCB). +- use your favorite programming tool to reflash, you have time if the esp really entered programming mode, it often takes me + 2-3 tries until the programming works + Serial flashing --------------- diff --git a/Makefile b/Makefile index 99d5617..a1fe4c0 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,9 @@ # `ESP_HOSTNAME=my.esp.example.com make wiflash` is an easy way to override a variable # # Makefile heavily adapted to esp-link and wireless flashing by Thorsten von Eicken +# Lots of work, in particular to support windows, by brunnels # Original from esphttpd and others... +#VERBOSE=1 # --------------- toolchain configuration --------------- @@ -19,10 +21,11 @@ XTENSA_TOOLS_ROOT ?= $(abspath ../esp-open-sdk/xtensa-lx106-elf/bin)/ # Base directory of the ESP8266 SDK package, absolute # Typically you'll download from Espressif's BBS, http://bbs.espressif.com/viewforum.php?f=5 -SDK_BASE ?= $(abspath ../esp_iot_sdk_v1.2.0) +SDK_BASE ?= $(abspath ../esp_iot_sdk_v1.4.0) # Esptool.py path and port, only used for 1-time serial flashing # Typically you'll use https://github.com/themadinventor/esptool +# Windows users use the com port i.e: ESPPORT ?= com3 ESPTOOL ?= $(abspath ../esp-open-sdk/esptool/esptool.py) ESPPORT ?= /dev/ttyUSB0 ESPBAUD ?= 460800 @@ -54,6 +57,11 @@ endif # hostname or IP address for wifi flashing ESP_HOSTNAME ?= esp-link +# --------------- chipset configuration --------------- + +# Pick your flash size: "512KB", "1MB", or "4MB" +FLASH_SIZE ?= 4MB + # The pin assignments below are used when the settings in flash are invalid, they # can be changed via the web interface # GPIO pin used to reset attached microcontroller, acative low @@ -65,19 +73,6 @@ LED_CONN_PIN ?= 0 # GPIO pin used for "serial activity" LED, active low LED_SERIAL_PIN ?= 14 -# --------------- esp-link version --------------- - -# This queries git to produce a version string like "esp-link v0.9.0 2015-06-01 34bc76" -# If you don't have a proper git checkout or are on windows, then simply swap for the constant -# Steps to release: create release on github, git pull, git describe --tags to verify you're -# on the release tag, make release, upload esp-link.tgz into the release files -#VERSION ?= "esp-link custom version" -DATE := $(shell date '+%F %T') -BRANCH := $(shell git describe --tags) -SHA := $(shell if git diff --quiet HEAD; then git rev-parse --short HEAD | cut -d"/" -f 3; \ - else echo "development"; fi) -VERSION ?=esp-link $(BRANCH) - $(DATE) - $(SHA) - # --------------- esp-link config options --------------- # If CHANGE_TO_STA is set to "yes" the esp-link module will switch to station mode @@ -85,6 +80,9 @@ VERSION ?=esp-link $(BRANCH) - $(DATE) - $(SHA) CHANGE_TO_STA ?= yes +# Optional Modules +MODULES ?= mqtt rest + # --------------- esphttpd config options --------------- # If GZIP_COMPRESSION is set to "yes" then the static css, js, and html files will be compressed @@ -92,7 +90,6 @@ CHANGE_TO_STA ?= yes # This could speed up the downloading of these files, but might break compatibility with older # web browsers not supporting gzip encoding because Accept-Encoding is simply ignored. # Enable this option if you have large static files to serve (for e.g. JQuery, Twitter bootstrap) -# By default only js, css and html files are compressed using heatshrink. # If you have text based static files with different extensions what you want to serve compressed # then you will need to add the extension to the following places: # - Add the extension to this Makefile at the webpages.espfs target to the find command @@ -104,20 +101,83 @@ CHANGE_TO_STA ?= yes #Static gzipping is disabled by default. GZIP_COMPRESSION ?= yes -# If COMPRESS_W_YUI is set to "yes" then the static css and js files will be compressed with -# yui-compressor. This option works only when GZIP_COMPRESSION is set to "yes". +# If COMPRESS_W_HTMLCOMPRESSOR is set to "yes" then the static css and js files will be compressed with +# htmlcompressor and yui-compressor. This option works only when GZIP_COMPRESSION is set to "yes". +# https://code.google.com/p/htmlcompressor/#For_Non-Java_Projects # http://yui.github.io/yuicompressor/ -#Disabled by default. -COMPRESS_W_YUI ?= yes -YUI-COMPRESSOR ?= yuicompressor-2.4.8.jar - -# If USE_HEATSHRINK is set to "yes" then the espfs files will be compressed with Heatshrink and -# decompressed on the fly while reading the file. -# Because the decompression is done in the esp8266, it does not require any support in the browser. -USE_HEATSHRINK ?= no +# enabled by default. +COMPRESS_W_HTMLCOMPRESSOR ?= yes +HTML_COMPRESSOR ?= htmlcompressor-1.5.3.jar +YUI_COMPRESSOR ?= yuicompressor-2.4.8.jar # -------------- End of config options ------------- +HTML_PATH = $(abspath ./html)/ +WIFI_PATH = $(HTML_PATH)wifi/ + +ifeq ("$(FLASH_SIZE)","512KB") +# Winbond 25Q40 512KB flash, typ for esp-01 thru esp-11 +ESP_SPI_SIZE ?= 0 # 0->512KB (256KB+256KB) +ESP_FLASH_MODE ?= 0 # 0->QIO +ESP_FLASH_FREQ_DIV ?= 0 # 0->40Mhz +ESP_FLASH_MAX ?= 241664 # max bin file for 512KB flash: 236KB +ET_FS ?= 4m # 4Mbit flash size in esptool flash command +ET_FF ?= 40m # 40Mhz flash speed in esptool flash command +ET_BLANK ?= 0x7E000 # where to flash blank.bin to erase wireless settings + +else ifeq ("$(FLASH_SIZE)","1MB") +# ESP-01E +ESP_SPI_SIZE ?= 2 # 2->1MB (512KB+512KB) +ESP_FLASH_MODE ?= 0 # 0->QIO +ESP_FLASH_FREQ_DIV ?= 15 # 15->80MHz +ESP_FLASH_MAX ?= 503808 # max bin file for 1MB flash: 492KB +ET_FS ?= 8m # 8Mbit flash size in esptool flash command +ET_FF ?= 80m # 80Mhz flash speed in esptool flash command +ET_BLANK ?= 0xFE000 # where to flash blank.bin to erase wireless settings + +else ifeq ("$(FLASH_SIZE)","2MB") +# Manuf 0xA1 Chip 0x4015 found on wroom-02 modules +# Here we're using two partitions of approx 0.5MB because that's what's easily available in terms +# of linker scripts in the SDK. Ideally we'd use two partitions of approx 1MB, the remaining 2MB +# cannot be used for code (esp8266 limitation). +ESP_SPI_SIZE ?= 4 # 6->4MB (1MB+1MB) or 4->4MB (512KB+512KB) +ESP_FLASH_MODE ?= 0 # 0->QIO, 2->DIO +ESP_FLASH_FREQ_DIV ?= 15 # 15->80Mhz +ESP_FLASH_MAX ?= 503808 # max bin file for 512KB flash partition: 492KB +#ESP_FLASH_MAX ?= 1028096 # max bin file for 1MB flash partition: 1004KB +ET_FS ?= 16m # 16Mbit flash size in esptool flash command +ET_FF ?= 80m # 80Mhz flash speed in esptool flash command +ET_BLANK ?= 0x1FE000 # where to flash blank.bin to erase wireless settings + +else +# Winbond 25Q32 4MB flash, typ for esp-12 +# Here we're using two partitions of approx 0.5MB because that's what's easily available in terms +# of linker scripts in the SDK. Ideally we'd use two partitions of approx 1MB, the remaining 2MB +# cannot be used for code (esp8266 limitation). +ESP_SPI_SIZE ?= 4 # 6->4MB (1MB+1MB) or 4->4MB (512KB+512KB) +ESP_FLASH_MODE ?= 0 # 0->QIO, 2->DIO +ESP_FLASH_FREQ_DIV ?= 15 # 15->80Mhz +ESP_FLASH_MAX ?= 503808 # max bin file for 512KB flash partition: 492KB +#ESP_FLASH_MAX ?= 1028096 # max bin file for 1MB flash partition: 1004KB +ET_FS ?= 32m # 32Mbit flash size in esptool flash command +ET_FF ?= 80m # 80Mhz flash speed in esptool flash command +ET_BLANK ?= 0x3FE000 # where to flash blank.bin to erase wireless settings +endif + +# --------------- esp-link version --------------- + +# This queries git to produce a version string like "esp-link v0.9.0 2015-06-01 34bc76" +# If you don't have a proper git checkout or are on windows, then simply swap for the constant +# Steps to release: create release on github, git pull, git describe --tags to verify you're +# on the release tag, make release, upload esp-link.tgz into the release files +#VERSION ?= "esp-link custom version" +DATE := $(shell date '+%F %T') +BRANCH := $(shell if git diff --quiet HEAD; then git describe --tags; \ + else git symbolic-ref --short HEAD; fi) +SHA := $(shell if git diff --quiet HEAD; then git rev-parse --short HEAD | cut -d"/" -f 3; \ + else echo "development"; fi) +VERSION ?=esp-link $(BRANCH) - $(DATE) - $(SHA) + # Output directors to store intermediate compiled files # relative to the project directory BUILD_BASE = build @@ -129,15 +189,28 @@ TARGET = httpd # espressif tool to concatenate sections for OTA upload using bootloader v1.2+ APPGEN_TOOL ?= gen_appbin.py +CFLAGS= + +# set defines for optional modules +ifneq (,$(findstring mqtt,$(MODULES))) + CFLAGS += -DMQTT +endif + +ifneq (,$(findstring rest,$(MODULES))) + CFLAGS += -DREST +endif + # which modules (subdirectories) of the project to include in compiling -MODULES = espfs httpd user serial -EXTRA_INCDIR = include . # lib/heatshrink/ +LIBRARIES_DIR = libraries +MODULES += espfs httpd user serial cmd esp-link +MODULES += $(foreach sdir,$(LIBRARIES_DIR),$(wildcard $(sdir)/*)) +EXTRA_INCDIR = include . # libraries used in this project, mainly provided by the SDK -LIBS = c gcc hal phy pp net80211 wpa main lwip +LIBS = c gcc hal phy pp net80211 wpa main lwip # compiler flags using during compilation of source files -CFLAGS = -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ +CFLAGS += -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ -nostdlib -mlongcalls -mtext-section-literals -ffunction-sections -fdata-sections \ -D__ets__ -DICACHE_FLASH -D_STDINT_H -Wno-address -DFIRMWARE_SIZE=$(ESP_FLASH_MAX) \ -DMCU_RESET_PIN=$(MCU_RESET_PIN) -DMCU_ISP_PIN=$(MCU_ISP_PIN) \ @@ -154,7 +227,7 @@ LD_SCRIPT2 := build/eagle.esphttpd2.v6.ld # various paths from the SDK used in this project SDK_LIBDIR = lib -SDK_LDDIR = ld +SDK_LDDIR = ld SDK_INCDIR = include include/json SDK_TOOLSDIR = tools @@ -162,8 +235,8 @@ SDK_TOOLSDIR = tools CC := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc AR := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-ar LD := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc -OBJCP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objcopy -OBJDP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objdump +OBJCP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objcopy +OBJDP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objdump #### @@ -173,17 +246,17 @@ BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES)) SDK_LIBDIR := $(addprefix $(SDK_BASE)/,$(SDK_LIBDIR)) SDK_LDDIR := $(addprefix $(SDK_BASE)/,$(SDK_LDDIR)) SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR)) -SDK_TOOLS := $(addprefix $(SDK_BASE)/,$(SDK_TOOLSDIR)) +SDK_TOOLS := $(addprefix $(SDK_BASE)/,$(SDK_TOOLSDIR)) APPGEN_TOOL := $(addprefix $(SDK_TOOLS)/,$(APPGEN_TOOL)) -SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c)) -OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC)) $(BUILD_BASE)/espfs_img.o +SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c)) +OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC)) $(BUILD_BASE)/espfs_img.o LIBS := $(addprefix -l,$(LIBS)) APP_AR := $(addprefix $(BUILD_BASE)/,$(TARGET)_app.a) USER1_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).user1.out) USER2_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).user2.out) -INCDIR := $(addprefix -I,$(SRC_DIR)) +INCDIR := $(addprefix -I,$(SRC_DIR)) EXTRA_INCDIR := $(addprefix -I,$(EXTRA_INCDIR)) MODULE_INCDIR := $(addsuffix /include,$(INCDIR)) @@ -196,16 +269,20 @@ Q := @ vecho := @echo endif -ifeq ("$(GZIP_COMPRESSION)","yes") -CFLAGS += -DGZIP_COMPRESSION +ifneq ($(strip $(STA_SSID)),) +CFLAGS += -DSTA_SSID="$(STA_SSID)" endif -ifeq ("$(USE_HEATSHRINK)","yes") -CFLAGS += -DESPFS_HEATSHRINK +ifneq ($(strip $(STA_PASS)),) +CFLAGS += -DSTA_PASS="$(STA_PASS)" +endif + +ifeq ("$(GZIP_COMPRESSION)","yes") +CFLAGS += -DGZIP_COMPRESSION endif ifeq ("$(CHANGE_TO_STA)","yes") -CFLAGS += -DCHANGE_TO_STA +CFLAGS += -DCHANGE_TO_STA endif vpath %.c $(SRC_DIR) @@ -273,35 +350,63 @@ $(BUILD_DIR): wiflash: all ./wiflash $(ESP_HOSTNAME) $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin +baseflash: all + $(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash 0x01000 $(FW_BASE)/user1.bin + flash: all - $(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash \ + $(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash -fs $(ET_FS) -ff $(ET_FF) \ 0x00000 "$(SDK_BASE)/bin/boot_v1.4(b1).bin" 0x01000 $(FW_BASE)/user1.bin \ - 0x7E000 $(SDK_BASE)/bin/blank.bin - -yui/$(YUI-COMPRESSOR): - $(Q) mkdir -p yui - cd yui; wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI-COMPRESSOR) - -ifeq ("$(COMPRESS_W_YUI)","yes") -$(BUILD_BASE)/espfs_img.o: yui/$(YUI-COMPRESSOR) + $(ET_BLANK) $(SDK_BASE)/bin/blank.bin + +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 + 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 + +ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") +$(BUILD_BASE)/espfs_img.o: tools/$(HTML_COMPRESSOR) endif $(BUILD_BASE)/espfs_img.o: html/ html/wifi/ espfs/mkespfsimage/mkespfsimage - $(Q) rm -rf html_compressed; - $(Q) cp -r html html_compressed; - $(Q) for file in `find html_compressed -type f -name "*.htm*"`; do \ - cat html_compressed/head- $$file >$${file}-; \ - mv $$file- $$file; \ - done -ifeq ("$(COMPRESS_W_YUI)","yes") + $(Q) rm -rf html_compressed; mkdir html_compressed; mkdir html_compressed/wifi; + $(Q) cp -r html/*.ico html_compressed; + $(Q) cp -r html/*.css html_compressed; + $(Q) cp -r html/*.js html_compressed; + $(Q) cp -r html/wifi/*.png html_compressed/wifi; + $(Q) cp -r html/wifi/*.js html_compressed/wifi; +ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") + $(Q) echo "Compression assets with htmlcompressor. This may take a while..." + $(Q) java -jar tools/$(HTML_COMPRESSOR) \ + -t html --remove-surrounding-spaces max --remove-quotes --remove-intertag-spaces \ + -o $(abspath ./html_compressed)/ \ + $(HTML_PATH)head- \ + $(HTML_PATH)*.html + $(Q) java -jar tools/$(HTML_COMPRESSOR) \ + -t html --remove-surrounding-spaces max --remove-quotes --remove-intertag-spaces \ + -o $(abspath ./html_compressed)/wifi/ \ + $(WIFI_PATH)*.html $(Q) echo "Compression assets with yui-compressor. This may take a while..." $(Q) for file in `find html_compressed -type f -name "*.js"`; do \ - java -jar yui/$(YUI-COMPRESSOR) $$file --nomunge --line-break 40 -o $$file; \ + java -jar tools/$(YUI_COMPRESSOR) $$file -o $$file; \ done $(Q) for file in `find html_compressed -type f -name "*.css"`; do \ - java -jar yui/$(YUI-COMPRESSOR) $$file -o $$file; \ + java -jar tools/$(YUI_COMPRESSOR) $$file -o $$file; \ done endif +ifeq (,$(findstring mqtt,$(MODULES))) + $(Q) rm -rf html_compressed/mqtt.html + $(Q) rm -rf html_compressed/mqtt.js +endif + $(Q) for file in `find html_compressed -type f -name "*.htm*"`; do \ + cat html_compressed/head- $$file >$${file}-; \ + mv $$file- $$file; \ + done + $(Q) rm html_compressed/head- $(Q) cd html_compressed; find . \! -name \*- | ../espfs/mkespfsimage/mkespfsimage > ../build/espfs.img; cd ..; $(Q) ls -sl build/espfs.img $(Q) cd build; $(OBJCP) -I binary -O elf32-xtensa-le -B xtensa --rename-section .data=.espfs \ @@ -332,13 +437,16 @@ build/eagle.esphttpd2.v6.ld: $(SDK_LDDIR)/eagle.app.v6.new.1024.app2.ld endif espfs/mkespfsimage/mkespfsimage: espfs/mkespfsimage/ - $(Q) $(MAKE) -C espfs/mkespfsimage USE_HEATSHRINK="$(USE_HEATSHRINK)" GZIP_COMPRESSION="$(GZIP_COMPRESSION)" + $(Q) $(MAKE) -C espfs/mkespfsimage GZIP_COMPRESSION="$(GZIP_COMPRESSION)" release: all - $(Q) rm -rf release; mkdir -p release/esp-link + $(Q) rm -rf release; mkdir -p release/esp-link-$(BRANCH) + $(Q) egrep -a 'esp-link [a-z0-9.]+ - 201' $(FW_BASE)/user1.bin | cut -b 1-80 + $(Q) egrep -a 'esp-link [a-z0-9.]+ - 201' $(FW_BASE)/user2.bin | cut -b 1-80 $(Q) cp $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin $(SDK_BASE)/bin/blank.bin \ - "$(SDK_BASE)/bin/boot_v1.4(b1).bin" wiflash release/esp-link - $(Q) tar zcf esp-link.tgz -C release esp-link + "$(SDK_BASE)/bin/boot_v1.4(b1).bin" wiflash release/esp-link-$(BRANCH) + $(Q) tar zcf esp-link-$(BRANCH).tgz -C release esp-link-$(BRANCH) + $(Q) echo "Release file: esp-link-$(BRANCH).tgz" $(Q) rm -rf release clean: @@ -348,7 +456,7 @@ clean: $(Q) make -C espfs/mkespfsimage/ clean $(Q) rm -rf $(FW_BASE) $(Q) rm -f webpages.espfs -ifeq ("$(COMPRESS_W_YUI)","yes") +ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") $(Q) rm -rf html_compressed endif diff --git a/README.md b/README.md index 78d0027..d90fa9b 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,52 @@ ESP-LINK ======== -This firmware implements a transparent bridge between Wifi and serial using an ESP8266 module. -It also provides support for flash-programming Arduino/AVR microcontrollers as well as -LPC800-series and other ARM microcontrollers via the ESP8266. +This firmware connects an attached micro-controller to the internet using a ESP8266 Wifi module. +It implements a number of features: +- transparent bridge between Wifi and serial, useful for debugging or inputting into a uC +- flash-programming attached Arduino/AVR microcontrollers as well as LPC800-series and other + ARM microcontrollers via Wifi +- 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) The firmware includes a tiny HTTP server based on [esphttpd](http://www.esp8266.com/viewforum.php?f=34) with a simple web interface, many thanks to Jeroen Domburg for making it available! +Many thanks to https://github.com/brunnels for contributions around the espduino functionality. +###[Releases](https://github.com/jeelabs/esp-link/releases) + +- [V2.0.beta2](https://github.com/jeelabs/esp-link/releases/tag/v2.0.beta2) has REST support but + requires a 1MByte or 4MByte ESP8266 flash, e.g. esp-12 or wroom-02 +- [V1.0.1](https://github.com/jeelabs/esp-link/releases/tag/v1.0.1) is _stable_ + and has the web server, transparent bridge, flash-programming support, but lacks + the REST and upcoming MQTT support. V1 works with 512KB flash, e.g. esp-1, esp-3, ... + +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) -###[Latest release](https://github.com/jeelabs/esp-link/releases) +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. + +The next level is to use the outbound connectivity of esp-link in the uC code. For example, the +uC can use REST requests to services like thingspeak.com to send sensor values that then get +stored and plotted by the external service. +The uC can also use REST requests to retrieve simple configuration +information or push other forms of notifications. (MQTT functionality is forthcoming.) + +An additional option is to add code to esp-link to customize it and put all the communication +code into esp-link and only keep simple sensor/actuator control in the attached uC. In this +mode the attached uC sends custom commands to esp-link with sensor/acturator info and +registers a set of callbacks with esp-link that control sensors/actuators. This way, custom +commands in esp-link can receive MQTT messages, make simple callbacks into the uC to get sensor +values or change actuators, and then respond back with MQTT. The way this is architected is that +the attached uC registers callbacks at start-up such that the code in the esp doesn't need to +know which exact sensors/actuators the attached uC has, it learns thta through the initial +callback registration. Eye Candy --------- @@ -26,7 +61,7 @@ attached microcontroller, and the pin assignments card: Hardware info ------------- This firmware is designed for esp8266 modules which have most ESP I/O pins available and -512KB flash. +at least 1MB flash. (The V1 firmware supports modules with 512KB flash). The default connections are: - URXD: connect to TX of microcontroller - UTXD: connect to RX of microcontroller @@ -37,6 +72,9 @@ The default connections are: If you are using an FTDI connector, GPIO12 goes to DTR and GPIO13 goes to CTS. +If you are using an esp-12 module, you can avoid the initial boot message from the esp8266 +bootloader by using the swap-pins option. This swaps the esp8266 TX/RX to gpio15/gpio13 respectively. + The GPIO pin assignments can be changed dynamically in the web UI and are saved in flash. Initial flashing @@ -215,7 +253,28 @@ modes are supported that can be set in the web UI (and the mode is saved in flas Note that even if the UART log is always off the bootloader prints to uart0 whenever the esp8266 comes out of reset. This cannot be disabled. +Outbound TCP connections +------------------------ +The attached micro-controller can open outbound TCP connections using a simple +[serial protocol](https://gist.github.com/tve/a46c44bf1f6b42bc572e). +More info and sample code forthcoming... + +Outbound HTTP REST requests +--------------------------- +The V2 versions of esp-link support the espduino SLIP protocol that supports simple outbound +HTTP REST requests. 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. +The responses back use a callback address in the attached microcontroller code, i.e., the +command sent by the uC contains a callback address and the response from the esp8266 starts +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. +Support for MQTT is forthcoming. + +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). + Contact ------- If you find problems with esp-link, please create a github issue. If you have a question, please -use the gitter link at the top of this page. +use the gitter chat link at the top of this page. diff --git a/cmd/cmd.c b/cmd/cmd.c new file mode 100644 index 0000000..e7ec63f --- /dev/null +++ b/cmd/cmd.c @@ -0,0 +1,211 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// +// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh + +#include "esp8266.h" +#include "cmd.h" +#include "crc16.h" +#include "uart.h" + +extern const CmdList commands[]; + +//===== ESP -> Serial responses + +static void ICACHE_FLASH_ATTR +CMD_ProtoWrite(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)); + break; + default: + uart0_write_char(data); + } +} + +static void ICACHE_FLASH_ATTR +CMD_ProtoWriteBuf(uint8_t *data, short len) { + while (len--) CMD_ProtoWrite(*data++); +} + +// 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; +} + +// 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); + + CMD_ProtoWriteBuf(data, len); + crc_in = crc16_data(data, len, crc_in); + + if (pad_len > len) { + uint32_t temp = 0; + CMD_ProtoWriteBuf((uint8_t*)&temp, pad_len-len); + crc_in = crc16_data((uint8_t*)&temp, pad_len-len, crc_in); + } + + return crc_in; +} + +// Ends a response +void ICACHE_FLASH_ATTR +CMD_ResponseEnd(uint16_t crc) { + CMD_ProtoWriteBuf((uint8_t*)&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; + // Iterate through the command table and call the appropriate function + while (scp->sc_function != NULL) { + if(scp->sc_name == packet->cmd) { + //os_printf("CMD: Dispatching cmd=%d\n", packet->cmd); + // call command function + uint32_t ret = scp->sc_function(packet); + // if requestor asked for a response, send it + if (packet->_return){ +#ifdef CMD_DBG + os_printf("CMD: Response: 0x%lx, cmd: %d\r\n", ret, packet->cmd); +#endif + crc = CMD_ResponseStart(packet->cmd, 0, ret, 0); + CMD_ResponseEnd(crc); + } else { +#ifdef CMD_DBG + os_printf("CMD: no response (%lu)\n", packet->_return); +#endif + } + return ret; + } + scp++; + } +#ifdef CMD_DBG + os_printf("CMD: cmd=%d not found\n", packet->cmd); +#endif + return 0; +} + +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", +}; + +// Parse a packet and print info about it +void ICACHE_FLASH_ATTR +CMD_parse_packet(uint8_t *buf, short len) { + // minimum command length + if (len < 12) return; + + // init pointers into buffer + CmdPacket *packet = (CmdPacket*)buf; + uint8_t *data_ptr = (uint8_t*)&packet->args; + uint8_t *data_limit = data_ptr+len; +#ifdef CMD_DBG + uint16_t argn = 0; + os_printf("CMD: cmd=%d(%s) argc=%d cb=%p ret=%lu\n", + packet->cmd, cmd_names[packet->cmd], packet->argc, (void *)packet->callback, packet->_return); +#endif + +#if 0 + // print out arguments + uint16_t argc = packet->argc; + while (data_ptr+2 < data_limit && argc--) { + short l = *(uint16_t*)data_ptr; + os_printf("CMD: arg[%d] len=%d:", argn++, l); + data_ptr += 2; + while (data_ptr < data_limit && l--) { + os_printf(" %02X", *data_ptr++); + } + os_printf("\n"); + } +#endif + + if (data_ptr <= data_limit) { + CMD_Exec(commands, packet); + } else { +#ifdef CMD_DBG + os_printf("CMD: packet length overrun, parsing arg %d\n", argn-1); +#endif + } +} + +//===== Helpers to parse a command packet + +// Fill out a CmdRequest struct given a CmdPacket +void ICACHE_FLASH_ATTR +CMD_Request(CmdRequest *req, CmdPacket* cmd) { + req->cmd = cmd; + req->arg_num = 0; + req->arg_ptr = (uint8_t*)&cmd->args; +} + +// Return the number of arguments given a command struct +uint32_t ICACHE_FLASH_ATTR +CMD_GetArgc(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) { + uint16_t length; + + if (req->arg_num >= req->cmd->argc) + return -1; + + length = *(uint16_t*)req->arg_ptr; + if (length != len) return -1; // safety check + + req->arg_ptr += 2; + os_memcpy(data, req->arg_ptr, length); + req->arg_ptr += length; + + req->arg_num ++; + return 0; +} + +// Skip the next argument +void ICACHE_FLASH_ATTR +CMD_SkipArg(CmdRequest *req) { + uint16_t length; + + if (req->arg_num >= req->cmd->argc) return; + + length = *(uint16_t*)req->arg_ptr; + + req->arg_ptr += 2; + req->arg_ptr += length; + req->arg_num ++; +} + +// Return the length of the next argument +uint16_t ICACHE_FLASH_ATTR +CMD_ArgLen(CmdRequest *req) { + return *(uint16_t*)req->arg_ptr; +} diff --git a/cmd/cmd.h b/cmd/cmd.h new file mode 100644 index 0000000..ecd8140 --- /dev/null +++ b/cmd/cmd.h @@ -0,0 +1,106 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// +// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh + +#ifndef CMD_H +#define CMD_H +#include + +// Escape chars used by tuanpmt, dunno why he didn't use std ones... +#define SLIP_START 0x7E +#define SLIP_END 0x7F +#define SLIP_REPL 0x7D +#define SLIP_ESC(x) (x ^ 0x20) + +#if 0 +// Proper SLIP escape chars from RFC +#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 + uint8_t data[0]; // really data[len] +} CmdArg; + +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 + CmdArg args[0]; // really args[argc] +} CmdPacket; + +typedef struct { + CmdPacket *cmd; // command packet header + uint32_t arg_num; // number of args parsed + uint8_t *arg_ptr; // pointer to ?? +} CmdRequest; + +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_REST_REQUEST, + CMD_REST_SETHEADER, + CMD_REST_EVENTS, + CMD_CB_ADD, // 15 + CMD_CB_EVENTS +} CmdName; + +typedef uint32_t (*cmdfunc_t)(CmdPacket *cmd); + +typedef struct { + CmdName sc_name; + cmdfunc_t sc_function; +} CmdList; + +#define CMD_CBNLEN 16 +typedef struct { + char name[CMD_CBNLEN]; + uint32_t callback; +} cmdCallback; + +// Used by slip protocol to cause parsing of a received packet +void CMD_parse_packet(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); + +// 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); +// Ends a response +void CMD_ResponseEnd(uint16_t crc); + +//void CMD_Response(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc, CmdArg* args[]); + +// Requests + +// Fill out a CmdRequest struct given a CmdPacket +void CMD_Request(CmdRequest *req, CmdPacket* cmd); +// Return the number of arguments given a request +uint32_t CMD_GetArgc(CmdRequest *req); +// Return the length of the next argument +uint16_t CMD_ArgLen(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); +// Skip next arg +void CMD_SkipArg(CmdRequest *req); + +#endif diff --git a/cmd/handlers.c b/cmd/handlers.c new file mode 100644 index 0000000..9642856 --- /dev/null +++ b/cmd/handlers.c @@ -0,0 +1,168 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// +// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh + +#include "esp8266.h" +#include "cmd.h" +#include +#ifdef MQTT +#include +#endif +#ifdef REST +#include +#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); + +// keep track of last status sent to uC so we can notify it when it changes +static uint8_t lastWifiStatus = wifiIsDisconnected; +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}, +#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}, +#endif +#ifdef REST + {CMD_REST_SETUP, REST_Setup}, + {CMD_REST_REQUEST, REST_Request}, + {CMD_REST_SETHEADER, REST_SetHeader}, +#endif + {CMD_CB_ADD, CMD_AddCallback}, + {CMD_NULL, NULL} +}; + +// 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; +} + +static uint32_t ICACHE_FLASH_ATTR +CMD_AddCb(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, + // (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; +#ifdef CMD_DBG + os_printf("CMD_AddCb: cb %s added at index %d\n", callbacks[i].name, i); +#endif + return 1; + } + } + return 0; +} + +cmdCallback* ICACHE_FLASH_ATTR +CMD_GetCbByName(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, + // (void *)callbacks[i].callback); + // if callback doesn't exist or it's null + if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0) { +#ifdef CMD_DBG + os_printf("CMD_GetCbByName: cb %s found at index %d\n", name, i); +#endif + return &callbacks[i]; + } + } + os_printf("CMD_GetCbByName: cb %s not found\n", name); + return 0; +} + +// Callback from wifi subsystem to notify us of status changes +static void ICACHE_FLASH_ATTR +CMD_WifiCb(uint8_t wifiStatus) { + if (wifiStatus != lastWifiStatus){ +#ifdef CMD_DBG + os_printf("CMD_WifiCb: wifiStatus=%d\n", wifiStatus); +#endif + lastWifiStatus = wifiStatus; + cmdCallback *wifiCb = CMD_GetCbByName("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); + } + } +} + +// Command handler for Wifi connect command +static uint32_t ICACHE_FLASH_ATTR +CMD_WifiConnect(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&req, cmd); + if(cmd->argc != 2 || cmd->callback == 0) + return 0; + + if (!wifiCbAdded) { + wifiAddStateChangeCb(CMD_WifiCb); // register our callback with wifi subsystem + wifiCbAdded = true; + } + CMD_AddCb("wifiCb", (uint32_t)cmd->callback); // save the MCU's callback + lastWifiStatus = 0xff; // set to invalid value so we immediately send status cb in all cases + CMD_WifiCb(wifiState); + + return 1; +} + +// 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) { + CmdRequest req; + CMD_Request(&req, cmd); + if (cmd->argc != 1 || cmd->callback == 0) + return 0; + + 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; + name[len] = 0; +#ifdef CMD_DBG + os_printf("CMD_AddCallback: name=%s\n", name); +#endif + + return CMD_AddCb(name, (uint32_t)cmd->callback); // save the sensor callback +} diff --git a/esp-link.sln b/esp-link.sln new file mode 100644 index 0000000..fb7b468 --- /dev/null +++ b/esp-link.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "esp-link", "esp-link.vcxproj", "{A92F0CAA-F89B-4F78-AD2A-A042429BD87F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Release|ARM = Release|ARM + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Debug|ARM.ActiveCfg = Debug|ARM + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Debug|ARM.Build.0 = Debug|ARM + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Release|ARM.ActiveCfg = Release|ARM + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Release|ARM.Build.0 = Release|ARM + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/esp-link.vcxproj b/esp-link.vcxproj new file mode 100644 index 0000000..57e3a8d --- /dev/null +++ b/esp-link.vcxproj @@ -0,0 +1,157 @@ + + + + + Debug + ARM + + + Release + ARM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F} + MakeFileProj + + + + Makefile + + + + + + + + + + + __ets__;_STDINT_H;ICACHE_FLASH;__MINGW32__;__WIN32__ + .\rest;.\esp-link;.\mqtt;.\cmd;.\serial;.\user;.\espfs;.\httpd;.\include;..\esp_iot_sdk_v1.3.0\include;..\xtensa-lx106-elf\xtensa-lx106-elf\include;c:\tools\mingw64\x86_64-w64-mingw32\include;c:\tools\mingw64\lib\gcc\x86_64-w64-mingw32\4.8.3\include + + + + + + + bin + build + + + espmake all wiflash + espmake clean all + espmake clean + + + espmake clean all wiflash + espmake clean all + espmake clean + + + + + + + + + + \ No newline at end of file diff --git a/esp-link/cgi.c b/esp-link/cgi.c new file mode 100644 index 0000000..0a52538 --- /dev/null +++ b/esp-link/cgi.c @@ -0,0 +1,163 @@ +/* +Some random cgi routines. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + * Heavily modified and enhanced by Thorsten von Eicken in 2015 + * ---------------------------------------------------------------------------- + */ + + +#include +#include "cgi.h" + +void noCacheHeaders(HttpdConnData *connData, int code) { + httpdStartResponse(connData, code); + httpdHeader(connData, "Cache-Control", "no-cache, no-store, must-revalidate"); + httpdHeader(connData, "Pragma", "no-cache"); + httpdHeader(connData, "Expires", "0"); +} + +void ICACHE_FLASH_ATTR +jsonHeader(HttpdConnData *connData, int code) { + noCacheHeaders(connData, code); + httpdHeader(connData, "Content-Type", "application/json"); + httpdEndHeaders(connData); +} + +void ICACHE_FLASH_ATTR +errorResponse(HttpdConnData *connData, int code, char *message) { + noCacheHeaders(connData, code); + httpdEndHeaders(connData); + httpdSend(connData, message, -1); +#ifdef CGI_DBG + os_printf("HTTP %d error response: \"%s\"\n", code, message); +#endif +} + +// look for the HTTP arg 'name' and store it at 'config' with max length 'max_len' (incl +// terminating zero), returns -1 on error, 0 if not found, 1 if found and OK +int8_t ICACHE_FLASH_ATTR +getStringArg(HttpdConnData *connData, char *name, char *config, int max_len) { + char buff[128]; + int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); + if (len < 0) return 0; // not found, skip + if (len >= max_len) { + os_sprintf(buff, "Value for %s too long (%d > %d allowed)", name, len, max_len-1); + errorResponse(connData, 400, buff); + return -1; + } + strcpy(config, buff); + return 1; +} + +int8_t ICACHE_FLASH_ATTR +getBoolArg(HttpdConnData *connData, char *name, bool*config) { + char buff[64]; + int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); + if (len < 0) return 0; // not found, skip + + if (strcmp(buff, "1") == 0 || strcmp(buff, "true") == 0) { + *config = true; + return 1; + } + if (strcmp(buff, "0") == 0 || strcmp(buff, "false") == 0) { + *config = false; + return 1; + } + os_sprintf(buff, "Invalid value for %s", name); + errorResponse(connData, 400, buff); + return -1; +} + +uint8_t ICACHE_FLASH_ATTR +UTILS_StrToIP(const char* str, void *ip){ + /* The count of the number of bytes processed. */ + int i; + /* A pointer to the next digit to process. */ + const char * start; + + start = str; + for (i = 0; i < 4; i++) { + /* The digit being processed. */ + char c; + /* The value of this byte. */ + int n = 0; + while (1) { + c = *start; + start++; + if (c >= '0' && c <= '9') { + n *= 10; + n += c - '0'; + } + /* We insist on stopping at "." if we are still parsing + the first, second, or third numbers. If we have reached + the end of the numbers, we will allow any character. */ + else if ((i < 3 && c == '.') || i == 3) { + break; + } + else { + return 0; + } + } + if (n >= 256) { + return 0; + } + ((uint8_t*)ip)[i] = n; + } + return 1; +} + +#define TOKEN(x) (os_strcmp(token, x) == 0) +#if 0 +// Handle system information variables and print their value, returns the number of +// characters appended to buff +int ICACHE_FLASH_ATTR printGlobalInfo(char *buff, int buflen, char *token) { + if (TOKEN("si_chip_id")) { + return os_sprintf(buff, "0x%x", system_get_chip_id()); + } else if (TOKEN("si_freeheap")) { + return os_sprintf(buff, "%dKB", system_get_free_heap_size()/1024); + } else if (TOKEN("si_uptime")) { + uint32 t = system_get_time() / 1000000; // in seconds + return os_sprintf(buff, "%dd%dh%dm%ds", t/(24*3600), (t/(3600))%24, (t/60)%60, t%60); + } else if (TOKEN("si_boot_version")) { + return os_sprintf(buff, "%d", system_get_boot_version()); + } else if (TOKEN("si_boot_address")) { + return os_sprintf(buff, "0x%x", system_get_userbin_addr()); + } else if (TOKEN("si_cpu_freq")) { + return os_sprintf(buff, "%dMhz", system_get_cpu_freq()); + } else { + return 0; + } +} +#endif + +extern char *esp_link_version; // in user_main.c + +int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + char buff[1024]; + // don't use jsonHeader so the response does get cached + httpdStartResponse(connData, 200); + httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); + httpdHeader(connData, "Content-Type", "application/json"); + httpdEndHeaders(connData); + // construct json response + os_sprintf(buff, + "{\"menu\": [\"Home\", \"/home.html\", " + "\"Wifi\", \"/wifi/wifi.html\"," + "\"\xC2\xB5" "C Console\", \"/console.html\", " +#ifdef MQTT + "\"REST/MQTT\", \"/mqtt.html\"," +#endif + "\"Debug log\", \"/log.html\" ],\n" + " \"version\": \"%s\" }", esp_link_version); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} diff --git a/esp-link/cgi.h b/esp-link/cgi.h new file mode 100644 index 0000000..66757fe --- /dev/null +++ b/esp-link/cgi.h @@ -0,0 +1,22 @@ +#ifndef CGI_H +#define CGI_H + +#include +#include "httpd.h" + +void jsonHeader(HttpdConnData *connData, int code); +void errorResponse(HttpdConnData *connData, int code, char *message); + +// Get the HTTP query-string param 'name' and store it at 'config' with max length +// 'max_len' (incl terminating zero), returns -1 on error, 0 if not found, 1 if found +int8_t getStringArg(HttpdConnData *connData, char *name, char *config, int max_len); + +// Get the HTTP query-string param 'name' and store it boolean value at 'config', +// supports 1/true and 0/false, returns -1 on error, 0 if not found, 1 if found +int8_t getBoolArg(HttpdConnData *connData, char *name, bool*config); + +int cgiMenu(HttpdConnData *connData); + +uint8_t UTILS_StrToIP(const char* str, void *ip); + +#endif diff --git a/user/cgiflash.c b/esp-link/cgiflash.c similarity index 88% rename from user/cgiflash.c rename to esp-link/cgiflash.c index 85aaf97..ccd6865 100644 --- a/user/cgiflash.c +++ b/esp-link/cgiflash.c @@ -22,8 +22,10 @@ Some flash handling cgi routines. Used for reading the existing flash and updati // Check that the header of the firmware blob looks like actual firmware... static char* ICACHE_FLASH_ATTR check_header(void *buf) { uint8_t *cd = (uint8_t *)buf; +#ifdef CGIFLASH_DBG uint32_t *buf32 = buf; os_printf("%p: %08lX %08lX %08lX %08lX\n", buf, buf32[0], buf32[1], buf32[2], buf32[3]); +#endif if (cd[0] != 0xEA) return "IROM magic missing"; if (cd[1] != 4 || cd[2] > 3 || (cd[3]>>4) > 6) return "bad flash header"; if (((uint16_t *)buf)[3] != 0x4010) return "Invalid entry addr"; @@ -31,30 +33,6 @@ static char* ICACHE_FLASH_ATTR check_header(void *buf) { return NULL; } -#if 0 -//===== Cgi that reads the SPI flash. Assumes 512KByte flash. -int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) { - int *pos=(int *)&connData->cgiData; - if (connData->conn==NULL) { - //Connection aborted. Clean up. - return HTTPD_CGI_DONE; - } - - if (*pos==0) { - os_printf("Start flash download.\n"); - httpdStartResponse(connData, 200); - httpdHeader(connData, "Content-Type", "application/bin"); - httpdEndHeaders(connData); - *pos=0x40200000; - return HTTPD_CGI_MORE; - } - //Send 1K of flash per call. We will get called again if we haven't sent 512K yet. - espconn_sent(connData->conn, (uint8 *)(*pos), 1024); - *pos+=1024; - if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE; -} -#endif - //===== Cgi to query which firmware needs to be uploaded next int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) { if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. @@ -66,7 +44,9 @@ int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) { httpdEndHeaders(connData); char *next = id == 1 ? "user1.bin" : "user2.bin"; httpdSend(connData, next, -1); +#ifdef CGIFLASH_DBG os_printf("Next firmware: %s (got %d)\n", next, id); +#endif return HTTPD_CGI_DONE; } @@ -104,7 +84,9 @@ int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) { // return an error if there is one if (err != NULL) { +#ifdef CGIFLASH_DBG os_printf("Error %d: %s\n", code, err); +#endif httpdStartResponse(connData, code); httpdHeader(connData, "Content-Type", "text/plain"); //httpdHeader(connData, "Content-Length", strlen(err)+2); @@ -123,7 +105,9 @@ int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) { // erase next flash block if necessary if (address % SPI_FLASH_SEC_SIZE == 0){ +#ifdef CGIFLASH_DBG os_printf("Flashing 0x%05x (id=%d)\n", address, 2-id); +#endif spi_flash_erase_sector(address/SPI_FLASH_SEC_SIZE); } @@ -153,10 +137,15 @@ int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) { int address = id == 1 ? 4*1024 // either start after 4KB boot partition : 4*1024 + FIRMWARE_SIZE + 16*1024 + 4*1024; // 4KB boot, fw1, 16KB user param, 4KB reserved uint32 buf[8]; +#ifdef CGIFLASH_DBG + os_printf("Checking %p\n", (void *)address); +#endif spi_flash_read(address, buf, sizeof(buf)); char *err = check_header(buf); if (err != NULL) { +#ifdef CGIFLASH_DBG os_printf("Error %d: %s\n", 400, err); +#endif httpdStartResponse(connData, 400); httpdHeader(connData, "Content-Type", "text/plain"); //httpdHeader(connData, "Content-Length", strlen(err)+2); diff --git a/user/cgiflash.h b/esp-link/cgiflash.h similarity index 100% rename from user/cgiflash.h rename to esp-link/cgiflash.h diff --git a/esp-link/cgimqtt.c b/esp-link/cgimqtt.c new file mode 100644 index 0000000..d4f7100 --- /dev/null +++ b/esp-link/cgimqtt.c @@ -0,0 +1,172 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +#ifdef MQTT +#include +#include "cgi.h" +#include "config.h" +#include "status.h" +#include "mqtt_client.h" +#include "cgimqtt.h" + +static char *mqtt_states[] = { + "disconnected", "reconnecting", "connecting", "connected", +}; + +// Cgi to return MQTT settings +int ICACHE_FLASH_ATTR cgiMqttGet(HttpdConnData *connData) { + char buff[1024]; + int len; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + // get the current status topic for display + char status_buf1[128], *sb1=status_buf1; + char status_buf2[128], *sb2=status_buf2; + mqttStatusMsg(status_buf1); + // quote all " for the json, sigh... + for (int i=0; i<127 && *sb1; i++) { + if (*sb1 == '"') { + *sb2++ = '\\'; + i++; + } + *sb2++ = *sb1++; + } + *sb2 = 0; + + len = os_sprintf(buff, "{ " + "\"slip-enable\":%d, " + "\"mqtt-enable\":%d, " + "\"mqtt-state\":\"%s\", " + "\"mqtt-status-enable\":%d, " + "\"mqtt-clean-session\":%d, " + "\"mqtt-port\":%d, " + "\"mqtt-timeout\":%d, " + "\"mqtt-keepalive\":%d, " + "\"mqtt-host\":\"%s\", " + "\"mqtt-client-id\":\"%s\", " + "\"mqtt-username\":\"%s\", " + "\"mqtt-password\":\"%s\", " + "\"mqtt-status-topic\":\"%s\", " + "\"mqtt-status-value\":\"%s\" }", + flashConfig.slip_enable, flashConfig.mqtt_enable, + mqtt_states[mqttClient.connState], flashConfig.mqtt_status_enable, + flashConfig.mqtt_clean_session, flashConfig.mqtt_port, + flashConfig.mqtt_timeout, flashConfig.mqtt_keepalive, + flashConfig.mqtt_host, flashConfig.mqtt_clientid, + flashConfig.mqtt_username, flashConfig.mqtt_password, + flashConfig.mqtt_status_topic, status_buf2); + + jsonHeader(connData, 200); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +// Cgi to change choice of pin assignments +int ICACHE_FLASH_ATTR cgiMqttSet(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + // handle MQTT server settings + int8_t mqtt_server = 0; // accumulator for changes/errors + mqtt_server |= getStringArg(connData, "mqtt-host", + flashConfig.mqtt_host, sizeof(flashConfig.mqtt_host)); + if (mqtt_server < 0) return HTTPD_CGI_DONE; + mqtt_server |= getStringArg(connData, "mqtt-client-id", + flashConfig.mqtt_clientid, sizeof(flashConfig.mqtt_clientid)); + + if (mqtt_server < 0) return HTTPD_CGI_DONE; + mqtt_server |= getStringArg(connData, "mqtt-username", + flashConfig.mqtt_username, sizeof(flashConfig.mqtt_username)); + if (mqtt_server < 0) return HTTPD_CGI_DONE; + mqtt_server |= getStringArg(connData, "mqtt-password", + flashConfig.mqtt_password, sizeof(flashConfig.mqtt_password)); + + if (mqtt_server < 0) return HTTPD_CGI_DONE; + mqtt_server |= getBoolArg(connData, "mqtt-clean-session", + &flashConfig.mqtt_clean_session); + + if (mqtt_server < 0) return HTTPD_CGI_DONE; + int8_t mqtt_en_chg = getBoolArg(connData, "mqtt-enable", + &flashConfig.mqtt_enable); + + char buff[16]; + + // handle mqtt port + if (httpdFindArg(connData->getArgs, "mqtt-port", buff, sizeof(buff)) > 0) { + int32_t port = atoi(buff); + if (port > 0 && port < 65536) { + flashConfig.mqtt_port = port; + mqtt_server |= 1; + } else { + errorResponse(connData, 400, "Invalid MQTT port"); + return HTTPD_CGI_DONE; + } + } + + // handle mqtt timeout + if (httpdFindArg(connData->getArgs, "mqtt-timeout", buff, sizeof(buff)) > 0) { + int32_t timeout = atoi(buff); + flashConfig.mqtt_timeout = timeout; + } + + // handle mqtt keepalive + if (httpdFindArg(connData->getArgs, "mqtt-keepalive", buff, sizeof(buff)) > 0) { + int32_t keepalive = atoi(buff); + flashConfig.mqtt_keepalive = keepalive; + } + + // if server setting changed, we need to "make it so" + if (mqtt_server) { +#ifdef CGIMQTT_DBG + os_printf("MQTT server settings changed, enable=%d\n", flashConfig.mqtt_enable); +#endif + MQTT_Free(&mqttClient); // safe even if not connected + mqtt_client_init(); + + // if just enable changed we just need to bounce the client + } else if (mqtt_en_chg > 0) { +#ifdef CGIMQTT_DBG + os_printf("MQTT server enable=%d changed\n", flashConfig.mqtt_enable); +#endif + if (flashConfig.mqtt_enable && strlen(flashConfig.mqtt_host) > 0) + MQTT_Reconnect(&mqttClient); + else + MQTT_Disconnect(&mqttClient); + } + + // no action required if mqtt status settings change, they just get picked up at the + // next status tick + if (getBoolArg(connData, "mqtt-status-enable", &flashConfig.mqtt_status_enable) < 0) + return HTTPD_CGI_DONE; + if (getStringArg(connData, "mqtt-status-topic", + flashConfig.mqtt_status_topic, sizeof(flashConfig.mqtt_status_topic)) < 0) + return HTTPD_CGI_DONE; + + // if SLIP-enable is toggled it gets picked-up immediately by the parser + int slip_update = getBoolArg(connData, "slip-enable", &flashConfig.slip_enable); + if (slip_update < 0) return HTTPD_CGI_DONE; +#ifdef CGIMQTT_DBG + if (slip_update > 0) os_printf("SLIP-enable changed: %d\n", flashConfig.slip_enable); + + os_printf("Saving config\n"); +#endif + if (configSave()) { + httpdStartResponse(connData, 200); + httpdEndHeaders(connData); + } else { + httpdStartResponse(connData, 500); + httpdEndHeaders(connData); + httpdSend(connData, "Failed to save config", -1); + } + return HTTPD_CGI_DONE; +} + +int ICACHE_FLASH_ATTR cgiMqtt(HttpdConnData *connData) { + if (connData->requestType == HTTPD_METHOD_GET) { + return cgiMqttGet(connData); + } else if (connData->requestType == HTTPD_METHOD_POST) { + return cgiMqttSet(connData); + } else { + jsonHeader(connData, 404); + return HTTPD_CGI_DONE; + } +} +#endif // MQTT diff --git a/esp-link/cgimqtt.h b/esp-link/cgimqtt.h new file mode 100644 index 0000000..ef58f38 --- /dev/null +++ b/esp-link/cgimqtt.h @@ -0,0 +1,9 @@ +#ifdef MQTT +#ifndef CGIMQTT_H +#define CGIMQTT_H + +#include "httpd.h" +int cgiMqtt(HttpdConnData *connData); + +#endif // CGIMQTT_H +#endif // MQTT diff --git a/user/cgipins.c b/esp-link/cgipins.c similarity index 58% rename from user/cgipins.c rename to esp-link/cgipins.c index ae6b26c..80326df 100644 --- a/user/cgipins.c +++ b/esp-link/cgipins.c @@ -8,38 +8,39 @@ #include "serbridge.h" static char *map_names[] = { - "esp-bridge", "jn-esp-v2", "esp-01(AVR)", "esp-01(ARM)", "esp-br-rev", + "esp-bridge", "jn-esp-v2", "esp-01(AVR)", "esp-01(ARM)", "esp-br-rev", "wifi-link-12", }; -static char* map_func[] = { "reset", "isp", "conn_led", "ser_led" }; -static int8_t map_asn[][4] = { - { 12, 13, 0, 14 }, // esp-bridge - { 12, 13, 0, 2 }, // jn-esp-v2 - { 0, -1, 2, -1 }, // esp-01(AVR) - { 0, 2, -1, -1 }, // esp-01(ARM) - { 13, 12, 14, 0 }, // esp-br-rev -- for test purposes +static char* map_func[] = { "reset", "isp", "conn_led", "ser_led", "swap_uart" }; +static int8_t map_asn[][5] = { + { 12, 13, 0, 14, 0 }, // esp-bridge + { 12, 13, 0, 2, 0 }, // jn-esp-v2 + { 0, -1, 2, -1, 0 }, // esp-01(AVR) + { 0, 2, -1, -1, 0 }, // esp-01(ARM) + { 13, 12, 14, 0, 0 }, // esp-br-rev -- for test purposes + { 3, 1, 0, 2, 1 }, // esp-link-12 }; static const int num_map_names = sizeof(map_names)/sizeof(char*); static const int num_map_func = sizeof(map_func)/sizeof(char*); // Cgi to return choice of pin assignments int ICACHE_FLASH_ATTR cgiPinsGet(HttpdConnData *connData) { - char buff[1024]; - int len; + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted - if (connData->conn==NULL) { - return HTTPD_CGI_DONE; // Connection aborted - } + char buff[2048]; + int len; // figure out current mapping - int curr = 99; + int curr = 0; for (int i=0; i= 0) len += os_sprintf(buff+len, " %s:gpio%d", map_func[f], p); - else len += os_sprintf(buff+len, " %s:n/a", map_func[f]); + if (f == 4) + len += os_sprintf(buff+len, " %s:%s", map_func[f], p?"yes":"no"); + else if (p >= 0) + len += os_sprintf(buff+len, " %s:gpio%d", map_func[f], p); + else + len += os_sprintf(buff+len, " %s:n/a", map_func[f]); } len += os_sprintf(buff+len, "\" }"); } len += os_sprintf(buff+len, "\n] }"); - jsonHeader(connData, 200); - httpdSend(connData, buff, len); - return HTTPD_CGI_DONE; + jsonHeader(connData, 200); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; } // Cgi to change choice of pin assignments int ICACHE_FLASH_ATTR cgiPinsSet(HttpdConnData *connData) { - if (connData->conn==NULL) { - return HTTPD_CGI_DONE; // Connection aborted - } + if (connData->conn==NULL) { + return HTTPD_CGI_DONE; // Connection aborted + } char buff[128]; - int len = httpdFindArg(connData->getArgs, "map", buff, sizeof(buff)); - if (len <= 0) { - jsonHeader(connData, 400); + int len = httpdFindArg(connData->getArgs, "map", buff, sizeof(buff)); + if (len <= 0) { + jsonHeader(connData, 400); return HTTPD_CGI_DONE; } int m = atoi(buff); - if (m < 0 || m >= num_map_names) { - jsonHeader(connData, 400); + if (m < 0 || m >= num_map_names) { + jsonHeader(connData, 400); return HTTPD_CGI_DONE; } - +#ifdef CGIPINS_DBG os_printf("Switching pin map to %s (%d)\n", map_names[m], m); +#endif int8_t *map = map_asn[m]; flashConfig.reset_pin = map[0]; flashConfig.isp_pin = map[1]; flashConfig.conn_led_pin = map[2]; flashConfig.ser_led_pin = map[3]; + flashConfig.swap_uart = map[4]; serbridgeInitPins(); serledInit(); statusInit(); if (configSave()) { - os_printf("New config saved\n"); httpdStartResponse(connData, 200); httpdEndHeaders(connData); } else { - os_printf("*** Failed to save config ***\n"); httpdStartResponse(connData, 500); httpdEndHeaders(connData); httpdSend(connData, "Failed to save config", -1); } - return HTTPD_CGI_DONE; + return HTTPD_CGI_DONE; } int ICACHE_FLASH_ATTR cgiPins(HttpdConnData *connData) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - if (connData->requestType == HTTPD_METHOD_GET) { - return cgiPinsGet(connData); - } else if (connData->requestType == HTTPD_METHOD_POST) { - return cgiPinsSet(connData); - } else { - jsonHeader(connData, 404); - return HTTPD_CGI_DONE; - } + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + if (connData->requestType == HTTPD_METHOD_GET) { + return cgiPinsGet(connData); + } else if (connData->requestType == HTTPD_METHOD_POST) { + return cgiPinsSet(connData); + } else { + jsonHeader(connData, 404); + return HTTPD_CGI_DONE; + } } diff --git a/user/cgipins.h b/esp-link/cgipins.h similarity index 100% rename from user/cgipins.h rename to esp-link/cgipins.h diff --git a/esp-link/cgitcp.c b/esp-link/cgitcp.c new file mode 100644 index 0000000..f8d9036 --- /dev/null +++ b/esp-link/cgitcp.c @@ -0,0 +1,74 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// // TCP Client settings + +#include +#include "cgi.h" +#include "config.h" +#include "cgitcp.h" + +// Cgi to return TCP client settings +int ICACHE_FLASH_ATTR cgiTcpGet(HttpdConnData *connData) { + char buff[1024]; + int len; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + len = os_sprintf(buff, "{ \"tcp_enable\":%d, \"rssi_enable\": %d, \"api_key\":\"%s\" }", + flashConfig.tcp_enable, flashConfig.rssi_enable, flashConfig.api_key); + + jsonHeader(connData, 200); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +// Cgi to change choice of pin assignments +int ICACHE_FLASH_ATTR cgiTcpSet(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + // Handle tcp_enable flag + char buff[128]; + int len = httpdFindArg(connData->getArgs, "tcp_enable", buff, sizeof(buff)); + if (len <= 0) { + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + } + flashConfig.tcp_enable = os_strcmp(buff, "true") == 0; + + // Handle rssi_enable flag + len = httpdFindArg(connData->getArgs, "rssi_enable", buff, sizeof(buff)); + if (len <= 0) { + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + } + flashConfig.rssi_enable = os_strcmp(buff, "true") == 0; + + // Handle api_key flag + len = httpdFindArg(connData->getArgs, "api_key", buff, sizeof(buff)); + if (len < 0) { + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + } + buff[sizeof(flashConfig.api_key)-1] = 0; // ensure we don't get an overrun + os_strcpy(flashConfig.api_key, buff); + + if (configSave()) { + httpdStartResponse(connData, 200); + httpdEndHeaders(connData); + } else { + httpdStartResponse(connData, 500); + httpdEndHeaders(connData); + httpdSend(connData, "Failed to save config", -1); + } + return HTTPD_CGI_DONE; +} + +int ICACHE_FLASH_ATTR cgiTcp(HttpdConnData *connData) { + if (connData->requestType == HTTPD_METHOD_GET) { + return cgiTcpGet(connData); + } else if (connData->requestType == HTTPD_METHOD_POST) { + return cgiTcpSet(connData); + } else { + jsonHeader(connData, 404); + return HTTPD_CGI_DONE; + } +} diff --git a/esp-link/cgitcp.h b/esp-link/cgitcp.h new file mode 100644 index 0000000..2b7b2a9 --- /dev/null +++ b/esp-link/cgitcp.h @@ -0,0 +1,8 @@ +#ifndef CGITCP_H +#define CGITCP_H + +#include "httpd.h" + +int cgiTcp(HttpdConnData *connData); + +#endif diff --git a/esp-link/cgiwifi.c b/esp-link/cgiwifi.c new file mode 100644 index 0000000..81929ea --- /dev/null +++ b/esp-link/cgiwifi.c @@ -0,0 +1,640 @@ +/* +Cgi/template routines for the /wifi url. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + * Heavily modified and enhanced by Thorsten von Eicken in 2015 + * ---------------------------------------------------------------------------- + */ + + +#include +#include "cgiwifi.h" +#include "cgi.h" +#include "status.h" +#include "config.h" +#include "log.h" + +//#define SLEEP_MODE LIGHT_SLEEP_T +#define SLEEP_MODE MODEM_SLEEP_T + +// ===== wifi status change callbacks +static WifiStateChangeCb wifi_state_change_cb[4]; + +uint8_t wifiState = wifiIsDisconnected; +// reasons for which a connection failed +uint8_t wifiReason = 0; +static char *wifiReasons[] = { + "", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", + "not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", + "ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", + "ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", + "unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", + "beacon_timeout", "no_ap_found" }; + +static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; +static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; + +void (*wifiStatusCb)(uint8_t); // callback when wifi status changes + +static char* ICACHE_FLASH_ATTR wifiGetReason(void) { + if (wifiReason <= 24) return wifiReasons[wifiReason]; + if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; + return wifiReasons[1]; +} + +// handler for wifi status change callback coming in from espressif library +static void ICACHE_FLASH_ATTR wifiHandleEventCb(System_Event_t *evt) { + switch (evt->event) { + case EVENT_STAMODE_CONNECTED: + wifiState = wifiIsConnected; + wifiReason = 0; +#ifdef CGIWIFI_DBG + os_printf("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, + evt->event_info.connected.channel); +#endif + statusWifiUpdate(wifiState); + break; + case EVENT_STAMODE_DISCONNECTED: + wifiState = wifiIsDisconnected; + wifiReason = evt->event_info.disconnected.reason; +#ifdef CGIWIFI_DBG + os_printf("Wifi disconnected from ssid %s, reason %s (%d)\n", + evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); +#endif + statusWifiUpdate(wifiState); + break; + case EVENT_STAMODE_AUTHMODE_CHANGE: +#ifdef CGIWIFI_DBG + os_printf("Wifi auth mode: %d -> %d\n", + evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); +#endif + break; + case EVENT_STAMODE_GOT_IP: + wifiState = wifiGotIP; + wifiReason = 0; +#ifdef CGIWIFI_DBG + os_printf("Wifi got ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR "\n", + IP2STR(&evt->event_info.got_ip.ip), IP2STR(&evt->event_info.got_ip.mask), + IP2STR(&evt->event_info.got_ip.gw)); +#endif + statusWifiUpdate(wifiState); + break; + case EVENT_SOFTAPMODE_STACONNECTED: +#ifdef CGIWIFI_DBG + os_printf("Wifi AP: station " MACSTR " joined, AID = %d\n", + MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); +#endif + break; + case EVENT_SOFTAPMODE_STADISCONNECTED: +#ifdef CGIWIFI_DBG + os_printf("Wifi AP: station " MACSTR " left, AID = %d\n", + MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); +#endif + break; + default: + break; + } + + for (int i = 0; i < 4; i++) { + if (wifi_state_change_cb[i] != NULL) (wifi_state_change_cb[i])(wifiState); + } +} + +void ICACHE_FLASH_ATTR +wifiAddStateChangeCb(WifiStateChangeCb cb) { + for (int i = 0; i < 4; i++) { + if (wifi_state_change_cb[i] == cb) return; + if (wifi_state_change_cb[i] == NULL) { + wifi_state_change_cb[i] = cb; + return; + } + } +#ifdef CGIWIFI_DBG + os_printf("WIFI: max state change cb count exceeded\n"); +#endif +} + +// ===== wifi scanning + +//WiFi access point data +typedef struct { + char ssid[32]; + sint8 rssi; + char enc; +} ApData; + +//Scan result +typedef struct { + char scanInProgress; //if 1, don't access the underlying stuff from the webpage. + ApData **apData; + int noAps; +} ScanResultData; + +//Static scan status storage. +static ScanResultData cgiWifiAps; + +//Callback the code calls when a wlan ap scan is done. Basically stores the result in +//the cgiWifiAps struct. +void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { + int n; + struct bss_info *bss_link = (struct bss_info *)arg; + + if (status!=OK) { +#ifdef CGIWIFI_DBG + os_printf("wifiScanDoneCb status=%d\n", status); +#endif + cgiWifiAps.scanInProgress=0; + return; + } + + //Clear prev ap data if needed. + if (cgiWifiAps.apData!=NULL) { + for (n=0; nnext.stqe_next; + n++; + } + //Allocate memory for access point data + cgiWifiAps.apData=(ApData **)os_malloc(sizeof(ApData *)*n); + cgiWifiAps.noAps=n; +#ifdef CGIWIFI_DBG + os_printf("Scan done: found %d APs\n", n); +#endif + + //Copy access point data to the static struct + n=0; + bss_link = (struct bss_info *)arg; + while (bss_link != NULL) { + if (n>=cgiWifiAps.noAps) { + //This means the bss_link changed under our nose. Shouldn't happen! + //Break because otherwise we will write in unallocated memory. +#ifdef CGIWIFI_DBG + os_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); +#endif + break; + } + //Save the ap data. + cgiWifiAps.apData[n]=(ApData *)os_malloc(sizeof(ApData)); + cgiWifiAps.apData[n]->rssi=bss_link->rssi; + cgiWifiAps.apData[n]->enc=bss_link->authmode; + strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); +#ifdef CGIWIFI_DBG + os_printf("bss%d: %s (%d)\n", n+1, (char*)bss_link->ssid, bss_link->rssi); +#endif + + bss_link = bss_link->next.stqe_next; + n++; + } + //We're done. + cgiWifiAps.scanInProgress=0; +} + +static ETSTimer scanTimer; +static void ICACHE_FLASH_ATTR scanStartCb(void *arg) { +#ifdef CGIWIFI_DBG + os_printf("Starting a scan\n"); +#endif + wifi_station_scan(NULL, wifiScanDoneCb); +} + +static int ICACHE_FLASH_ATTR cgiWiFiStartScan(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + jsonHeader(connData, 200); + if (!cgiWifiAps.scanInProgress) { + cgiWifiAps.scanInProgress = 1; + os_timer_disarm(&scanTimer); + os_timer_setfn(&scanTimer, scanStartCb, NULL); + os_timer_arm(&scanTimer, 1000, 0); + } + return HTTPD_CGI_DONE; +} + +static int ICACHE_FLASH_ATTR cgiWiFiGetScan(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + char buff[2048]; + int len; + + jsonHeader(connData, 200); + + if (cgiWifiAps.scanInProgress==1) { + //We're still scanning. Tell Javascript code that. + len = os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n"); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; + } + + len = os_sprintf(buff, "{\"result\": {\"inProgress\": \"0\", \"APs\": [\n"); + for (int pos=0; posssid, cgiWifiAps.apData[pos]->rssi, + cgiWifiAps.apData[pos]->enc, (pos==cgiWifiAps.noAps-1)?"":","); + } + len += os_sprintf(buff+len, "]}}\n"); + //os_printf("Sending %d bytes: %s\n", len, buff); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +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; + } +} + +// ===== timers to change state and rescue from failed associations + +// reset timer changes back to STA+AP if we can't associate +#define RESET_TIMEOUT (15000) // 15 seconds +static ETSTimer resetTimer; + +// This routine is ran some time after a connection attempt to an access point. If +// the connect succeeds, this gets the module in STA-only mode. If it fails, it ensures +// that the module is in STA+AP mode so the user has a chance to recover. +static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { + int x = wifi_station_get_connect_status(); + int m = wifi_get_opmode() & 0x3; +#ifdef CGIWIFI_DBG + os_printf("Wifi check: mode=%s status=%d\n", wifiMode[m], x); +#endif + + if (x == STATION_GOT_IP) { + if (m != 1) { +#ifdef CHANGE_TO_STA + // We're happily connected, go to STA mode +#ifdef CGIWIFI_DBG + os_printf("Wifi got IP. Going into STA mode..\n"); +#endif + wifi_set_opmode(1); + wifi_set_sleep_type(SLEEP_MODE); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); +#endif + } + log_uart(false); + // no more resetTimer at this point, gotta use physical reset to recover if in trouble + } else { + if (m != 3) { +#ifdef CGIWIFI_DBG + os_printf("Wifi connect failed. Going into STA+AP mode..\n"); +#endif + wifi_set_opmode(3); + } + log_uart(true); +#ifdef CGIWIFI_DBG + os_printf("Enabling/continuing uart log\n"); +#endif + 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; + +// Callback actually doing reassociation +static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { +#ifdef CGIWIFI_DBG + os_printf("Wifi changing association\n"); +#endif + wifi_station_disconnect(); + stconf.bssid_set = 0; + wifi_station_set_config(&stconf); + wifi_station_connect(); + // Schedule check + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); +} + +// 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) { + char essid[128]; + char passwd[128]; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + int el = httpdFindArg(connData->getArgs, "essid", essid, sizeof(essid)); + int pl = httpdFindArg(connData->getArgs, "passwd", passwd, sizeof(passwd)); + + if (el > 0 && pl >= 0) { + //Set to 0 if you want to disable the actual reconnecting bit + os_strncpy((char*)stconf.ssid, essid, 32); + os_strncpy((char*)stconf.password, passwd, 64); +#ifdef CGIWIFI_DBG + os_printf("Wifi try to connect to AP %s pw %s\n", essid, passwd); +#endif + + //Schedule disconnect/connect + os_timer_disarm(&reassTimer); + os_timer_setfn(&reassTimer, reassTimerCb, NULL); + os_timer_arm(&reassTimer, 1000, 0); + jsonHeader(connData, 200); + } else { + jsonHeader(connData, 400); + httpdSend(connData, "Cannot parse ssid or password", -1); + } + return HTTPD_CGI_DONE; +} + +static bool parse_ip(char *buff, ip_addr_t *ip_ptr) { + char *next = buff; // where to start parsing next integer + int found = 0; // number of integers parsed + uint32_t ip = 0; // the ip addres parsed + for (int i=0; i<32; i++) { // 32 is just a safety limit + char c = buff[i]; + if (c == '.' || c == 0) { + // parse the preceding integer and accumulate into IP address + bool last = c == 0; + buff[i] = 0; + uint32_t v = atoi(next); + ip = ip | ((v&0xff)<<(found*8)); + next = buff+i+1; // next integer starts after the '.' + found++; + if (last) { // if at end of string we better got 4 integers + ip_ptr->addr = ip; + return found == 4; + } + continue; + } + if (c < '0' || c > '9') return false; + } + return false; +} + +#ifdef DEBUGIP +static void ICACHE_FLASH_ATTR debugIP() { + struct ip_info info; + if (wifi_get_ip_info(0, &info)) { + os_printf("\"ip\": \"%d.%d.%d.%d\"\n", IP2STR(&info.ip.addr)); + os_printf("\"netmask\": \"%d.%d.%d.%d\"\n", IP2STR(&info.netmask.addr)); + os_printf("\"gateway\": \"%d.%d.%d.%d\"\n", IP2STR(&info.gw.addr)); + os_printf("\"hostname\": \"%s\"\n", wifi_station_get_hostname()); + } else { + os_printf("\"ip\": \"-none-\"\n"); + } +} +#endif + +// configure Wifi, specifically DHCP vs static IP address based on flash config +static void ICACHE_FLASH_ATTR configWifiIP() { + if (flashConfig.staticip == 0) { + // let's DHCP! + wifi_station_set_hostname(flashConfig.hostname); + if (wifi_station_dhcpc_status() == DHCP_STARTED) + wifi_station_dhcpc_stop(); + wifi_station_dhcpc_start(); +#ifdef CGIWIFI_DBG + os_printf("Wifi uses DHCP, hostname=%s\n", flashConfig.hostname); +#endif + } else { + // no DHCP, we got static network config! + wifi_station_dhcpc_stop(); + struct ip_info ipi; + ipi.ip.addr = flashConfig.staticip; + ipi.netmask.addr = flashConfig.netmask; + ipi.gw.addr = flashConfig.gateway; + wifi_set_ip_info(0, &ipi); +#ifdef CGIWIFI_DBG + os_printf("Wifi uses static IP %d.%d.%d.%d\n", IP2STR(&ipi.ip.addr)); +#endif + } +#ifdef DEBUGIP + debugIP(); +#endif +} + +// Change special settings +int ICACHE_FLASH_ATTR cgiWiFiSpecial(HttpdConnData *connData) { + char dhcp[8]; + char hostname[32]; + char staticip[20]; + char netmask[20]; + char gateway[20]; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + // get args and their string lengths + int dl = httpdFindArg(connData->getArgs, "dhcp", dhcp, sizeof(dhcp)); + int hl = httpdFindArg(connData->getArgs, "hostname", hostname, sizeof(hostname)); + int sl = httpdFindArg(connData->getArgs, "staticip", staticip, sizeof(staticip)); + int nl = httpdFindArg(connData->getArgs, "netmask", netmask, sizeof(netmask)); + int gl = httpdFindArg(connData->getArgs, "gateway", gateway, sizeof(gateway)); + + if (!(dl > 0 && hl >= 0 && sl >= 0 && nl >= 0 && gl >= 0)) { + jsonHeader(connData, 400); + httpdSend(connData, "Request is missing fields", -1); + return HTTPD_CGI_DONE; + } + + char url[64]; // redirect URL + if (os_strcmp(dhcp, "off") == 0) { + // parse static IP params + struct ip_info ipi; + bool ok = parse_ip(staticip, &ipi.ip); + if (nl > 0) ok = ok && parse_ip(netmask, &ipi.netmask); + else IP4_ADDR(&ipi.netmask, 255, 255, 255, 0); + if (gl > 0) ok = ok && parse_ip(gateway, &ipi.gw); + else ipi.gw.addr = 0; + if (!ok) { + jsonHeader(connData, 400); + httpdSend(connData, "Cannot parse static IP config", -1); + return HTTPD_CGI_DONE; + } + // save the params in flash + flashConfig.staticip = ipi.ip.addr; + flashConfig.netmask = ipi.netmask.addr; + flashConfig.gateway = ipi.gw.addr; + // construct redirect URL + os_sprintf(url, "{\"url\": \"http://%d.%d.%d.%d\"}", IP2STR(&ipi.ip)); + + } else { + // no static IP, set hostname + if (hl == 0) os_strcpy(hostname, "esp-link"); + flashConfig.staticip = 0; + os_strcpy(flashConfig.hostname, hostname); + os_sprintf(url, "{\"url\": \"http://%s\"}", hostname); + } + + configSave(); // ignore error... + // schedule change-over + os_timer_disarm(&reassTimer); + os_timer_setfn(&reassTimer, configWifiIP, NULL); + os_timer_arm(&reassTimer, 1000, 0); + // return redirect info + jsonHeader(connData, 200); + httpdSend(connData, url, -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]; + + 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); +#ifdef CGIWIFI_DBG + os_printf("Wifi switching to mode %d\n", m); +#endif + wifi_set_opmode(m&3); + if (m == 1) { + wifi_set_sleep_type(SLEEP_MODE); + // STA-only mode, reset into STA+AP after a timeout + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); + } + jsonHeader(connData, 200); + } else { + jsonHeader(connData, 400); + } + return HTTPD_CGI_DONE; +} + +static char *connStatuses[] = { "idle", "connecting", "wrong password", "AP not found", + "failed", "got IP address" }; + +static char *wifiWarn[] = { 0, + "Switch to STA+AP mode", + "Can't scan in this mode! Switch to STA+AP mode", + "Switch to STA mode", +}; + +#ifdef CHANGE_TO_STA +#define MODECHANGE "yes" +#else +#define MODECHANGE "no" +#endif + +// 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); + + 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; +} + +int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { + char buff[1024]; + int len; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + jsonHeader(connData, 200); + + len = os_sprintf(buff, "{"); + len += printWifiInfo(buff+len); + len += os_sprintf(buff+len, ", "); + + if (wifiReason != 0) { + len += os_sprintf(buff+len, "\"reason\": \"%s\", ", wifiGetReason()); + } + +#if 0 + // commented out 'cause often the client that requested the change can't get a request in to + // find out that it succeeded. Better to just wait the std 15 seconds... + int st=wifi_station_get_connect_status(); + if (st == STATION_GOT_IP) { + if (wifi_get_opmode() != 1) { + // Reset into AP-only mode sooner. + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, 1000, 0); + } + } +#endif + + len += os_sprintf(buff+len, "\"x\":0}\n"); +#ifdef CGIWIFI_DBG + //os_printf(" -> %s\n", buff); +#endif + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +// Cgi to return various Wifi information +int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) { + char buff[1024]; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + os_strcpy(buff, "{"); + printWifiInfo(buff+1); + os_strcat(buff, "}"); + + jsonHeader(connData, 200); + httpdSend(connData, buff, -1); + 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); +#ifdef CGIWIFI_DBG + int x = wifi_get_opmode() & 0x3; + os_printf("Wifi init, mode=%s\n", wifiMode[x]); +#endif + configWifiIP(); + + wifi_set_event_handler_cb(wifiHandleEventCb); + // check on the wifi in a few seconds to see whether we need to switch mode + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); +} + diff --git a/user/cgiwifi.h b/esp-link/cgiwifi.h similarity index 77% rename from user/cgiwifi.h rename to esp-link/cgiwifi.h index 557c31a..a2782a5 100644 --- a/user/cgiwifi.h +++ b/esp-link/cgiwifi.h @@ -4,6 +4,7 @@ #include "httpd.h" enum { wifiIsDisconnected, wifiIsConnected, wifiGotIP }; +typedef void(*WifiStateChangeCb)(uint8_t wifiStatus); int cgiWiFiScan(HttpdConnData *connData); int cgiWifiInfo(HttpdConnData *connData); @@ -13,5 +14,8 @@ int cgiWiFiSetMode(HttpdConnData *connData); int cgiWiFiConnStatus(HttpdConnData *connData); int cgiWiFiSpecial(HttpdConnData *connData); void wifiInit(void); +void wifiAddStateChangeCb(WifiStateChangeCb cb); + +extern uint8_t wifiState; #endif diff --git a/esp-link/config.c b/esp-link/config.c new file mode 100644 index 0000000..cd95e76 --- /dev/null +++ b/esp-link/config.c @@ -0,0 +1,173 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +/* Configuration stored in flash */ + +#include +#include +#include "config.h" +#include "espfs.h" +#include "crc16.h" + +FlashConfig flashConfig; +FlashConfig flashDefault = { + 33, 0, 0, + MCU_RESET_PIN, MCU_ISP_PIN, LED_CONN_PIN, LED_SERIAL_PIN, + 115200, + "esp-link\0", // hostname + 0, 0x00ffffff, 0, // static ip, netmask, gateway + 0, // log mode + 0, // swap uart (don't by default) + 1, 0, // tcp_enable, rssi_enable + "\0", // api_key + 0, 0, 0, // slip_enable, mqtt_enable, mqtt_status_enable + 2, 1, // mqtt_timeout, mqtt_clean_session + 1883, 60, // mqtt port, mqtt_keepalive + "\0", "\0", "\0", "\0", "\0", // mqtt host, client_id, user, password, status-topic +}; + +typedef union { + FlashConfig fc; + uint8_t block[1024]; +} FlashFull; + +// magic number to recognize thet these are our flash settings as opposed to some random stuff +#define FLASH_MAGIC (0xaa55) + +// size of the setting sector +#define FLASH_SECT (4096) + +// address where to flash the settings: there are 16KB of reserved space at the end of the first +// flash partition, we use the upper 8KB (2 sectors) +#define FLASH_ADDR (FLASH_SECT + FIRMWARE_SIZE + 2*FLASH_SECT) + +static int flash_pri; // primary flash sector (0 or 1, or -1 for error) + +#if 0 +static void memDump(void *addr, int len) { + for (int i=0; i>12) != SPI_FLASH_RESULT_OK) + goto fail; // no harm done, give up + // calculate CRC + ff.fc.seq = seq; + ff.fc.magic = FLASH_MAGIC; + ff.fc.crc = 0; + //os_printf("cksum of: "); + //memDump(&ff, sizeof(ff)); + ff.fc.crc = crc16_data((unsigned char*)&ff, sizeof(ff), 0); + //os_printf("cksum is %04x\n", ff.fc.crc); + // write primary with incorrect seq + ff.fc.seq = 0xffffffff; + if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) + goto fail; // no harm done, give up + // fill in correct seq + ff.fc.seq = seq; + if (spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)) != SPI_FLASH_RESULT_OK) + goto fail; // most likely failed, but no harm if successful + // now that we have safely written the new version, erase old primary + addr = FLASH_ADDR + flash_pri*FLASH_SECT; + flash_pri = 1-flash_pri; + if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK) + return true; // no back-up but we're OK + // write secondary + ff.fc.seq = 0xffffffff; + if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) + return true; // no back-up but we're OK + ff.fc.seq = seq; + spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)); + return true; +fail: +#ifdef CONFIG_DBG + os_printf("*** Failed to save config ***\n"); +#endif + return false; +} + +void ICACHE_FLASH_ATTR configWipe(void) { + spi_flash_erase_sector(FLASH_ADDR>>12); + spi_flash_erase_sector((FLASH_ADDR+FLASH_SECT)>>12); +} + +static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *fc0, FlashFull *fc1); + +bool ICACHE_FLASH_ATTR configRestore(void) { + FlashFull ff0, ff1; + // read both flash sectors + if (spi_flash_read(FLASH_ADDR, (void *)&ff0, sizeof(ff0)) != SPI_FLASH_RESULT_OK) + os_memset(&ff0, 0, sizeof(ff0)); // clear in case of error + if (spi_flash_read(FLASH_ADDR+FLASH_SECT, (void *)&ff1, sizeof(ff1)) != SPI_FLASH_RESULT_OK) + os_memset(&ff1, 0, sizeof(ff1)); // clear in case of error + // figure out which one is good + flash_pri = selectPrimary(&ff0, &ff1); + // if neither is OK, we revert to defaults + if (flash_pri < 0) { + os_memcpy(&flashConfig, &flashDefault, sizeof(FlashConfig)); + char chipIdStr[6]; + os_sprintf(chipIdStr, "%06x", system_get_chip_id()); +#ifdef CHIP_IN_HOSTNAME + char hostname[16]; + os_strcpy(hostname, "esp-link-"); + os_strcat(hostname, chipIdStr); + os_memcpy(&flashConfig.hostname, hostname, os_strlen(hostname)); +#endif + os_memcpy(&flashConfig.mqtt_clientid, &flashConfig.hostname, os_strlen(flashConfig.hostname)); + os_memcpy(&flashConfig.mqtt_status_topic, &flashConfig.hostname, os_strlen(flashConfig.hostname)); + flash_pri = 0; + return false; + } + // copy good one into global var and return + os_memcpy(&flashConfig, flash_pri == 0 ? &ff0.fc : &ff1.fc, sizeof(FlashConfig)); + return true; +} + +static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *ff0, FlashFull *ff1) { + // check CRC of ff0 + uint16_t crc = ff0->fc.crc; + ff0->fc.crc = 0; + bool ff0_crc_ok = crc16_data((unsigned char*)ff0, sizeof(FlashFull), 0) == crc; +#ifdef CONFIG_DBG + os_printf("FLASH chk=0x%04x crc=0x%04x full_sz=%d sz=%d chip_sz=%d\n", + crc16_data((unsigned char*)ff0, sizeof(FlashFull), 0), + crc, + sizeof(FlashFull), + sizeof(FlashConfig), + getFlashSize()); +#endif + + // check CRC of ff1 + crc = ff1->fc.crc; + ff1->fc.crc = 0; + bool ff1_crc_ok = crc16_data((unsigned char*)ff1, sizeof(FlashFull), 0) == crc; + + // decided which we like better + if (ff0_crc_ok) + if (!ff1_crc_ok || ff0->fc.seq >= ff1->fc.seq) + return 0; // use first sector as primary + else + return 1; // second sector is newer + else + return ff1_crc_ok ? 1 : -1; +} + +// returns the flash chip's size, in BYTES +const size_t ICACHE_FLASH_ATTR +getFlashSize() { + uint32_t id = spi_flash_get_id(); + uint8_t mfgr_id = id & 0xff; + //uint8_t type_id = (id >> 8) & 0xff; // not relevant for size calculation + uint8_t size_id = (id >> 16) & 0xff; // lucky for us, WinBond ID's their chips as a form that lets us calculate the size + if (mfgr_id != 0xEF) // 0xEF is WinBond; that's all we care about (for now) + return 0; + return 1 << size_id; +} diff --git a/esp-link/config.h b/esp-link/config.h new file mode 100644 index 0000000..5a67058 --- /dev/null +++ b/esp-link/config.h @@ -0,0 +1,34 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// Flash configuration settings. When adding new items always add them at the end and formulate +// them such that a value of zero is an appropriate default or backwards compatible. Existing +// modules that are upgraded will have zero in the new fields. This ensures that an upgrade does +// not wipe out the old settings. +typedef struct { + uint32_t seq; // flash write sequence number + uint16_t magic, crc; + int8_t reset_pin, isp_pin, conn_led_pin, ser_led_pin; + int32_t baud_rate; + char hostname[32]; // if using DHCP + uint32_t staticip, netmask, gateway; // using DHCP if staticip==0 + uint8_t log_mode; // UART log debug mode + uint8_t swap_uart; // swap uart0 to gpio 13&15 + uint8_t tcp_enable, rssi_enable; // TCP client settings + char api_key[48]; // RSSI submission API key (Grovestreams for now) + uint8_t slip_enable, mqtt_enable, // SLIP protocol, MQTT client + mqtt_status_enable, // MQTT status reporting + mqtt_timeout, // MQTT send timeout + mqtt_clean_session; // MQTT clean session + uint16_t mqtt_port, mqtt_keepalive; // MQTT Host port, MQTT Keepalive timer + char mqtt_host[32], mqtt_clientid[48], mqtt_username[32], mqtt_password[32]; + char mqtt_status_topic[32]; +} FlashConfig; +extern FlashConfig flashConfig; + +bool configSave(void); +bool configRestore(void); +void configWipe(void); +const size_t getFlashSize(); + +#endif diff --git a/esp-link/log.c b/esp-link/log.c new file mode 100644 index 0000000..1a9a985 --- /dev/null +++ b/esp-link/log.c @@ -0,0 +1,182 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt + +#include +#include "uart.h" +#include "cgi.h" +#include "config.h" +#include "log.h" + +// Web log for the esp8266 to replace outputting to uart1. +// The web log has a 1KB circular in-memory buffer which os_printf prints into and +// the HTTP handler simply displays the buffer content on a web page. + +// see console.c for invariants (same here) +#define BUF_MAX (1400) +static char log_buf[BUF_MAX]; +static int log_wr, log_rd; +static int log_pos; +static bool log_no_uart; // start out printing to uart +static bool log_newline; // at start of a new line + +// called from wifi reset timer to turn UART on when we loose wifi and back off +// when we connect to wifi AP. Here this is gated by the flash setting +void ICACHE_FLASH_ATTR +log_uart(bool enable) { + if (!enable && !log_no_uart && flashConfig.log_mode != LOG_MODE_ON) { + // we're asked to turn uart off, and uart is on, and the flash setting isn't always-on +#if 1 +#ifdef LOG_DBG + os_printf("Turning OFF uart log\n"); +#endif + os_delay_us(4*1000L); // time for uart to flush + log_no_uart = !enable; +#endif + } else if (enable && log_no_uart && flashConfig.log_mode != LOG_MODE_OFF) { + // we're asked to turn uart on, and uart is off, and the flash setting isn't always-off + log_no_uart = !enable; +#ifdef LOG_DBG + os_printf("Turning ON uart log\n"); +#endif + } +} + +static void ICACHE_FLASH_ATTR +log_write(char c) { + log_buf[log_wr] = c; + log_wr = (log_wr+1) % BUF_MAX; + if (log_wr == log_rd) { + log_rd = (log_rd+1) % BUF_MAX; // full, eat first char + log_pos++; + } +} + +#if 0 +static char ICACHE_FLASH_ATTR +log_read(void) { + char c = 0; + if (log_rd != log_wr) { + c = log_buf[log_rd]; + log_rd = (log_rd+1) % BUF_MAX; + } + return c; +} +#endif + +static void ICACHE_FLASH_ATTR +log_write_char(char c) { + // Uart output unless disabled + if (!log_no_uart) { + if (log_newline) { + char buff[16]; + int l = os_sprintf(buff, "%6d> ", (system_get_time()/1000)%1000000); + for (int i=0; iconn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + jsonHeader(connData, 200); + + // figure out where to start in buffer based on URI param + len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); + if (len > 0) { + start = atoi(buff); + if (start < log_pos) { + start = 0; + } else if (start >= log_pos+log_len) { + start = log_len; + } else { + start = start - log_pos; + } + } + + // start outputting + len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", + log_len-start, log_pos+start); + + int rd = (log_rd+start) % BUF_MAX; + while (len < 2040 && rd != log_wr) { + uint8_t c = log_buf[rd]; + if (c == '\\' || c == '"') { + buff[len++] = '\\'; + buff[len++] = c; + } else if (c < ' ') { + len += os_sprintf(buff+len, "\\u%04x", c); + } else { + buff[len++] = c; + } + rd = (rd + 1) % BUF_MAX; + } + os_strcpy(buff+len, "\"}"); len+=2; + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +static char *dbg_mode[] = { "auto", "off", "on" }; + +int ICACHE_FLASH_ATTR +ajaxLogDbg(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + char buff[512]; + int len, status = 400; + len = httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); + if (len > 0) { + int8_t mode = -1; + if (os_strcmp(buff, "auto") == 0) mode = LOG_MODE_AUTO; + if (os_strcmp(buff, "off") == 0) mode = LOG_MODE_OFF; + if (os_strcmp(buff, "on") == 0) mode = LOG_MODE_ON; + if (mode >= 0) { + flashConfig.log_mode = mode; + if (mode != LOG_MODE_AUTO) log_uart(mode == LOG_MODE_ON); + status = configSave() ? 200 : 400; + } + } else if (connData->requestType == HTTPD_METHOD_GET) { + status = 200; + } + + jsonHeader(connData, status); + os_sprintf(buff, "{\"mode\": \"%s\"}", dbg_mode[flashConfig.log_mode]); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} + +void ICACHE_FLASH_ATTR dumpMem(void *addr, int len) { + uint8_t *a = addr; + int off = 0; + while (off < len) { + os_printf("%p ", a); + for (int i = 0; i < 16 && off + i < len; i++) { + os_printf(" %02x", a[i]); + } + os_printf(" "); + for (int i=0; i<16 && off 0x20 && *a < 0x3f ? *a : '.'); + os_printf("\n"); + } +} + +void ICACHE_FLASH_ATTR logInit() { + log_no_uart = flashConfig.log_mode == LOG_MODE_OFF; // ON unless set to always-off + log_wr = 0; + log_rd = 0; + os_install_putc1((void *)log_write_char); +} + + diff --git a/user/log.h b/esp-link/log.h similarity index 87% rename from user/log.h rename to esp-link/log.h index 94d52c0..bf2c763 100644 --- a/user/log.h +++ b/esp-link/log.h @@ -12,4 +12,6 @@ void log_uart(bool enable); int ajaxLog(HttpdConnData *connData); int ajaxLogDbg(HttpdConnData *connData); +void dumpMem(void *addr, int len); + #endif diff --git a/esp-link/main.c b/esp-link/main.c new file mode 100644 index 0000000..023021d --- /dev/null +++ b/esp-link/main.c @@ -0,0 +1,179 @@ +/* +* ---------------------------------------------------------------------------- +* "THE BEER-WARE LICENSE" (Revision 42): +* Jeroen Domburg wrote this file. As long as you retain +* this notice you can do whatever you want with this stuff. If we meet some day, +* and you think this stuff is worth it, you can buy me a beer in return. +* ---------------------------------------------------------------------------- +* Heavily modified and enhanced by Thorsten von Eicken in 2015 +* ---------------------------------------------------------------------------- +*/ + + +#include +#include "httpd.h" +#include "httpdespfs.h" +#include "cgi.h" +#include "cgiwifi.h" +#include "cgipins.h" +#include "cgitcp.h" +#include "cgimqtt.h" +#include "cgiflash.h" +#include "auth.h" +#include "espfs.h" +#include "uart.h" +#include "serbridge.h" +#include "status.h" +#include "serled.h" +#include "console.h" +#include "config.h" +#include "log.h" +#include + +/* +This is the main url->function dispatching data struct. +In short, it's a struct with various URLs plus their handlers. The handlers can +be 'standard' CGI functions you wrote, or 'special' CGIs requiring an argument. +They can also be auth-functions. An asterisk will match any url starting with +everything before the asterisks; "*" matches everything. The list will be +handled top-down, so make sure to put more specific rules above the more +general ones. Authorization things (like authBasic) act as a 'barrier' and +should be placed above the URLs they protect. +*/ +HttpdBuiltInUrl builtInUrls[] = { + { "/", cgiRedirect, "/home.html" }, + { "/menu", cgiMenu, NULL }, + { "/flash/next", cgiGetFirmwareNext, NULL }, + { "/flash/upload", cgiUploadFirmware, NULL }, + { "/flash/reboot", cgiRebootFirmware, NULL }, + { "/log/text", ajaxLog, NULL }, + { "/log/dbg", ajaxLogDbg, NULL }, + { "/console/reset", ajaxConsoleReset, NULL }, + { "/console/baud", ajaxConsoleBaud, NULL }, + { "/console/text", ajaxConsole, NULL }, + //Enable the line below to protect the WiFi configuration with an username/password combo. + // {"/wifi/*", authBasic, myPassFn}, + { "/wifi", cgiRedirect, "/wifi/wifi.html" }, + { "/wifi/", cgiRedirect, "/wifi/wifi.html" }, + { "/wifi/info", cgiWifiInfo, NULL }, + { "/wifi/scan", cgiWiFiScan, NULL }, + { "/wifi/connect", cgiWiFiConnect, NULL }, + { "/wifi/connstatus", cgiWiFiConnStatus, NULL }, + { "/wifi/setmode", cgiWiFiSetMode, NULL }, + { "/wifi/special", cgiWiFiSpecial, NULL }, + { "/pins", cgiPins, NULL }, + { "/tcpclient", cgiTcp, NULL }, +#ifdef MQTT + { "/mqtt", cgiMqtt, NULL }, +#endif + + { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem + { NULL, NULL, NULL } +}; + +#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()); +} +#endif + +# define VERS_STR_STR(V) #V +# define VERS_STR(V) VERS_STR_STR(V) +char* esp_link_version = VERS_STR(VERSION); + +// address of espfs binary blob +extern uint32_t _binary_espfs_img_start; + +static char *rst_codes[] = { + "normal", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "external", +}; +static char *flash_maps[] = { + "512KB:256/256", "256KB", "1MB:512/512", "2MB:512/512", "4MB:512/512", + "2MB:1024/1024", "4MB:1024/1024" +}; + +extern void app_init(void); +extern void mqtt_client_init(void); + +void user_rf_pre_init(void) { + //default is enabled + system_set_os_print(DEBUG_SDK); +} + +// 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 + bool restoreOk = configRestore(); + // init gpio pin registers + gpio_init(); + gpio_output_set(0, 0, 0, (1<<15)); // some people tie it 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 + 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); + //os_printf("espFsInit %s\n", res?"ERR":"ok"); + // mount the http handlers + httpdInit(builtInUrls, 80); + // init the wifi-serial transparent bridge (port 23) + serbridgeInit(23, 2323); + uart_add_recv_cb(&serbridgeUartCb); +#ifdef SHOW_HEAP_USE + os_timer_disarm(&prHeapTimer); + os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL); + os_timer_arm(&prHeapTimer, 10000, 1); +#endif + + struct rst_info *rst_info = system_get_rst_info(); + os_printf("Reset cause: %d=%s\n", rst_info->reason, rst_codes[rst_info->reason]); + os_printf("exccause=%d epc1=0x%x epc2=0x%x epc3=0x%x excvaddr=0x%x depc=0x%x\n", + rst_info->exccause, rst_info->epc1, rst_info->epc2, rst_info->epc3, + rst_info->excvaddr, rst_info->depc); + uint32_t fid = spi_flash_get_id(); + os_printf("Flash map %s, manuf 0x%02lX chip 0x%04lX\n", flash_maps[system_get_flash_size_map()], + fid & 0xff, (fid&0xff00)|((fid>>16)&0xff)); + + os_printf("** esp-link ready\n"); +#ifdef MQTT + mqtt_client_init(); +#endif + + app_init(); +} diff --git a/esp-link/mqtt_client.c b/esp-link/mqtt_client.c new file mode 100644 index 0000000..3969530 --- /dev/null +++ b/esp-link/mqtt_client.c @@ -0,0 +1,150 @@ +#ifdef MQTT +#include +#include "cgiwifi.h" +#include "config.h" +#include "mqtt.h" + +#ifdef MQTTCLIENT_DBG +#define DBG_MQTTCLIENT(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG_MQTTCLIENT(format, ...) do { } while(0) +#endif + +MQTT_Client mqttClient; // main mqtt client used by esp-link + +#ifdef BRUNNELS +char* statusTopicStr; +static char* onlineMsgStr; +#endif + +static MqttCallback connected_cb; +static MqttCallback disconnected_cb; +static MqttCallback published_cb; +static MqttDataCallback data_cb; + +void ICACHE_FLASH_ATTR +mqttConnectedCb(uint32_t *args) { + DBG_MQTTCLIENT("MQTT Client: Connected\n"); + //MQTT_Client* client = (MQTT_Client*)args; + //MQTT_Subscribe(client, "system/time", 0); // handy for testing +#ifdef BRUNNELS + MQTT_Publish(client, "announce/all", onlineMsgStr, 0, 0); +#endif + if (connected_cb) + connected_cb(args); +} + +void ICACHE_FLASH_ATTR +mqttDisconnectedCb(uint32_t *args) { +// MQTT_Client* client = (MQTT_Client*)args; + DBG_MQTTCLIENT("MQTT Client: Disconnected\n"); + if (disconnected_cb) + disconnected_cb(args); +} + +void ICACHE_FLASH_ATTR +mqttPublishedCb(uint32_t *args) { +// MQTT_Client* client = (MQTT_Client*)args; + DBG_MQTTCLIENT("MQTT Client: Published\n"); + if (published_cb) + published_cb(args); +} + +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; + +#ifdef MQTTCLIENT_DBG + char *topicBuf = (char*)os_zalloc(topic_len + 1); + char *dataBuf = (char*)os_zalloc(data_len + 1); + + os_memcpy(topicBuf, topic, topic_len); + topicBuf[topic_len] = 0; + + os_memcpy(dataBuf, data, data_len); + dataBuf[data_len] = 0; + + os_printf("MQTT Client: Received topic: %s, data: %s\n", topicBuf, dataBuf); + os_free(topicBuf); + os_free(dataBuf); +#endif + + if (data_cb) + data_cb(args, topic, topic_len, data, data_len); +} + +void ICACHE_FLASH_ATTR +wifiStateChangeCb(uint8_t status) +{ + if (flashConfig.mqtt_enable) { + if (status == wifiGotIP && mqttClient.connState != TCP_CONNECTING) { + MQTT_Connect(&mqttClient); + } + else if (status == wifiIsDisconnected && mqttClient.connState == TCP_CONNECTING) { + MQTT_Disconnect(&mqttClient); + } + } +} + +void ICACHE_FLASH_ATTR +mqtt_client_init() +{ + MQTT_Init(&mqttClient, flashConfig.mqtt_host, flashConfig.mqtt_port, 0, flashConfig.mqtt_timeout, + flashConfig.mqtt_clientid, flashConfig.mqtt_username, flashConfig.mqtt_password, + flashConfig.mqtt_keepalive); + +// removed client_id concat for now until a better solution is devised +// statusTopicStr = (char*)os_zalloc(strlen(flashConfig.mqtt_clientid) + strlen(flashConfig.mqtt_status_topic) + 2); +// os_strcpy(statusTopicStr, flashConfig.mqtt_clientid); +// os_strcat(statusTopicStr, "/"); + +#ifdef BRUNNELS + char* onlineMsg = " is online"; + onlineMsgStr = (char*)os_zalloc(strlen(flashConfig.mqtt_clientid) + strlen(onlineMsg) + 1); + os_strcpy(onlineMsgStr, flashConfig.mqtt_clientid); + os_strcat(onlineMsgStr, onlineMsg); + + char* offlineMsg = " is offline"; + char* offlineMsgStr = (char*)os_zalloc(strlen(flashConfig.mqtt_clientid) + strlen(offlineMsg) + 1); + os_strcpy(offlineMsgStr, flashConfig.mqtt_clientid); + os_strcat(offlineMsgStr, offlineMsg); + + char* lwt = "/lwt"; + char *lwtMsgStr = (char*)os_zalloc(strlen(flashConfig.mqtt_clientid) + strlen(lwt) + 1); + os_strcpy(lwtMsgStr, flashConfig.mqtt_clientid); + os_strcat(lwtMsgStr, lwt); + MQTT_InitLWT(&mqttClient, lwtMsgStr, offlineMsg, 0, 0); +#endif + + MQTT_OnConnected(&mqttClient, mqttConnectedCb); + MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb); + MQTT_OnPublished(&mqttClient, mqttPublishedCb); + MQTT_OnData(&mqttClient, mqttDataCb); + + if (flashConfig.mqtt_enable && strlen(flashConfig.mqtt_host) > 0) + MQTT_Connect(&mqttClient); + + wifiAddStateChangeCb(wifiStateChangeCb); +} + +void ICACHE_FLASH_ATTR +mqtt_client_on_connected(MqttCallback connectedCb) { + connected_cb = connectedCb; +} + +void ICACHE_FLASH_ATTR +mqtt_client_on_disconnected(MqttCallback disconnectedCb) { + disconnected_cb = disconnectedCb; +} + +void ICACHE_FLASH_ATTR +mqtt_client_on_published(MqttCallback publishedCb) { + published_cb = publishedCb; +} + +void ICACHE_FLASH_ATTR +mqtt_client_on_data(MqttDataCallback dataCb) { + data_cb = dataCb; +} + +#endif // MQTT diff --git a/esp-link/mqtt_client.h b/esp-link/mqtt_client.h new file mode 100644 index 0000000..75fc8dc --- /dev/null +++ b/esp-link/mqtt_client.h @@ -0,0 +1,15 @@ +#ifdef MQTT +#ifndef MQTT_CLIENT_H +#define MQTT_CLIENT_H +#include "mqtt.h" + +extern MQTT_Client mqttClient; +extern char* statusTopicStr; +void mqtt_client_init(); +void mqtt_client_on_connected(MqttCallback connectedCb); +void mqtt_client_on_disconnected(MqttCallback disconnectedCb); +void mqtt_client_on_published(MqttCallback publishedCb); +void mqtt_client_on_data(MqttDataCallback dataCb); + +#endif //MQTT_CLIENT_H +#endif // MQTT diff --git a/esp-link/status.c b/esp-link/status.c new file mode 100644 index 0000000..a49726f --- /dev/null +++ b/esp-link/status.c @@ -0,0 +1,129 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt + +#include +#include "config.h" +#include "serled.h" +#include "cgiwifi.h" + +#ifdef MQTT +#include "mqtt.h" +#include "mqtt_client.h" +extern MQTT_Client mqttClient; + +//===== MQTT Status update + +// Every minute... +#define MQTT_STATUS_INTERVAL (60*1000) + +static ETSTimer mqttStatusTimer; + +int ICACHE_FLASH_ATTR +mqttStatusMsg(char *buf) { + sint8 rssi = wifi_station_get_rssi(); + if (rssi > 0) rssi = 0; // not connected or other error + //os_printf("timer rssi=%d\n", rssi); + + // compose MQTT message + return os_sprintf(buf, + "{\"rssi\":%d, \"heap_free\":%ld}", + rssi, (unsigned long)system_get_free_heap_size()); +} + +// Timer callback to send an RSSI update to a monitoring system +static void ICACHE_FLASH_ATTR mqttStatusCb(void *v) { + if (!flashConfig.mqtt_status_enable || os_strlen(flashConfig.mqtt_status_topic) == 0 || + mqttClient.connState != MQTT_CONNECTED) + return; + + char buf[128]; + mqttStatusMsg(buf); + MQTT_Publish(&mqttClient, flashConfig.mqtt_status_topic, buf, 1, 0); +} + + + +#endif // MQTT + +//===== "CONN" LED status indication + +static ETSTimer ledTimer; + +// Set the LED on or off, respecting the defined polarity +static void ICACHE_FLASH_ATTR setLed(int on) { + int8_t pin = flashConfig.conn_led_pin; + if (pin < 0) return; // disabled + // LED is active-low + if (on) { + gpio_output_set(0, (1<= 0) { + makeGpio(flashConfig.conn_led_pin); + setLed(1); + } +#ifdef STATUS_DBG + os_printf("CONN led=%d\n", flashConfig.conn_led_pin); +#endif + + os_timer_disarm(&ledTimer); + os_timer_setfn(&ledTimer, ledTimerCb, NULL); + os_timer_arm(&ledTimer, 2000, 0); + +#ifdef MQTT + os_timer_disarm(&mqttStatusTimer); + os_timer_setfn(&mqttStatusTimer, mqttStatusCb, NULL); + os_timer_arm(&mqttStatusTimer, MQTT_STATUS_INTERVAL, 1); // recurring timer +#endif // MQTT +} + + diff --git a/user/status.h b/esp-link/status.h similarity index 77% rename from user/status.h rename to esp-link/status.h index 0f89edf..c6fb855 100644 --- a/user/status.h +++ b/esp-link/status.h @@ -1,6 +1,7 @@ #ifndef STATUS_H #define STATUS_H +int mqttStatusMsg(char *buf); void statusWifiUpdate(uint8_t state); void statusInit(void); diff --git a/espfs/espfs.c b/espfs/espfs.c index 3da6692..f9942f3 100644 --- a/espfs/espfs.c +++ b/espfs/espfs.c @@ -40,11 +40,6 @@ It's written for use with httpd, but doesn't need to be used as such. #include "espfsformat.h" #include "espfs.h" -#ifdef ESPFS_HEATSHRINK -#include "heatshrink_config_custom.h" -#include "heatshrink_decoder.h" -#endif - static char* espFsData = NULL; struct EspFsFile { @@ -114,7 +109,9 @@ void ICACHE_FLASH_ATTR memcpyAligned(char *dst, char *src, int len) { // Returns flags of opened file. int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { if (fh == NULL) { +#ifdef ESPFS_DBG os_printf("File handle not ready\n"); +#endif return -1; } @@ -126,7 +123,9 @@ int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { //Open a file and return a pointer to the file desc struct. EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { if (espFsData == NULL) { +#ifdef ESPFS_DBG os_printf("Call espFsInit first!\n"); +#endif return NULL; } char *p=espFsData; @@ -142,7 +141,9 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { //Grab the next file header. os_memcpy(&h, p, sizeof(EspFsHeader)); if (h.magic!=ESPFS_MAGIC) { +#ifdef ESPFS_DBG os_printf("Magic mismatch. EspFS image broken.\n"); +#endif return NULL; } if (h.flags&FLAG_LASTFILE) { @@ -167,20 +168,10 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { r->posDecomp=0; if (h.compression==COMPRESS_NONE) { r->decompData=NULL; -#ifdef ESPFS_HEATSHRINK - } else if (h.compression==COMPRESS_HEATSHRINK) { - //File is compressed with Heatshrink. - char parm; - heatshrink_decoder *dec; - //Decoder params are stored in 1st byte. - memcpyAligned(&parm, r->posComp, 1); - r->posComp++; - //os_printf("Heatshrink compressed file; decode parms = %x\n", parm); - dec=heatshrink_decoder_alloc(16, (parm>>4)&0xf, parm&0xf); - r->decompData=dec; -#endif } else { +#ifdef ESPFS_DBG os_printf("Invalid compression: %d\n", h.compression); +#endif return NULL; } return r; @@ -209,48 +200,6 @@ int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { fh->posComp+=len; // os_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp); return len; -#ifdef ESPFS_HEATSHRINK - } else if (fh->decompressor==COMPRESS_HEATSHRINK) { - int decoded=0; - size_t elen, rlen; - char ebuff[16]; - heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; -// os_printf("Alloc %p\n", dec); - if (fh->posDecomp == fdlen) { - return 0; - } - - // We must ensure that whole file is decompressed and written to output buffer. - // This means even when there is no input data (elen==0) try to poll decoder until - // posDecomp equals decompressed file length - - while(decodedposComp - fh->posStart); - if (elen>0) { - memcpyAligned(ebuff, fh->posComp, 16); - heatshrink_decoder_sink(dec, (uint8_t *)ebuff, (elen>16)?16:elen, &rlen); - fh->posComp+=rlen; - } - //Grab decompressed data and put into buff - heatshrink_decoder_poll(dec, (uint8_t *)buff, len-decoded, &rlen); - fh->posDecomp+=rlen; - buff+=rlen; - decoded+=rlen; - -// os_printf("Elen %d rlen %d d %d pd %ld fdl %d\n",elen,rlen,decoded, fh->posDecomp, fdlen); - - if (elen == 0) { - if (fh->posDecomp == fdlen) { -// os_printf("Decoder finish\n"); - heatshrink_decoder_finish(dec); - } - return decoded; - } - } - return len; -#endif } return 0; } @@ -258,13 +207,6 @@ int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { //Close the file. void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { if (fh==NULL) return; -#ifdef ESPFS_HEATSHRINK - if (fh->decompressor==COMPRESS_HEATSHRINK) { - heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; - heatshrink_decoder_free(dec); -// os_printf("Freed %p\n", dec); - } -#endif //os_printf("Freed %p\n", fh); os_free(fh); } diff --git a/espfs/espfs.h b/espfs/espfs.h index c41df0a..c8e13e7 100644 --- a/espfs/espfs.h +++ b/espfs/espfs.h @@ -1,10 +1,6 @@ #ifndef ESPFS_H #define ESPFS_H -// This define is done in Makefile. If you do not use default Makefile, uncomment -// to be able to use Heatshrink-compressed espfs images. -//#define ESPFS_HEATSHRINK - typedef enum { ESPFS_INIT_RESULT_OK, ESPFS_INIT_RESULT_NO_IMAGE, diff --git a/espfs/espfstest/Makefile b/espfs/espfstest/Makefile deleted file mode 100644 index f8296f4..0000000 --- a/espfs/espfstest/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -CFLAGS=-I../../lib/heatshrink -I.. -std=gnu99 -DESPFS_HEATSHRINK - -espfstest: main.o espfs.o heatshrink_decoder.o - $(CC) -o $@ $^ - -espfs.o: ../espfs.c - $(CC) $(CFLAGS) -c $^ -o $@ - -heatshrink_decoder.o: ../heatshrink_decoder.c - $(CC) $(CFLAGS) -c $^ -o $@ - -clean: - rm -f *.o espfstest diff --git a/espfs/espfstest/main.c b/espfs/espfstest/main.c deleted file mode 100644 index 16a6287..0000000 --- a/espfs/espfstest/main.c +++ /dev/null @@ -1,67 +0,0 @@ -/* -Simple and stupid file decompressor for an espfs image. Mostly used as a testbed for espfs.c and -the decompressors: code compiled natively is way easier to debug using gdb et all :) -*/ -#include -#include -#include -#include -#include -#include -#include -#include - - -#include "espfs.h" - -char *espFsData; - -int main(int argc, char **argv) { - int f, out; - int len; - char buff[128]; - EspFsFile *ef; - off_t size; - EspFsInitResult ir; - - if (argc!=3) { - printf("Usage: %s espfs-image file\nExpands file from the espfs-image archive.\n", argv[0]); - exit(0); - } - - f=open(argv[1], O_RDONLY); - if (f<=0) { - perror(argv[1]); - exit(1); - } - size=lseek(f, 0, SEEK_END); - espFsData=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0); - if (espFsData==MAP_FAILED) { - perror("mmap"); - exit(1); - } - - ir=espFsInit(espFsData); - if (ir != ESPFS_INIT_RESULT_OK) { - printf("Couldn't init espfs filesystem (code %d)\n", ir); - exit(1); - } - - ef=espFsOpen(argv[2]); - if (ef==NULL) { - printf("Couldn't find %s in image.\n", argv[2]); - exit(1); - } - - out=open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0644); - if (out<=0) { - perror(argv[2]); - exit(1); - } - - while ((len=espFsRead(ef, buff, 128))!=0) { - write(out, buff, len); - } - espFsClose(ef); - //munmap, close, ... I can't be bothered. -} diff --git a/espfs/heatshrink_config_custom.h b/espfs/heatshrink_config_custom.h deleted file mode 100644 index f885f87..0000000 --- a/espfs/heatshrink_config_custom.h +++ /dev/null @@ -1,30 +0,0 @@ -//Heatshrink config for the decompressor. -#ifndef HEATSHRINK_CONFIG_H -#define HEATSHRINK_CONFIG_H - -/* Should functionality assuming dynamic allocation be used? */ -#define HEATSHRINK_DYNAMIC_ALLOC 1 - -#if HEATSHRINK_DYNAMIC_ALLOC - /* Optional replacement of malloc/free */ - #ifdef __ets__ - #define HEATSHRINK_MALLOC(SZ) os_malloc(SZ) - #define HEATSHRINK_FREE(P, SZ) os_free(P) - #else - #define HEATSHRINK_MALLOC(SZ) malloc(SZ) - #define HEATSHRINK_FREE(P, SZ) free(P) - #endif -#else - /* Required parameters for static configuration */ - #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 - #define HEATSHRINK_STATIC_WINDOW_BITS 8 - #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 -#endif - -/* Turn on logging for debugging. */ -#define HEATSHRINK_DEBUGGING_LOGS 0 - -/* Use indexing for faster compression. (This requires additional space.) */ -#define HEATSHRINK_USE_INDEX 1 - -#endif diff --git a/espfs/heatshrink_decoder.c b/espfs/heatshrink_decoder.c deleted file mode 100644 index 522b560..0000000 --- a/espfs/heatshrink_decoder.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "espfs.h" -#ifdef ESPFS_HEATSHRINK -//Stupid wrapper so we don't have to move c-files around -//Also loads httpd-specific config. - -#ifdef __ets__ -//esp build - -#include - -#define memset(x,y,z) os_memset(x,y,z) -#define memcpy(x,y,z) os_memcpy(x,y,z) -#endif - -#include "heatshrink_config_custom.h" -#include "../lib/heatshrink/heatshrink_decoder.c" - - -#endif diff --git a/espfs/mkespfsimage/Makefile b/espfs/mkespfsimage/Makefile index 8a39d46..2980331 100644 --- a/espfs/mkespfsimage/Makefile +++ b/espfs/mkespfsimage/Makefile @@ -1,16 +1,45 @@ GZIP_COMPRESSION ?= no -USE_HEATSHRINK ?= yes -CFLAGS=-I../../lib/heatshrink -I.. -std=gnu99 +ifeq ($(OS),Windows_NT) + +TARGET = mkespfsimage.exe + +CC = gcc +LD = $(CC) +CFLAGS=-c -I.. -Imman-win32 -std=gnu99 +LDFLAGS=-Lmman-win32 -lmman + ifeq ("$(GZIP_COMPRESSION)","yes") -CFLAGS += -DESPFS_GZIP +CFLAGS += -DESPFS_GZIP +LDFLAGS += -lz endif -ifeq ("$(USE_HEATSHRINK)","yes") -CFLAGS += -DESPFS_HEATSHRINK +OBJECTS = main.o + +all: libmman $(TARGET) + +libmman: + $(Q) make -C mman-win32 + +$(TARGET): $(OBJECTS) + $(LD) -o $@ $^ $(LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(OBJECTS) $(TARGET) ./mman-win32/libmman.a ./mman-win32/mman.o + +.PHONY: all clean + +else + +CFLAGS=-I.. -std=gnu99 +ifeq ("$(GZIP_COMPRESSION)","yes") +CFLAGS += -DESPFS_GZIP endif -OBJS=main.o heatshrink_encoder.o +OBJS=main.o TARGET=mkespfsimage $(TARGET): $(OBJS) @@ -21,4 +50,6 @@ else endif clean: - rm -f $(TARGET) $(OBJS) \ No newline at end of file + rm -f $(TARGET) $(OBJS) + +endif \ No newline at end of file diff --git a/espfs/mkespfsimage/heatshrink_encoder.c b/espfs/mkespfsimage/heatshrink_encoder.c deleted file mode 100644 index b563970..0000000 --- a/espfs/mkespfsimage/heatshrink_encoder.c +++ /dev/null @@ -1,4 +0,0 @@ -//Stupid wraparound include to make sure object file doesn't end up in heatshrink dir -#ifdef ESPFS_HEATSHRINK -#include "../lib/heatshrink/heatshrink_encoder.c" -#endif \ No newline at end of file diff --git a/espfs/mkespfsimage/main.c b/espfs/mkespfsimage/main.c index 4d5da0d..be3aeb7 100644 --- a/espfs/mkespfsimage/main.c +++ b/espfs/mkespfsimage/main.c @@ -5,27 +5,29 @@ #include #include #include -#include -#include #include #include "espfs.h" -#include "espfsformat.h" - -//Heatshrink -#ifdef ESPFS_HEATSHRINK -#include "heatshrink_common.h" -#include "heatshrink_config.h" -#include "heatshrink_encoder.h" +#ifdef __MINGW32__ +#include "mman-win32/mman.h" +#else +#include +#endif +#ifdef __WIN32__ +#include +#else +#include #endif +#include "espfsformat.h" //Gzip #ifdef ESPFS_GZIP -// If compiler complains about missing header, try running "sudo apt-get install zlib1g-dev" +// If compiler complains about missing header, try running "sudo apt-get install zlib1g-dev" // to install missing package. #include #endif + //Routines to convert host format to the endianness used in the xtensa short htoxs(short in) { char r[2]; @@ -43,53 +45,6 @@ int htoxl(int in) { return *((int *)r); } -#ifdef ESPFS_HEATSHRINK -size_t compressHeatshrink(char *in, int insize, char *out, int outsize, int level) { - char *inp=in; - char *outp=out; - size_t len; - int ws[]={5, 6, 8, 11, 13}; - int ls[]={3, 3, 4, 4, 4}; - HSE_poll_res pres; - HSE_sink_res sres; - size_t r; - if (level==-1) level=8; - level=(level-1)/2; //level is now 0, 1, 2, 3, 4 - heatshrink_encoder *enc=heatshrink_encoder_alloc(ws[level], ls[level]); - if (enc==NULL) { - perror("allocating mem for heatshrink"); - exit(1); - } - //Save encoder parms as first byte - *outp=(ws[level]<<4)|ls[level]; - outp++; outsize--; - - r=1; - do { - if (insize>0) { - sres=heatshrink_encoder_sink(enc, inp, insize, &len); - if (sres!=HSER_SINK_OK) break; - inp+=len; insize-=len; - if (insize==0) heatshrink_encoder_finish(enc); - } - do { - pres=heatshrink_encoder_poll(enc, outp, outsize, &len); - if (pres!=HSER_POLL_MORE && pres!=HSER_POLL_EMPTY) break; - outp+=len; outsize-=len; - r+=len; - } while (pres==HSER_POLL_MORE); - } while (insize!=0); - - if (insize!=0) { - fprintf(stderr, "Heatshrink: Bug? insize is still %d. sres=%d pres=%d\n", insize, sres, pres); - exit(1); - } - - heatshrink_encoder_free(enc); - return r; -} -#endif - #ifdef ESPFS_GZIP size_t compressGzip(char *in, int insize, char *out, int outsize, int level) { z_stream stream; @@ -202,11 +157,6 @@ int handleFile(int f, char *name, int compression, int level, char **compName, o if (compression==COMPRESS_NONE) { csize=size; cdat=fdat; -#ifdef ESPFS_HEATSHRINK - } else if (compression==COMPRESS_HEATSHRINK) { - cdat=malloc(size*2); - csize=compressHeatshrink(fdat, size, cdat, size*2, level); -#endif } else { fprintf(stderr, "Unknown compression - %d\n", compression); exit(1); @@ -245,9 +195,7 @@ int handleFile(int f, char *name, int compression, int level, char **compName, o munmap(fdat, size); if (compName != NULL) { - if (h.compression==COMPRESS_HEATSHRINK) { - *compName = "heatshrink"; - } else if (h.compression==COMPRESS_NONE) { + if (h.compression==COMPRESS_NONE) { if (h.flags & FLAG_GZIP) { *compName = "gzip"; } else { @@ -284,11 +232,7 @@ int main(int argc, char **argv) { int compType; //default compression type - heatshrink int compLvl=-1; -#ifdef ESPFS_HEATSHRINK - compType = COMPRESS_HEATSHRINK; -#else compType = COMPRESS_NONE; -#endif for (x=1; x=x-2) { @@ -322,11 +266,7 @@ int main(int argc, char **argv) { #endif fprintf(stderr, "> out.espfs\n"); fprintf(stderr, "Compressors:\n"); -#ifdef ESPFS_HEATSHRINK - fprintf(stderr, "0 - None\n1 - Heatshrink(default)\n"); -#else fprintf(stderr, "0 - None(default)\n"); -#endif fprintf(stderr, "\nCompression level: 1 is worst but low RAM usage, higher is better compression \nbut uses more ram on decompression. -1 = compressors default.\n"); #ifdef ESPFS_GZIP fprintf(stderr, "\nGzipped extensions: list of comma separated, case sensitive file extensions \nthat will be gzipped. Defaults to 'html,css,js'\n"); @@ -334,6 +274,10 @@ int main(int argc, char **argv) { exit(0); } +#ifdef __WIN32__ + setmode(fileno(stdout), _O_BINARY); +#endif + while(fgets(fileName, sizeof(fileName), stdin)) { //Kill off '\n' at the end fileName[strlen(fileName)-1]=0; diff --git a/espfs/mkespfsimage/mman-win32/Makefile b/espfs/mkespfsimage/mman-win32/Makefile new file mode 100644 index 0000000..5624c1b --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/Makefile @@ -0,0 +1,48 @@ +# +# mman-win32 (mingw32) Makefile +# +include config.mak + +ifeq ($(BUILD_STATIC),yes) + TARGETS+=libmman.a + INSTALL+=static-install +endif +ifeq ($(BUILD_MSVC),yes) + SHFLAGS+=-Wl,--output-def,libmman.def + INSTALL+=lib-install +endif + +all: $(TARGETS) + +mman.o: mman.c mman.h + $(CC) -o mman.o -c mman.c -Wall -O3 -fomit-frame-pointer + +libmman.a: mman.o + $(AR) cru libmman.a mman.o + $(RANLIB) libmman.a + +static-install: + mkdir -p $(DESTDIR)$(libdir) + cp libmman.a $(DESTDIR)$(libdir) + mkdir -p $(DESTDIR)$(incdir) + cp mman.h $(DESTDIR)$(incdir) + +lib-install: + mkdir -p $(DESTDIR)$(libdir) + cp libmman.lib $(DESTDIR)$(libdir) + +install: $(INSTALL) + +test.exe: test.c mman.c mman.h + $(CC) -o test.exe test.c -L. -lmman + +test: $(TARGETS) test.exe + test.exe + +clean:: + rm -f mman.o libmman.a libmman.def libmman.lib test.exe *.dat + +distclean: clean + rm -f config.mak + +.PHONY: clean distclean install test diff --git a/espfs/mkespfsimage/mman-win32/config.mak b/espfs/mkespfsimage/mman-win32/config.mak new file mode 100644 index 0000000..2d2ae1b --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/config.mak @@ -0,0 +1,11 @@ +# Automatically generated by configure +PREFIX=/mingw +libdir=/mingw/lib +incdir=/mingw/include/sys +AR=ar +CC=gcc +RANLIB=ranlib +STRIP=strip +BUILD_STATIC=yes +BUILD_MSVC= +LIBCMD=echo ignoring lib diff --git a/espfs/mkespfsimage/mman-win32/configure b/espfs/mkespfsimage/mman-win32/configure new file mode 100644 index 0000000..c928f11 --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/configure @@ -0,0 +1,157 @@ +#!/bin/sh +# mmap-win32 configure script +# +# Parts copied from FFmpeg's configure +# + +set_all(){ + value=$1 + shift + for var in $*; do + eval $var=$value + done +} + +enable(){ + set_all yes $* +} + +disable(){ + set_all no $* +} + +enabled(){ + eval test "x\$$1" = "xyes" +} + +disabled(){ + eval test "x\$$1" = "xno" +} + +show_help(){ + echo "Usage: configure [options]" + echo "Options: [defaults in brackets after descriptions]" + echo "All \"enable\" options have \"disable\" counterparts" + echo + echo " --help print this message" + echo " --prefix=PREFIX install in PREFIX [$PREFIX]" + echo " --libdir=DIR install libs in DIR [$PREFIX/lib]" + echo " --incdir=DIR install includes in DIR [$PREFIX/include]" + echo " --enable-static build static libraries [yes]" + echo " --enable-msvc create msvc-compatible import lib [auto]" + echo + echo " --cc=CC use C compiler CC [$cc_default]" + echo " --cross-prefix=PREFIX use PREFIX for compilation tools [$cross_prefix]" + exit 1 +} + +die_unknown(){ + echo "Unknown option \"$1\"." + echo "See $0 --help for available options." + exit 1 +} + +PREFIX="/mingw" +libdir="${PREFIX}/lib" +incdir="${PREFIX}/include/sys" +ar="ar" +cc_default="gcc" +ranlib="ranlib" +strip="strip" + +DEFAULT="msvc +" + +DEFAULT_YES="static + stripping +" + +CMDLINE_SELECT="$DEFAULT + $DEFAULT_NO + $DEFAULT_YES +" + +enable $DEFAULT_YES +disable $DEFAULT_NO + +for opt do + optval="${opt#*=}" + case "$opt" in + --help) + show_help + ;; + --prefix=*) + PREFIX="$optval" + ;; + --libdir=*) + libdir="$optval" + ;; + --incdir=*) + incdir="$optval" + ;; + --cc=*) + cc="$optval" + ;; + --cross-prefix=*) + cross_prefix="$optval" + ;; + --enable-?*|--disable-?*) + eval `echo "$opt" | sed 's/--/action=/;s/-/ option=/;s/-/_/g'` + echo "$CMDLINE_SELECT" | grep -q "^ *$option\$" || die_unknown $opt + $action $option + ;; + *) + die_unknown $opt + ;; + esac +done + +ar="${cross_prefix}${ar}" +cc_default="${cross_prefix}${cc_default}" +ranlib="${cross_prefix}${ranlib}" +strip="${cross_prefix}${strip}" + +if ! test -z $cc; then + cc_default="${cc}" +fi +cc="${cc_default}" + +disabled static && { + echo "At least one library type must be set."; + exit 1; +} + +if enabled msvc; then + lib /? > /dev/null 2>&1 /dev/null || { + echo "MSVC's lib command not found." + echo "Make sure MSVC is installed and its bin folder is in your \$PATH." + exit 1 + } +fi + +if ! enabled stripping; then + strip="echo ignoring strip" +fi + +enabled msvc && libcmd="lib" || libcmd="echo ignoring lib" + +echo "# Automatically generated by configure" > config.mak +echo "PREFIX=$PREFIX" >> config.mak +echo "libdir=$libdir" >> config.mak +echo "incdir=$incdir" >> config.mak +echo "AR=$ar" >> config.mak +echo "CC=$cc" >> config.mak +echo "RANLIB=$ranlib" >> config.mak +echo "STRIP=$strip" >> config.mak +echo "BUILD_STATIC=$static" >> config.mak +echo "BUILD_MSVC=$msvc" >> config.mak +echo "LIBCMD=$libcmd" >> config.mak + +echo "prefix: $PREFIX" +echo "libdir: $libdir" +echo "incdir: $incdir" +echo "ar: $ar" +echo "cc: $cc" +echo "ranlib: $ranlib" +echo "strip: $strip" +echo "static: $static" diff --git a/espfs/mkespfsimage/mman-win32/mman.c b/espfs/mkespfsimage/mman-win32/mman.c new file mode 100644 index 0000000..486ed94 --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/mman.c @@ -0,0 +1,180 @@ + +#include +#include +#include + +#include "mman.h" + +#ifndef FILE_MAP_EXECUTE +#define FILE_MAP_EXECUTE 0x0020 +#endif /* FILE_MAP_EXECUTE */ + +static int __map_mman_error(const DWORD err, const int deferr) +{ + if (err == 0) + return 0; + //TODO: implement + return err; +} + +static DWORD __map_mmap_prot_page(const int prot) +{ + DWORD protect = 0; + + if (prot == PROT_NONE) + return protect; + + if ((prot & PROT_EXEC) != 0) + { + protect = ((prot & PROT_WRITE) != 0) ? + PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; + } + else + { + protect = ((prot & PROT_WRITE) != 0) ? + PAGE_READWRITE : PAGE_READONLY; + } + + return protect; +} + +static DWORD __map_mmap_prot_file(const int prot) +{ + DWORD desiredAccess = 0; + + if (prot == PROT_NONE) + return desiredAccess; + + if ((prot & PROT_READ) != 0) + desiredAccess |= FILE_MAP_READ; + if ((prot & PROT_WRITE) != 0) + desiredAccess |= FILE_MAP_WRITE; + if ((prot & PROT_EXEC) != 0) + desiredAccess |= FILE_MAP_EXECUTE; + + return desiredAccess; +} + +void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) +{ + HANDLE fm, h; + + void * map = MAP_FAILED; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4293) +#endif + + const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)off : (DWORD)(off & 0xFFFFFFFFL); + const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL); + const DWORD protect = __map_mmap_prot_page(prot); + const DWORD desiredAccess = __map_mmap_prot_file(prot); + + const off_t maxSize = off + (off_t)len; + + const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL); + const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL); + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + errno = 0; + + if (len == 0 + /* Unsupported flag combinations */ + || (flags & MAP_FIXED) != 0 + /* Usupported protection combinations */ + || prot == PROT_EXEC) + { + errno = EINVAL; + return MAP_FAILED; + } + + h = ((flags & MAP_ANONYMOUS) == 0) ? + (HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE; + + if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE) + { + errno = EBADF; + return MAP_FAILED; + } + + fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL); + + if (fm == NULL) + { + errno = __map_mman_error(GetLastError(), EPERM); + return MAP_FAILED; + } + + map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len); + + CloseHandle(fm); + + if (map == NULL) + { + errno = __map_mman_error(GetLastError(), EPERM); + return MAP_FAILED; + } + + return map; +} + +int munmap(void *addr, size_t len) +{ + if (UnmapViewOfFile(addr)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int mprotect(void *addr, size_t len, int prot) +{ + DWORD newProtect = __map_mmap_prot_page(prot); + DWORD oldProtect = 0; + + if (VirtualProtect(addr, len, newProtect, &oldProtect)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int msync(void *addr, size_t len, int flags) +{ + if (FlushViewOfFile(addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int mlock(const void *addr, size_t len) +{ + if (VirtualLock((LPVOID)addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int munlock(const void *addr, size_t len) +{ + if (VirtualUnlock((LPVOID)addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} diff --git a/espfs/mkespfsimage/mman-win32/mman.h b/espfs/mkespfsimage/mman-win32/mman.h new file mode 100644 index 0000000..ffa3748 --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/mman.h @@ -0,0 +1,55 @@ +/* + * sys/mman.h + * mman-win32 + */ + +#ifndef _SYS_MMAN_H_ +#define _SYS_MMAN_H_ + +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. +#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. +#endif + +/* All the headers include this file. */ +#ifndef _MSC_VER +#include <_mingw.h> +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_FILE 0 +#define MAP_SHARED 1 +#define MAP_PRIVATE 2 +#define MAP_TYPE 0xf +#define MAP_FIXED 0x10 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS + +#define MAP_FAILED ((void *)-1) + +/* Flags for msync. */ +#define MS_ASYNC 1 +#define MS_SYNC 2 +#define MS_INVALIDATE 4 + +void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); +int munmap(void *addr, size_t len); +int mprotect(void *addr, size_t len, int prot); +int msync(void *addr, size_t len, int flags); +int mlock(const void *addr, size_t len); +int munlock(const void *addr, size_t len); + +#ifdef __cplusplus +}; +#endif + +#endif /* _SYS_MMAN_H_ */ diff --git a/espfs/mkespfsimage/mman-win32/test.c b/espfs/mkespfsimage/mman-win32/test.c new file mode 100644 index 0000000..9374b9f --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/test.c @@ -0,0 +1,235 @@ + +#include "mman.h" + +#include +#include +#include + +#ifndef NULL +#define NULL (void*)0 +#endif + +const char* map_file_name = "map_file.dat"; + +int test_anon_map_readwrite() +{ + void* map = mmap(NULL, 1024, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + { + printf("mmap (MAP_ANONYMOUS, PROT_READ | PROT_WRITE) returned unexpected error: %d\n", errno); + return -1; + } + + *((unsigned char*)map) = 1; + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap (MAP_ANONYMOUS, PROT_READ | PROT_WRITE) returned unexpected error: %d\n", errno); + + return result; +} + +int test_anon_map_readonly() +{ + void* map = mmap(NULL, 1024, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + { + printf("mmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); + return -1; + } + + *((unsigned char*)map) = 1; + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); + + return result; +} + +int test_anon_map_writeonly() +{ + void* map = mmap(NULL, 1024, PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + { + printf("mmap (MAP_ANONYMOUS, PROT_WRITE) returned unexpected error: %d\n", errno); + return -1; + } + + *((unsigned char*)map) = 1; + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap (MAP_ANONYMOUS, PROT_WRITE) returned unexpected error: %d\n", errno); + + return result; +} + +int test_anon_map_readonly_nowrite() +{ + void* map = mmap(NULL, 1024, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + { + printf("mmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); + return -1; + } + + if (*((unsigned char*)map) != 0) + printf("test_anon_map_readonly_nowrite (MAP_ANONYMOUS, PROT_READ) returned unexpected value: %d\n", + (int)*((unsigned char*)map)); + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); + + return result; +} + +int test_file_map_readwrite() +{ + mode_t mode = S_IRUSR | S_IWUSR; + int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); + + void* map = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); + if (map == MAP_FAILED) + { + printf("mmap returned unexpected error: %d\n", errno); + return -1; + } + + *((unsigned char*)map) = 1; + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap returned unexpected error: %d\n", errno); + + close(o); + + /*TODO: get file info and content and compare it with the sources conditions */ + unlink(map_file_name); + + return result; +} + +int test_file_map_mlock_munlock() +{ + const size_t map_size = 1024; + + int result = 0; + mode_t mode = S_IRUSR | S_IWUSR; + int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); + if (o == -1) + { + printf("unable to create file %s: %d\n", map_file_name, errno); + return -1; + } + + void* map = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); + if (map == MAP_FAILED) + { + printf("mmap returned unexpected error: %d\n", errno); + result = -1; + goto done_close; + } + + if (mlock(map, map_size) != 0) + { + printf("mlock returned unexpected error: %d\n", errno); + result = -1; + goto done_munmap; + } + + *((unsigned char*)map) = 1; + + if (munlock(map, map_size) != 0) + { + printf("munlock returned unexpected error: %d\n", errno); + result = -1; + } + +done_munmap: + result = munmap(map, map_size); + + if (result != 0) + printf("munmap returned unexpected error: %d\n", errno); + +done_close: + close(o); + + unlink(map_file_name); +done: + return result; +} + +int test_file_map_msync() +{ + const size_t map_size = 1024; + + int result = 0; + mode_t mode = S_IRUSR | S_IWUSR; + int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); + if (o == -1) + { + printf("unable to create file %s: %d\n", map_file_name, errno); + return -1; + } + + void* map = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); + if (map == MAP_FAILED) + { + printf("mmap returned unexpected error: %d\n", errno); + result = -1; + goto done_close; + } + + *((unsigned char*)map) = 1; + + if (msync(map, map_size, MS_SYNC) != 0) + { + printf("msync returned unexpected error: %d\n", errno); + result = -1; + } + + result = munmap(map, map_size); + + if (result != 0) + printf("munmap returned unexpected error: %d\n", errno); + +done_close: + close(o); + + unlink(map_file_name); +done: + return result; +} + +#define EXEC_TEST(name) \ + if (name() != 0) { result = -1; printf( #name ": fail\n"); } \ + else { printf(#name ": pass\n"); } + +int main() +{ + int result = 0; + + EXEC_TEST(test_anon_map_readwrite); + //NOTE: this test must cause an access violation exception + //EXEC_TEST(test_anon_map_readonly); + EXEC_TEST(test_anon_map_readonly_nowrite); + EXEC_TEST(test_anon_map_writeonly); + + EXEC_TEST(test_file_map_readwrite); + EXEC_TEST(test_file_map_mlock_munlock); + EXEC_TEST(test_file_map_msync); + //TODO: EXEC_TEST(test_file_map_mprotect); + + return result; +} diff --git a/espmake.cmd b/espmake.cmd new file mode 100644 index 0000000..42e25a6 --- /dev/null +++ b/espmake.cmd @@ -0,0 +1,7 @@ +@echo off + +REM remove automatic created obj folder +rd obj /S /Q >nul 2>&1 + +PATH=%PATH%;C:\Espressif\xtensa-lx106-elf\bin;C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\espressif\git-bin;C:\espressif\java-bin;C:\Python27 +make -f Makefile %1 %2 %3 %4 %5 \ No newline at end of file diff --git a/html/console.html b/html/console.html index db4c0a6..9dc359c 100644 --- a/html/console.html +++ b/html/console.html @@ -20,7 +20,7 @@ diff --git a/html/mqtt.html b/html/mqtt.html new file mode 100644 index 0000000..28d763c --- /dev/null +++ b/html/mqtt.html @@ -0,0 +1,101 @@ +
+
+

REST & MQTT

+
+ +
+
+
+

The REST & MQTT support uses the SLIP protocol over the serial port to enable + the attached microcontroller to initiate outbound connections. + The REST support lets the uC initiate simple HTTP requests while the MQTT support + lets it communicate with an MQTT server bidirectionally at QoS 0 thru 2.

+

The MQTT support is in the form of a built-in client that connects to a server + 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.

+

The MQTT client also supports sending periodic status messages about esp-link itself, + including Wifi RSSI, and free heap memory.

+
+ + +
+
+
+
+
+
+

MQTT +
+

+ +
+
+
+
+

Status reporting +
+

+ +
+
+

REST

+

REST requests are enabled as soon as SLIP is enabled. + There are no REST-specific settings.

+
+
+
+
+
+ + + + + diff --git a/html/mqtt.js b/html/mqtt.js new file mode 100644 index 0000000..bf37ed2 --- /dev/null +++ b/html/mqtt.js @@ -0,0 +1,76 @@ +//===== MQTT cards + +function changeMqtt(e) { + e.preventDefault(); + var url = "mqtt?1=1"; + var i, inputs = document.querySelectorAll('#mqtt-form input'); + for (i = 0; i < inputs.length; i++) { + if (inputs[i].type != "checkbox") + url += "&" + inputs[i].name + "=" + inputs[i].value; + }; + + hideWarning(); + var cb = $("#mqtt-button"); + addClass(cb, 'pure-button-disabled'); + ajaxSpin("POST", url, function (resp) { + showNotification("MQTT updated"); + removeClass(cb, 'pure-button-disabled'); + }, function (s, st) { + showWarning("Error: " + st); + removeClass(cb, 'pure-button-disabled'); + window.setTimeout(fetchMqtt, 100); + }); +} + +function displayMqtt(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) { + if (el.type == "checkbox") el.checked = data[v] > 0; + else el.value = data[v]; + } + }); + $("#mqtt-spinner").setAttribute("hidden", ""); + $("#mqtt-status-spinner").setAttribute("hidden", ""); + $("#mqtt-form").removeAttribute("hidden"); + $("#mqtt-status-form").removeAttribute("hidden"); + + var i, inputs = $("input"); + for (i = 0; i < inputs.length; i++) { + if (inputs[i].type == "checkbox") + inputs[i].onclick = function () { console.log(this); setMqtt(this.name, this.checked) }; + } +} + +function fetchMqtt() { + ajaxJson("GET", "/mqtt", displayMqtt, function () { + window.setTimeout(fetchMqtt, 1000); + }); +} + +function changeMqttStatus(e) { + e.preventDefault(); + var v = document.querySelector('input[name="mqtt-status-topic"]').value; + ajaxSpin("POST", "/mqtt?mqtt-status-topic=" + v, function () { + showNotification("MQTT status settings updated"); + }, function (s, st) { + showWarning("Error: " + st); + window.setTimeout(fetchMqtt, 100); + }); +} + +function setMqtt(name, v) { + ajaxSpin("POST", "/mqtt?" + name + "=" + (v ? 1 : 0), function () { + var n = name.replace("-enable", ""); + showNotification(n + " is now " + (v ? "enabled" : "disabled")); + }, function () { + showWarning("Enable/disable failed"); + window.setTimeout(fetchMqtt, 100); + }); +} \ No newline at end of file diff --git a/html/style.css b/html/style.css index 29d15a3..eba050f 100644 --- a/html/style.css +++ b/html/style.css @@ -3,6 +3,15 @@ html, button, input, select, textarea, .pure-g [class *= "pure-u"] { font-family: sans-serif; } +input[type="text"], input[type="password"] { + width: 100%; +} + +input[type=checkbox] { + float: left; + margin: .35em 0.4em; +} + body { color: #777; } diff --git a/html/ui.js b/html/ui.js index 5de2f4c..16d6e7f 100644 --- a/html/ui.js +++ b/html/ui.js @@ -316,43 +316,42 @@ function createInputForPin(pin) { input.type = "radio"; input.name = "pins"; input.data = pin.name; - input.className = "pin-input"; + input.className = "pin-input"; input.value= pin.value; input.id = "opt-" + pin.value; if (currPin == pin.name) input.checked = "1"; - var descr = m('"); - var div = document.createElement("div"); - div.appendChild(input); - div.appendChild(descr); - return div; + var descr = m('"); + var div = document.createElement("div"); + div.appendChild(input); + div.appendChild(descr); + return div; } function displayPins(resp) { - var po = $("#pin-mux"); - po.innerHTML = ""; - currPin = resp.curr; - resp.map.forEach(function(v) { - po.appendChild(createInputForPin(v)); - }); - var i, inputs = $(".pin-input"); - for (i=0; i wrote this file. As long as you retain - * this notice you can do whatever you want with this stuff. If we meet some day, - * and you think this stuff is worth it, you can buy me a beer in return. - * ---------------------------------------------------------------------------- - * Modified and enhanced by Thorsten von Eicken in 2015 - * ---------------------------------------------------------------------------- - */ +* ---------------------------------------------------------------------------- +* "THE BEER-WARE LICENSE" (Revision 42): +* Jeroen Domburg wrote this file. As long as you retain +* this notice you can do whatever you want with this stuff. If we meet some day, +* and you think this stuff is worth it, you can buy me a beer in return. +* ---------------------------------------------------------------------------- +* Modified and enhanced by Thorsten von Eicken in 2015 +* ---------------------------------------------------------------------------- +*/ #include @@ -33,10 +33,12 @@ static HttpdBuiltInUrl *builtInUrls; //Private data for http connection struct HttpdPriv { - char head[MAX_HEAD_LEN]; - int headPos; - char *sendBuff; - int sendBuffLen; + char head[MAX_HEAD_LEN]; // buffer to accumulate header + char from[24]; // source ip&port + char *sendBuff; // output buffer + short headPos; // offset into header + short sendBuffLen; // offset into output buffer + short code; // http response code (only for logging) }; //Connection pool @@ -50,36 +52,36 @@ static esp_tcp httpdTcp; //Struct to keep extension->mime data in typedef struct { - const char *ext; - const char *mimetype; + const char *ext; + const char *mimetype; } MimeMap; //The mappings from file extensions to mime types. If you need an extra mime type, //add it here. -static const MimeMap mimeTypes[]={ - {"htm", "text/htm"}, - {"html", "text/html; charset=UTF-8"}, - {"css", "text/css"}, - {"js", "text/javascript"}, - {"txt", "text/plain"}, - {"jpg", "image/jpeg"}, - {"jpeg", "image/jpeg"}, - {"png", "image/png"}, - {"tpl", "text/html; charset=UTF-8"}, - {NULL, "text/html"}, //default value +static const MimeMap mimeTypes[] = { + { "htm", "text/htm" }, + { "html", "text/html; charset=UTF-8" }, + { "css", "text/css" }, + { "js", "text/javascript" }, + { "txt", "text/plain" }, + { "jpg", "image/jpeg" }, + { "jpeg", "image/jpeg" }, + { "png", "image/png" }, + { "tpl", "text/html; charset=UTF-8" }, + { NULL, "text/html" }, //default value }; //Returns a static char* to a mime type for a given url to a file. const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { - int i=0; - //Go find the extension - char *ext=url+(strlen(url)-1); - while (ext!=url && *ext!='.') ext--; - if (*ext=='.') ext++; - - //ToDo: os_strcmp is case sensitive; we may want to do case-intensive matching here... - while (mimeTypes[i].ext!=NULL && os_strcmp(ext, mimeTypes[i].ext)!=0) i++; - return mimeTypes[i].mimetype; + int i = 0; + //Go find the extension + char *ext = url + (strlen(url) - 1); + while (ext != url && *ext != '.') ext--; + if (*ext == '.') ext++; + + //ToDo: os_strcmp is case sensitive; we may want to do case-intensive matching here... + while (mimeTypes[i].ext != NULL && os_strcmp(ext, mimeTypes[i].ext) != 0) i++; + return mimeTypes[i].mimetype; } // debug string to identify connection (ip address & port) @@ -87,59 +89,46 @@ const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { static char connStr[24]; static void debugConn(void *arg, char *what) { - struct espconn *espconn = arg; - esp_tcp *tcp = espconn->proto.tcp; - os_sprintf(connStr, "%d.%d.%d.%d:%d", - tcp->remote_ip[0], tcp->remote_ip[1], tcp->remote_ip[2], tcp->remote_ip[3], - tcp->remote_port); - //os_printf("%s %s\n", connStr, what); -} - -//Looks up the connData info for a specific esp connection -static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(void *arg) { - struct espconn *espconn = arg; - for (int i=0; iproto.tcp->remote_port && - os_memcmp(connData[i].remote_ip, espconn->proto.tcp->remote_ip, 4) == 0) - { #if 0 - os_printf("FindConn: 0x%p->0x%p", arg, &connData[i]); - if (arg == connData[i].conn) os_printf("\n"); - else os_printf(" *** was 0x%p\n", connData[i].conn); + struct espconn *espconn = arg; + esp_tcp *tcp = espconn->proto.tcp; + os_sprintf(connStr, "%d.%d.%d.%d:%d ", + tcp->remote_ip[0], tcp->remote_ip[1], tcp->remote_ip[2], tcp->remote_ip[3], + tcp->remote_port); + os_printf("%s %s\n", connStr, what); +#else + connStr[0] = 0; #endif - if (arg != connData[i].conn) connData[i].conn = arg; // yes, this happens!? - return &connData[i]; - } - } - //Shouldn't happen. - os_printf("%s *** Unknown connection 0x%p\n", connStr, arg); - return NULL; } -//Retires a connection for re-use +// Retires a connection for re-use static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) { - conn->conn = NULL; // don't try to send anything, the SDK crashes... - if (conn->cgi != NULL) conn->cgi(conn); // free cgi data - if (conn->post->buff != NULL) { - os_free(conn->post->buff); - } - conn->cgi=NULL; - conn->post->buff=NULL; - conn->remote_port = 0; - conn->remote_ip[0] = 0; - - uint32 dt = conn->startTime; - if (dt > 0) dt = (system_get_time() - dt)/1000; - os_printf("%s Closed, %ums, heap=%ld\n", connStr, dt, - (unsigned long)system_get_free_heap_size()); + if (conn->conn && conn->conn->reverse == conn) + conn->conn->reverse = NULL; // break reverse link + +#ifdef HTTPD_DBG + // log information about the request we handled + uint32 dt = conn->startTime; + if (dt > 0) dt = (system_get_time() - dt) / 1000; + if (conn->conn && conn->url) + os_printf("HTTP %s %s from %s -> %d in %ums, heap=%ld\n", + conn->requestType == HTTPD_METHOD_GET ? "GET" : "POST", conn->url, conn->priv->from, + conn->priv->code, dt, (unsigned long)system_get_free_heap_size()); +#endif + + conn->conn = NULL; // don't try to send anything, the SDK crashes... + if (conn->cgi != NULL) conn->cgi(conn); // free cgi data + if (conn->post->buff != NULL) os_free(conn->post->buff); + conn->cgi = NULL; + conn->post->buff = NULL; } //Stupid li'l helper function that returns the value of a hex char. static int httpdHexVal(char c) { - if (c>='0' && c<='9') return c-'0'; - if (c>='A' && c<='F') return c-'A'+10; - if (c>='a' && c<='f') return c-'a'+10; - return 0; + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + return 0; } //Decode a percent-encoded value. @@ -147,27 +136,31 @@ static int httpdHexVal(char c) { //are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also //zero-terminates the ret buffer. int httpdUrlDecode(char *val, int valLen, char *ret, int retLen) { - int s=0, d=0; - int esced=0, escVal=0; - while (s1) { - *ret++=*p++; - retLen--; - } - //Zero-terminate string - *ret=0; - //All done :) - return 1; - } - p+=strlen(p)+1; //Skip past end of string and \0 terminator - } - return 0; + char *p = conn->priv->head; + p = p + strlen(p) + 1; //skip GET/POST part + p = p + strlen(p) + 1; //skip HTTP part + while (p<(conn->priv->head + conn->priv->headPos)) { + while (*p <= 32 && *p != 0) p++; //skip crap at start + //See if this is the header + if (os_strncmp(p, header, strlen(header)) == 0 && p[strlen(header)] == ':') { + //Skip 'key:' bit of header line + p = p + strlen(header) + 1; + //Skip past spaces after the colon + while (*p == ' ') p++; + //Copy from p to end + while (*p != 0 && *p != '\r' && *p != '\n' && retLen>1) { + *ret++ = *p++; + retLen--; + } + //Zero-terminate string + *ret = 0; + //All done :) + return 1; + } + p += strlen(p) + 1; //Skip past end of string and \0 terminator + } + return 0; } //Start the response headers. void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code) { - char buff[128]; - int l; - char *status = code < 400 ? "OK" : "ERROR"; - l = os_sprintf(buff, "HTTP/1.0 %d %s\r\nServer: esp-link\r\nConnection: close\r\n", code, status); - httpdSend(conn, buff, l); + char buff[128]; + int l; + conn->priv->code = code; + char *status = code < 400 ? "OK" : "ERROR"; + l = os_sprintf(buff, "HTTP/1.0 %d %s\r\nServer: esp-link\r\nConnection: close\r\n", code, status); + httpdSend(conn, buff, l); } //Send a http header. void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val) { - char buff[256]; - int l; + char buff[256]; + int l; - l=os_sprintf(buff, "%s: %s\r\n", field, val); - httpdSend(conn, buff, l); + l = os_sprintf(buff, "%s: %s\r\n", field, val); + httpdSend(conn, buff, l); } //Finish the headers. void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn) { - httpdSend(conn, "\r\n", -1); + httpdSend(conn, "\r\n", -1); } //ToDo: sprintf->snprintf everywhere... esp doesn't have snprintf tho' :/ //Redirect to the given URL. void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl) { - char buff[1024]; - int l; - l=os_sprintf(buff, "HTTP/1.0 302 Found\r\nServer: esp8266-link\r\nConnection: close\r\nLocation: %s\r\n\r\nRedirecting to %s\r\n", newUrl, newUrl); - httpdSend(conn, buff, l); + char buff[1024]; + int l; + conn->priv->code = 302; + l = os_sprintf(buff, "HTTP/1.0 302 Found\r\nServer: esp8266-link\r\nConnection: close\r\n" + "Location: %s\r\n\r\nRedirecting to %s\r\n", newUrl, newUrl); + httpdSend(conn, buff, l); } //Use this as a cgi function to redirect one url to another. int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) { - if (connData->conn==NULL) { - //Connection aborted. Clean up. - return HTTPD_CGI_DONE; - } - httpdRedirect(connData, (char*)connData->cgiArg); - return HTTPD_CGI_DONE; + if (connData->conn == NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + httpdRedirect(connData, (char*)connData->cgiArg); + return HTTPD_CGI_DONE; } @@ -270,321 +266,354 @@ int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) { //the data is seen as a C-string. //Returns 1 for success, 0 for out-of-memory. int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len) { - if (len<0) len=strlen(data); - if (conn->priv->sendBuffLen+len>MAX_SENDBUFF_LEN) { - os_printf("%s ERROR! httpdSend full (%d of %d)\n", - connStr, conn->priv->sendBuffLen, MAX_SENDBUFF_LEN); - return 0; - } - os_memcpy(conn->priv->sendBuff+conn->priv->sendBuffLen, data, len); - conn->priv->sendBuffLen+=len; - return 1; + if (len<0) len = strlen(data); + if (conn->priv->sendBuffLen + len>MAX_SENDBUFF_LEN) { +#ifdef HTTPD_DBG + os_printf("%sERROR! httpdSend full (%d of %d)\n", + connStr, conn->priv->sendBuffLen, MAX_SENDBUFF_LEN); +#endif + return 0; + } + os_memcpy(conn->priv->sendBuff + conn->priv->sendBuffLen, data, len); + conn->priv->sendBuffLen += len; + return 1; } //Helper function to send any data in conn->priv->sendBuff static void ICACHE_FLASH_ATTR xmitSendBuff(HttpdConnData *conn) { - if (conn->priv->sendBuffLen!=0) { - sint8 status = espconn_sent(conn->conn, (uint8_t*)conn->priv->sendBuff, conn->priv->sendBuffLen); - if (status != 0) { - os_printf("%s ERROR! espconn_sent returned %d\n", connStr, status); - } - conn->priv->sendBuffLen=0; - } + if (conn->priv->sendBuffLen != 0) { + sint8 status = espconn_sent(conn->conn, (uint8_t*)conn->priv->sendBuff, conn->priv->sendBuffLen); + if (status != 0) { +#ifdef HTTPD_DBG + os_printf("%sERROR! espconn_sent returned %d\n", connStr, status); +#endif + } + conn->priv->sendBuffLen = 0; + } } //Callback called when the data on a socket has been successfully sent. static void ICACHE_FLASH_ATTR httpdSentCb(void *arg) { - debugConn(arg, "httpdSentCb"); - int r; - HttpdConnData *conn=httpdFindConnData(arg); - char sendBuff[MAX_SENDBUFF_LEN]; - - if (conn==NULL) return; - conn->priv->sendBuff=sendBuff; - conn->priv->sendBuffLen=0; - - if (conn->cgi==NULL) { //Marked for destruction? - //os_printf("Closing 0x%p/0x%p->0x%p\n", arg, conn->conn, conn); - espconn_disconnect(conn->conn); // we will get a disconnect callback - return; //No need to call xmitSendBuff. - } - - r=conn->cgi(conn); //Execute cgi fn. - if (r==HTTPD_CGI_DONE) { - conn->cgi=NULL; //mark for destruction. - } - if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) { - os_printf("%s ERROR! CGI fn returns code %d after sending data! Bad CGI!\n", connStr, r); - conn->cgi=NULL; //mark for destruction. - } - xmitSendBuff(conn); + debugConn(arg, "httpdSentCb"); + struct espconn* pCon = (struct espconn *)arg; + HttpdConnData *conn = (HttpdConnData *)pCon->reverse; + if (conn == NULL) return; // aborted connection + + char sendBuff[MAX_SENDBUFF_LEN]; + conn->priv->sendBuff = sendBuff; + conn->priv->sendBuffLen = 0; + + if (conn->cgi == NULL) { //Marked for destruction? + //os_printf("Closing 0x%p/0x%p->0x%p\n", arg, conn->conn, conn); + espconn_disconnect(conn->conn); // we will get a disconnect callback + return; //No need to call xmitSendBuff. + } + + int r = conn->cgi(conn); //Execute cgi fn. + if (r == HTTPD_CGI_DONE) { + conn->cgi = NULL; //mark for destruction. + } + if (r == HTTPD_CGI_NOTFOUND || r == HTTPD_CGI_AUTHENTICATED) { +#ifdef HTTPD_DBG + os_printf("%sERROR! Bad CGI code %d\n", connStr, r); +#endif + conn->cgi = NULL; //mark for destruction. + } + xmitSendBuff(conn); } -static const char *httpNotFoundHeader="HTTP/1.0 404 Not Found\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nNot Found.\r\n"; +static const char *httpNotFoundHeader = "HTTP/1.0 404 Not Found\r\nConnection: close\r\n" + "Content-Type: text/plain\r\nContent-Length: 12\r\n\r\nNot Found.\r\n"; //This is called when the headers have been received and the connection is ready to send //the result headers and data. //We need to find the CGI function to call, call it, and dependent on what it returns either //find the next cgi function, wait till the cgi data is sent or close up the connection. static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) { - int r; - int i=0; - if (conn->url==NULL) { - os_printf("%s WtF? url = NULL\n", connStr); - return; //Shouldn't happen - } - //See if we can find a CGI that's happy to handle the request. - while (1) { - //Look up URL in the built-in URL table. - while (builtInUrls[i].url!=NULL) { - int match=0; - //See if there's a literal match - if (os_strcmp(builtInUrls[i].url, conn->url)==0) match=1; - //See if there's a wildcard match - if (builtInUrls[i].url[os_strlen(builtInUrls[i].url)-1]=='*' && - os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url)-1)==0) match=1; - if (match) { - //os_printf("Is url index %d\n", i); - conn->cgiData=NULL; - conn->cgi=builtInUrls[i].cgiCb; - conn->cgiArg=builtInUrls[i].cgiArg; - break; - } - i++; - } - if (builtInUrls[i].url==NULL) { - //Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just - //generate a built-in 404 to handle this. - os_printf("%s %s not found. 404!\n", connStr, conn->url); - httpdSend(conn, httpNotFoundHeader, -1); - xmitSendBuff(conn); - conn->cgi=NULL; //mark for destruction - return; - } - - //Okay, we have a CGI function that matches the URL. See if it wants to handle the - //particular URL we're supposed to handle. - r=conn->cgi(conn); - if (r==HTTPD_CGI_MORE) { - //Yep, it's happy to do so and has more data to send. - xmitSendBuff(conn); - return; - } else if (r==HTTPD_CGI_DONE) { - //Yep, it's happy to do so and already is done sending data. - xmitSendBuff(conn); - conn->cgi=NULL; //mark conn for destruction - return; - } else if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) { - //URL doesn't want to handle the request: either the data isn't found or there's no - //need to generate a login screen. - i++; //look at next url the next iteration of the loop. - } - } + int r; + int i = 0; + if (conn->url == NULL) { +#ifdef HTTPD_DBG + os_printf("%sWtF? url = NULL\n", connStr); +#endif + return; //Shouldn't happen + } + //See if we can find a CGI that's happy to handle the request. + while (1) { + //Look up URL in the built-in URL table. + while (builtInUrls[i].url != NULL) { + int match = 0; + //See if there's a literal match + if (os_strcmp(builtInUrls[i].url, conn->url) == 0) match = 1; + //See if there's a wildcard match + if (builtInUrls[i].url[os_strlen(builtInUrls[i].url) - 1] == '*' && + os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url) - 1) == 0) match = 1; + if (match) { + //os_printf("Is url index %d\n", i); + conn->cgiData = NULL; + conn->cgi = builtInUrls[i].cgiCb; + conn->cgiArg = builtInUrls[i].cgiArg; + break; + } + i++; + } + if (builtInUrls[i].url == NULL) { + //Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just + //generate a built-in 404 to handle this. +#ifdef HTTPD_DBG + os_printf("%s%s not found. 404!\n", connStr, conn->url); +#endif + httpdSend(conn, httpNotFoundHeader, -1); + xmitSendBuff(conn); + conn->cgi = NULL; //mark for destruction + return; + } + + //Okay, we have a CGI function that matches the URL. See if it wants to handle the + //particular URL we're supposed to handle. + r = conn->cgi(conn); + if (r == HTTPD_CGI_MORE) { + //Yep, it's happy to do so and has more data to send. + xmitSendBuff(conn); + return; + } + else if (r == HTTPD_CGI_DONE) { + //Yep, it's happy to do so and already is done sending data. + xmitSendBuff(conn); + conn->cgi = NULL; //mark conn for destruction + return; + } + else if (r == HTTPD_CGI_NOTFOUND || r == HTTPD_CGI_AUTHENTICATED) { + //URL doesn't want to handle the request: either the data isn't found or there's no + //need to generate a login screen. + i++; //look at next url the next iteration of the loop. + } + } } //Parse a line of header data and modify the connection data accordingly. static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) { - int i; - char first_line = false; - - if (os_strncmp(h, "GET ", 4)==0) { - conn->requestType = HTTPD_METHOD_GET; - first_line = true; - } else if (os_strncmp(h, "POST ", 5)==0) { - conn->requestType = HTTPD_METHOD_POST; - first_line = true; - } - - if (first_line) { - char *e; - - //Skip past the space after POST/GET - i=0; - while (h[i]!=' ') i++; - conn->url=h+i+1; - - //Figure out end of url. - e=(char*)os_strstr(conn->url, " "); - if (e==NULL) return; //wtf? - *e=0; //terminate url part - - // Count number of open connections - int open = 0; - for (int j=0; jrequestType == HTTPD_METHOD_GET ? "GET" : "POST", conn->url, open); - //Parse out the URL part before the GET parameters. - conn->getArgs=(char*)os_strstr(conn->url, "?"); - if (conn->getArgs!=0) { - *conn->getArgs=0; - conn->getArgs++; - os_printf("%s GET args = %s\n", connStr, conn->getArgs); - } else { - conn->getArgs=NULL; - } - - } else if (os_strncmp(h, "Content-Length:", 15)==0) { - i=15; - //Skip trailing spaces - while (h[i]==' ') i++; - //Get POST data length - conn->post->len=atoi(h+i); - - // Allocate the buffer - if (conn->post->len > MAX_POST) { - // we'll stream this in in chunks - conn->post->buffSize = MAX_POST; - } else { - conn->post->buffSize = conn->post->len; - } - //os_printf("Mallocced buffer for %d + 1 bytes of post data.\n", conn->post->buffSize); - conn->post->buff=(char*)os_malloc(conn->post->buffSize + 1); - conn->post->buffLen=0; - } else if (os_strncmp(h, "Content-Type: ", 14)==0) { - if (os_strstr(h, "multipart/form-data")) { - // It's multipart form data so let's pull out the boundary for future use - char *b; - if ((b = os_strstr(h, "boundary=")) != NULL) { - conn->post->multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes - conn->post->multipartBoundary[0] = '-'; - conn->post->multipartBoundary[1] = '-'; - //os_printf("boundary = %s\n", conn->post->multipartBoundary); - } - } - } + int i; + char first_line = false; + + if (os_strncmp(h, "GET ", 4) == 0) { + conn->requestType = HTTPD_METHOD_GET; + first_line = true; + } + else if (os_strncmp(h, "POST ", 5) == 0) { + conn->requestType = HTTPD_METHOD_POST; + first_line = true; + } + + if (first_line) { + char *e; + + //Skip past the space after POST/GET + i = 0; + while (h[i] != ' ') i++; + conn->url = h + i + 1; + + //Figure out end of url. + e = (char*)os_strstr(conn->url, " "); + if (e == NULL) return; //wtf? + *e = 0; //terminate url part + + // Count number of open connections +#ifdef HTTPD_DBG + //esp_tcp *tcp = conn->conn->proto.tcp; + //os_printf("%sHTTP %s %s from %s\n", connStr, + // conn->requestType == HTTPD_METHOD_GET ? "GET" : "POST", conn->url, conn->priv->from); +#endif + //Parse out the URL part before the GET parameters. + conn->getArgs = (char*)os_strstr(conn->url, "?"); + if (conn->getArgs != 0) { + *conn->getArgs = 0; + conn->getArgs++; +#ifdef HTTPD_DBG + //os_printf("%sargs = %s\n", connStr, conn->getArgs); +#endif + } + else { + conn->getArgs = NULL; + } + + } + else if (os_strncmp(h, "Content-Length:", 15) == 0) { + i = 15; + //Skip trailing spaces + while (h[i] == ' ') i++; + //Get POST data length + conn->post->len = atoi(h + i); + + // Allocate the buffer + if (conn->post->len > MAX_POST) { + // we'll stream this in in chunks + conn->post->buffSize = MAX_POST; + } + else { + conn->post->buffSize = conn->post->len; + } + //os_printf("Mallocced buffer for %d + 1 bytes of post data.\n", conn->post->buffSize); + conn->post->buff = (char*)os_malloc(conn->post->buffSize + 1); + conn->post->buffLen = 0; + } + else if (os_strncmp(h, "Content-Type: ", 14) == 0) { + if (os_strstr(h, "multipart/form-data")) { + // It's multipart form data so let's pull out the boundary for future use + char *b; + if ((b = os_strstr(h, "boundary=")) != NULL) { + conn->post->multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes + conn->post->multipartBoundary[0] = '-'; + conn->post->multipartBoundary[1] = '-'; + //os_printf("boundary = %s\n", conn->post->multipartBoundary); + } + } + } } //Callback called when there's data available on a socket. static void ICACHE_FLASH_ATTR httpdRecvCb(void *arg, char *data, unsigned short len) { - debugConn(arg, "httpdRecvCb"); - int x; - char *p, *e; - char sendBuff[MAX_SENDBUFF_LEN]; - HttpdConnData *conn=httpdFindConnData(arg); - if (conn==NULL) return; - conn->priv->sendBuff=sendBuff; - conn->priv->sendBuffLen=0; - - //This is slightly evil/dirty: we abuse conn->post->len as a state variable for where in the http communications we are: - //<0 (-1): Post len unknown because we're still receiving headers - //==0: No post data - //>0: Need to receive post data - //ToDo: See if we can use something more elegant for this. - - for (x=0; xpost->len<0) { - //This byte is a header byte. - if (conn->priv->headPos!=MAX_HEAD_LEN) conn->priv->head[conn->priv->headPos++]=data[x]; - conn->priv->head[conn->priv->headPos]=0; - //Scan for /r/n/r/n. Receiving this indicate the headers end. - if (data[x]=='\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n")!=NULL) { - //Indicate we're done with the headers. - conn->post->len=0; - //Reset url data - conn->url=NULL; - //Iterate over all received headers and parse them. - p=conn->priv->head; - while(p<(&conn->priv->head[conn->priv->headPos-4])) { - e=(char *)os_strstr(p, "\r\n"); //Find end of header line - if (e==NULL) break; //Shouldn't happen. - e[0]=0; //Zero-terminate header - httpdParseHeader(p, conn); //and parse it. - p=e+2; //Skip /r/n (now /0/n) - } - //If we don't need to receive post data, we can send the response now. - if (conn->post->len==0) { - httpdProcessRequest(conn); - } - } - } else if (conn->post->len!=0) { - //This byte is a POST byte. - conn->post->buff[conn->post->buffLen++]=data[x]; - conn->post->received++; - if (conn->post->buffLen >= conn->post->buffSize || conn->post->received == conn->post->len) { - //Received a chunk of post data - conn->post->buff[conn->post->buffLen]=0; //zero-terminate, in case the cgi handler knows it can use strings - //Send the response. - httpdProcessRequest(conn); - conn->post->buffLen = 0; - } - } - } + debugConn(arg, "httpdRecvCb"); + struct espconn* pCon = (struct espconn *)arg; + HttpdConnData *conn = (HttpdConnData *)pCon->reverse; + if (conn == NULL) return; // aborted connection + + char sendBuff[MAX_SENDBUFF_LEN]; + conn->priv->sendBuff = sendBuff; + conn->priv->sendBuffLen = 0; + + //This is slightly evil/dirty: we abuse conn->post->len as a state variable for where in the http communications we are: + //<0 (-1): Post len unknown because we're still receiving headers + //==0: No post data + //>0: Need to receive post data + //ToDo: See if we can use something more elegant for this. + + for (int x = 0; xpost->len<0) { + //This byte is a header byte. + if (conn->priv->headPos != MAX_HEAD_LEN) conn->priv->head[conn->priv->headPos++] = data[x]; + conn->priv->head[conn->priv->headPos] = 0; + //Scan for /r/n/r/n. Receiving this indicate the headers end. + if (data[x] == '\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n") != NULL) { + //Indicate we're done with the headers. + conn->post->len = 0; + //Reset url data + conn->url = NULL; + //Iterate over all received headers and parse them. + char *p = conn->priv->head; + while (p<(&conn->priv->head[conn->priv->headPos - 4])) { + char *e = (char *)os_strstr(p, "\r\n"); //Find end of header line + if (e == NULL) break; //Shouldn't happen. + e[0] = 0; //Zero-terminate header + httpdParseHeader(p, conn); //and parse it. + p = e + 2; //Skip /r/n (now /0/n) + } + //If we don't need to receive post data, we can send the response now. + if (conn->post->len == 0) { + httpdProcessRequest(conn); + } + } + } + else if (conn->post->len != 0) { + //This byte is a POST byte. + conn->post->buff[conn->post->buffLen++] = data[x]; + conn->post->received++; + if (conn->post->buffLen >= conn->post->buffSize || conn->post->received == conn->post->len) { + //Received a chunk of post data + conn->post->buff[conn->post->buffLen] = 0; //zero-terminate, in case the cgi handler knows it can use strings + //Send the response. + httpdProcessRequest(conn); + conn->post->buffLen = 0; + } + } + } } static void ICACHE_FLASH_ATTR httpdDisconCb(void *arg) { - debugConn(arg, "httpdDisconCb"); - HttpdConnData *conn = httpdFindConnData(arg); - if (conn == NULL) return; - httpdRetireConn(conn); + debugConn(arg, "httpdDisconCb"); + struct espconn* pCon = (struct espconn *)arg; + HttpdConnData *conn = (HttpdConnData *)pCon->reverse; + if (conn == NULL) return; // aborted connection + httpdRetireConn(conn); } // Callback indicating a failure in the connection. "Recon" is probably intended in the sense -// of "you need to reconnect". Sigh... Note that there is no DiconCb after ReconCb +// of "you need to reconnect". Sigh... Note that there is no DisconCb after ReconCb static void ICACHE_FLASH_ATTR httpdReconCb(void *arg, sint8 err) { - debugConn(arg, "httpdReconCb"); - HttpdConnData *conn = httpdFindConnData(arg); - os_printf("%s ***** reset, err=%d\n", connStr, err); - if (conn == NULL) return; - httpdRetireConn(conn); + debugConn(arg, "httpdReconCb"); + struct espconn* pCon = (struct espconn *)arg; + HttpdConnData *conn = (HttpdConnData *)pCon->reverse; + if (conn == NULL) return; // aborted connection + +#ifdef HTTPD_DBG + os_printf("%s***** reset, err=%d\n", connStr, err); +#endif + httpdRetireConn(conn); } static void ICACHE_FLASH_ATTR httpdConnectCb(void *arg) { - debugConn(arg, "httpdConnectCb"); - struct espconn *conn=arg; - int i; - //Find empty conndata in pool - for (i=0; iproto.tcp->remote_port; - os_memcpy(connData[i].remote_ip, conn->proto.tcp->remote_ip, 4); - connData[i].priv->headPos=0; - connData[i].post=&connPostData[i]; - connData[i].post->buff=NULL; - connData[i].post->buffLen=0; - connData[i].post->received=0; - connData[i].post->len=-1; - connData[i].startTime = system_get_time(); - - espconn_regist_recvcb(conn, httpdRecvCb); - espconn_regist_reconcb(conn, httpdReconCb); - espconn_regist_disconcb(conn, httpdDisconCb); - espconn_regist_sentcb(conn, httpdSentCb); - - espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY); + connData[i].priv = &connPrivData[i]; + connData[i].conn = conn; + conn->reverse = connData+i; + connData[i].priv->headPos = 0; + + esp_tcp *tcp = conn->proto.tcp; + os_sprintf(connData[i].priv->from, "%d.%d.%d.%d:%d", tcp->remote_ip[0], tcp->remote_ip[1], + tcp->remote_ip[2], tcp->remote_ip[3], tcp->remote_port); + connData[i].post = &connPostData[i]; + connData[i].post->buff = NULL; + connData[i].post->buffLen = 0; + connData[i].post->received = 0; + connData[i].post->len = -1; + connData[i].startTime = system_get_time(); + + espconn_regist_recvcb(conn, httpdRecvCb); + espconn_regist_reconcb(conn, httpdReconCb); + espconn_regist_disconcb(conn, httpdDisconCb); + espconn_regist_sentcb(conn, httpdSentCb); + + espconn_set_opt(conn, ESPCONN_REUSEADDR | ESPCONN_NODELAY); } //Httpd initialization routine. Call this to kick off webserver functionality. void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) { - int i; - - for (i=0; i #include "httpdespfs.h" -#include "espfs.h" -#include "espfsformat.h" -#include "cgi.h" // The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression. // If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.) @@ -27,7 +22,8 @@ static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nSe //This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding //path in the filesystem and if it exists, passes the file through. This simulates what a normal //webserver would do with static files. -int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { +int ICACHE_FLASH_ATTR +cgiEspFsHook(HttpdConnData *connData) { EspFsFile *file=connData->cgiData; int len; char buff[1024]; @@ -93,7 +89,8 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { #if 0 //cgiEspFsHtml is a simple HTML file that gets prefixed by head.tpl -int ICACHE_FLASH_ATTR cgiEspFsHtml(HttpdConnData *connData) { +int ICACHE_FLASH_ATTR +cgiEspFsHtml(HttpdConnData *connData) { EspFsFile *file = connData->cgiData; char buff[2048]; diff --git a/httpd/httpdespfs.h b/httpd/httpdespfs.h index fb07008..847a8b6 100644 --- a/httpd/httpdespfs.h +++ b/httpd/httpdespfs.h @@ -1,10 +1,14 @@ #ifndef HTTPDESPFS_H #define HTTPDESPFS_H +#include +#include "espfs.h" +#include "espfsformat.h" +#include "cgi.h" #include "httpd.h" int cgiEspFsHook(HttpdConnData *connData); -int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData); +//int cgiEspFsTemplate(HttpdConnData *connData); //int ICACHE_FLASH_ATTR cgiEspFsHtml(HttpdConnData *connData); #endif diff --git a/include/esp8266.h b/include/esp8266.h index ce92455..3e7eeb9 100644 --- a/include/esp8266.h +++ b/include/esp8266.h @@ -1,5 +1,8 @@ // Combined include file for esp8266 +#ifndef _ESP8266_H_ +#define _ESP8266_H_ +#include #include #include #include @@ -13,7 +16,12 @@ #include #include #include -#include #include "espmissingincludes.h" #include "uart_hw.h" + +#ifdef __WIN32__ +#include <_mingw.h> +#endif + +#endif // _ESP8266_H_ \ No newline at end of file diff --git a/include/espmissingincludes.h b/include/espmissingincludes.h index c69b9fc..f421b6e 100644 --- a/include/espmissingincludes.h +++ b/include/espmissingincludes.h @@ -1,9 +1,8 @@ #ifndef ESPMISSINGINCLUDES_H #define ESPMISSINGINCLUDES_H -#include -#include -#include +#include +#include //Missing function prototypes in include folders. Gcc will warn on these if we don't define 'em anywhere. //MOST OF THESE ARE GUESSED! but they seem to work and shut up the compiler. @@ -37,19 +36,32 @@ void ets_timer_setfn(ETSTimer *t, ETSTimerFunc *fn, void *parg); void ets_update_cpu_frequency(int freqmhz); -int os_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); -int os_snprintf(char *str, size_t size, const char *format, ...) __attribute__ ((format (printf, 3, 4))); -int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2))); +#ifdef SDK_DBG +#define DEBUG_SDK true +#else +#define DEBUG_SDK false +#endif + +int os_snprintf(char *str, size_t size, const char *format, ...) __attribute__((format(printf, 3, 4))); +int os_printf_plus(const char *format, ...) __attribute__((format(printf, 1, 2))); + +#undef os_printf +#define os_printf(format, ...) \ + system_set_os_print(true); \ + os_printf_plus(format, ## __VA_ARGS__); \ + system_set_os_print(DEBUG_SDK); // int os_printf(const char *format, ...) + +// memory allocation functions are "different" due to memory debugging functionality +// added in SDK 1.4.0 +void vPortFree(void *ptr, char * file, int line); +void *pvPortMalloc(size_t xWantedSize, char * file, int line); +void *pvPortZalloc(size_t, char * file, int line); +void *vPortMalloc(size_t xWantedSize); void pvPortFree(void *ptr); -void *pvPortMalloc(size_t xWantedSize); -void *pvPortZalloc(size_t); + void uart_div_modify(int no, unsigned int freq); -void vPortFree(void *ptr); -void *vPortMalloc(size_t xWantedSize); uint32 system_get_time(); -//uint8 wifi_get_opmode(void); // defined in SDK 1.0.0 onwards -//int os_random(); // defined in SDK 1.1.0 onwards int rand(void); void ets_bzero(void *s, size_t n); void ets_delay_us(int ms); @@ -67,4 +79,12 @@ void ets_delay_us(int ms); |( (((FUNC&BIT2)<<2)|(FUNC&0x3))< +#ifdef __WIN32__ +#include <_mingw.h> +#endif +#undef SHOW_HEAP_USE +#define DEBUGIP +#define SDK_DBG + +#define CMD_DBG +#undef ESPFS_DBG +#undef CGI_DBG +#define CGIFLASH_DBG +#define CGIMQTT_DBG +#define CGIPINS_DBG +#define CGIWIFI_DBG +#define CONFIG_DBG +#define LOG_DBG +#define STATUS_DBG +#define HTTPD_DBG +#define MQTT_DBG +#define MQTTCMD_DBG +#undef PKTBUF_DBG +#define REST_DBG +#define RESTCMD_DBG +#define SERBR_DBG +#define SERLED_DBG +#undef SLIP_DBG +#define UART_DBG + +// If defined, the default hostname for DHCP will include the chip ID to make it unique +#undef CHIP_IN_HOSTNAME + +extern char* esp_link_version; +extern uint8_t UTILS_StrToIP(const char* str, void *ip); + +#endif diff --git a/mqtt/mqtt.c b/mqtt/mqtt.c new file mode 100644 index 0000000..d3d174e --- /dev/null +++ b/mqtt/mqtt.c @@ -0,0 +1,817 @@ +/* mqtt.c +* Protocol: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html +* +* Copyright (c) 2014-2015, Tuan PM +* All rights reserved. +* +* Modified by Thorsten von Eicken to make it fully callback based +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of Redis nor the names of its contributors may be used +* to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +// TODO: +// Handle SessionPresent=0 in CONNACK and rexmit subscriptions +// Improve timeout for CONNACK, currently only has keep-alive timeout (maybe send artificial ping?) +// Allow messages that don't require ACK to be sent even when pending_buffer is != NULL +// Set dup flag in retransmissions + +#include +#include "pktbuf.h" +#include "mqtt.h" + +#ifdef MQTT_DBG +#define DBG_MQTT(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG_MQTT(format, ...) do { } while(0) +#endif + +extern void dumpMem(void *buf, int len); + +// HACK +sint8 espconn_secure_connect(struct espconn *espconn) { + return espconn_connect(espconn); +} +sint8 espconn_secure_disconnect(struct espconn *espconn) { + return espconn_disconnect(espconn); +} +sint8 espconn_secure_sent(struct espconn *espconn, uint8 *psent, uint16 length) { + return espconn_sent(espconn, psent, length); +} + +// max message size supported for receive +#define MQTT_MAX_RCV_MESSAGE 2048 +// max message size for sending (except publish) +#define MQTT_MAX_SHORT_MESSAGE 128 + +#ifdef MQTT_DBG +static char* mqtt_msg_type[] = { + "NULL", "TYPE_CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC", "PUBREL", "PUBCOMP", + "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT", "RESV", +}; +#endif + +// forward declarations +static void mqtt_enq_message(MQTT_Client *client, const uint8_t *data, uint16_t len); +static void mqtt_send_message(MQTT_Client* client); +static void mqtt_doAbort(MQTT_Client* client); + +// Deliver a publish message to the client +static void ICACHE_FLASH_ATTR +deliver_publish(MQTT_Client* client, uint8_t* message, uint16_t length) { + + // parse the message into topic and data + uint16_t topic_length = length; + const char *topic = mqtt_get_publish_topic(message, &topic_length); + uint16_t data_length = length; + const char *data = mqtt_get_publish_data(message, &data_length); + + // callback to client + if (client->dataCb) + client->dataCb((uint32_t*)client, topic, topic_length, data, data_length); + if (client->cmdDataCb) + client->cmdDataCb((uint32_t*)client, topic, topic_length, data, data_length); +} + +/** +* @brief Client received callback function. +* @param arg: contain the ip link information +* @param pdata: received data +* @param len: the length of received data +* @retval None +*/ +static void ICACHE_FLASH_ATTR +mqtt_tcpclient_recv(void* arg, char* pdata, unsigned short len) { + //os_printf("MQTT: recv CB\n"); + uint8_t msg_type; + uint16_t msg_id; + uint16_t msg_len; + + struct espconn* pCon = (struct espconn*)arg; + MQTT_Client* client = (MQTT_Client *)pCon->reverse; + if (client == NULL) return; // aborted connection + + //os_printf("MQTT: Data received %d bytes\n", len); + + do { + // append data to our buffer + int avail = client->in_buffer_size - client->in_buffer_filled; + if (len <= avail) { + os_memcpy(client->in_buffer + client->in_buffer_filled, pdata, len); + client->in_buffer_filled += len; + len = 0; + } else { + os_memcpy(client->in_buffer + client->in_buffer_filled, pdata, avail); + client->in_buffer_filled += avail; + len -= avail; + pdata += avail; + } + + // check out what's at the head of the buffer + msg_type = mqtt_get_type(client->in_buffer); + msg_id = mqtt_get_id(client->in_buffer, client->in_buffer_size); + msg_len = mqtt_get_total_length(client->in_buffer, client->in_buffer_size); + + if (msg_len > client->in_buffer_size) { + // oops, too long a message for us to digest, disconnect and hope for a miracle + os_printf("MQTT: Too long a message (%d bytes)\n", msg_len); + mqtt_doAbort(client); + return; + } + + // check whether what's left in the buffer is a complete message + if (msg_len > client->in_buffer_filled) break; + + if (client->connState != MQTT_CONNECTED) { + // why are we receiving something?? + DBG_MQTT("MQTT ERROR: recv in invalid state %d\n", client->connState); + mqtt_doAbort(client); + return; + } + + // we are connected and are sending/receiving data messages + uint8_t pending_msg_type = 0; + uint16_t pending_msg_id = 0; + if (client->pending_buffer != NULL) { + pending_msg_type = mqtt_get_type(client->pending_buffer->data); + pending_msg_id = mqtt_get_id(client->pending_buffer->data, client->pending_buffer->filled); + } + DBG_MQTT("MQTT: Recv type=%s id=%04X len=%d; Pend type=%s id=%02X\n", + mqtt_msg_type[msg_type], msg_id, msg_len, mqtt_msg_type[pending_msg_type],pending_msg_id); + + switch (msg_type) { + case MQTT_MSG_TYPE_CONNACK: + //DBG_MQTT("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); + client->reconTimeout = 1; // reset the reconnect backoff + break; + + case MQTT_MSG_TYPE_SUBACK: + if (pending_msg_type == MQTT_MSG_TYPE_SUBSCRIBE && pending_msg_id == msg_id) { + //DBG_MQTT("MQTT: Subscribe successful\n"); + client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); + } + break; + + case MQTT_MSG_TYPE_UNSUBACK: + if (pending_msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE && pending_msg_id == msg_id) { + //DBG_MQTT("MQTT: Unsubscribe successful\n"); + client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); + } + break; + + case MQTT_MSG_TYPE_PUBACK: // ack for a publish we sent + if (pending_msg_type == MQTT_MSG_TYPE_PUBLISH && pending_msg_id == msg_id) { + //DBG_MQTT("MQTT: QoS1 Publish successful\n"); + client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); + } + break; + + case MQTT_MSG_TYPE_PUBREC: // rec for a publish we sent + if (pending_msg_type == MQTT_MSG_TYPE_PUBLISH && pending_msg_id == msg_id) { + //DBG_MQTT("MQTT: QoS2 publish cont\n"); + client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); + // we need to send PUBREL + mqtt_msg_pubrel(&client->mqtt_connection, msg_id); + mqtt_enq_message(client, client->mqtt_connection.message.data, + client->mqtt_connection.message.length); + } + break; + + case MQTT_MSG_TYPE_PUBCOMP: // comp for a pubrel we sent (originally publish we sent) + if (pending_msg_type == MQTT_MSG_TYPE_PUBREL && pending_msg_id == msg_id) { + //DBG_MQTT("MQTT: QoS2 Publish successful\n"); + client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); + } + break; + + case MQTT_MSG_TYPE_PUBLISH: { // incoming publish + // we may need to ACK the publish + uint8_t msg_qos = mqtt_get_qos(client->in_buffer); +#ifdef MQTT_DBG + uint16_t topic_length = msg_len; + os_printf("MQTT: Recv PUBLISH qos=%d %s\n", msg_qos, + mqtt_get_publish_topic(client->in_buffer, &topic_length)); +#endif + if (msg_qos == 1) mqtt_msg_puback(&client->mqtt_connection, msg_id); + if (msg_qos == 2) mqtt_msg_pubrec(&client->mqtt_connection, msg_id); + if (msg_qos == 1 || msg_qos == 2) { + mqtt_enq_message(client, client->mqtt_connection.message.data, + client->mqtt_connection.message.length); + } + // send the publish message to clients + deliver_publish(client, client->in_buffer, msg_len); + } + break; + + case MQTT_MSG_TYPE_PUBREL: // rel for a rec we sent (originally publish received) + if (pending_msg_type == MQTT_MSG_TYPE_PUBREC && pending_msg_id == msg_id) { + //DBG_MQTT("MQTT: Cont QoS2 recv\n"); + client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); + // we need to send PUBCOMP + mqtt_msg_pubcomp(&client->mqtt_connection, msg_id); + mqtt_enq_message(client, client->mqtt_connection.message.data, + client->mqtt_connection.message.length); + } + break; + + case MQTT_MSG_TYPE_PINGRESP: + client->keepAliveAckTick = 0; + break; + } + + // Shift out the message and see whether we have another one + if (msg_len < client->in_buffer_filled) + os_memcpy(client->in_buffer, client->in_buffer+msg_len, client->in_buffer_filled-msg_len); + client->in_buffer_filled -= msg_len; + } while(client->in_buffer_filled > 0 || len > 0); + + // Send next packet out, if possible + if (!client->sending && client->pending_buffer == NULL && client->msgQueue != NULL) { + mqtt_send_message(client); + } +} + +/** +* @brief Callback from TCP that previous send completed +* @param arg: contain the ip link information +* @retval None +*/ +static void ICACHE_FLASH_ATTR +mqtt_tcpclient_sent_cb(void* arg) { + //DBG_MQTT("MQTT: sent CB\n"); + struct espconn* pCon = (struct espconn *)arg; + MQTT_Client* client = (MQTT_Client *)pCon->reverse; + if (client == NULL) return; // aborted connection ? + //DBG_MQTT("MQTT: Sent\n"); + + // if the message we sent is not a "pending" one, we need to free the buffer + if (client->sending_buffer != NULL) { + PktBuf *buf = client->sending_buffer; + //DBG_MQTT("PktBuf free %p l=%d\n", buf, buf->filled); + os_free(buf); + client->sending_buffer = NULL; + } + client->sending = false; + + // send next message if one is queued and we're not expecting an ACK + if (client->connState == MQTT_CONNECTED && client->pending_buffer == NULL && + client->msgQueue != NULL) { + mqtt_send_message(client); + } +} + +/* + * @brief: Timer function to handle timeouts + */ +static void ICACHE_FLASH_ATTR +mqtt_timer(void* arg) { + MQTT_Client* client = (MQTT_Client*)arg; + //DBG_MQTT("MQTT: timer CB\n"); + + switch (client->connState) { + default: break; + + case MQTT_CONNECTED: + // first check whether we're timing out for an ACK + if (client->pending_buffer != NULL && --client->timeoutTick == 0) { + // looks like we're not getting a response in time, abort the connection + mqtt_doAbort(client); + client->timeoutTick = 0; // trick to make reconnect happen in 1 second + return; + } + + // check whether our last keep-alive timed out + if (client->keepAliveAckTick > 0 && --client->keepAliveAckTick == 0) { + os_printf("\nMQTT ERROR: Keep-alive timed out\n"); + mqtt_doAbort(client); + return; + } + + // check whether we need to send a keep-alive message + if (client->keepAliveTick > 0 && --client->keepAliveTick == 0) { + // timeout: we need to send a ping message + //DBG_MQTT("MQTT: Send keepalive\n"); + mqtt_msg_pingreq(&client->mqtt_connection); + PktBuf *buf = PktBuf_New(client->mqtt_connection.message.length); + os_memcpy(buf->data, client->mqtt_connection.message.data, + client->mqtt_connection.message.length); + buf->filled = client->mqtt_connection.message.length; + client->msgQueue = PktBuf_Unshift(client->msgQueue, buf); + mqtt_send_message(client); + client->keepAliveTick = client->connect_info.keepalive; + client->keepAliveAckTick = client->sendTimeout; + } + + break; + + case TCP_RECONNECT_REQ: + if (client->timeoutTick == 0 || --client->timeoutTick == 0) { + // it's time to reconnect! start by re-enqueueing anything pending + if (client->pending_buffer != NULL) { + client->msgQueue = PktBuf_Unshift(client->msgQueue, client->pending_buffer); + client->pending_buffer = NULL; + } + client->connect_info.clean_session = 0; // ask server to keep state + MQTT_Connect(client); + } + } +} + +/** + * @brief Callback from SDK that socket is disconnected + * @param arg: contain the ip link information + * @retval None + */ +void ICACHE_FLASH_ATTR +mqtt_tcpclient_discon_cb(void* arg) { + struct espconn* pespconn = (struct espconn *)arg; + MQTT_Client* client = (MQTT_Client *)pespconn->reverse; + DBG_MQTT("MQTT: Disconnect CB, freeing espconn %p\n", arg); + if (pespconn->proto.tcp) os_free(pespconn->proto.tcp); + os_free(pespconn); + + // if this is an aborted connection we're done + if (client == NULL) return; + DBG_MQTT("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); + + // reconnect unless we're in a permanently disconnected state + if (client->connState == MQTT_DISCONNECTED) return; + client->timeoutTick = client->reconTimeout; + if (client->reconTimeout < 128) client->reconTimeout <<= 1; + client->connState = TCP_RECONNECT_REQ; +} + +/** +* @brief Callback from SDK that socket got reset, note that no discon_cb will follow +* @param arg: contain the ip link information +* @retval None +*/ +static void ICACHE_FLASH_ATTR +mqtt_tcpclient_recon_cb(void* arg, int8_t err) { + struct espconn* pespconn = (struct espconn *)arg; + MQTT_Client* client = (MQTT_Client *)pespconn->reverse; + //DBG_MQTT("MQTT: Reset CB, freeing espconn %p (err=%d)\n", arg, 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); + + // reconnect unless we're in a permanently disconnected state + if (client->connState == MQTT_DISCONNECTED) return; + client->timeoutTick = client->reconTimeout; + if (client->reconTimeout < 128) client->reconTimeout <<= 1; + client->connState = TCP_RECONNECT_REQ; + os_printf("timeoutTick=%d reconTimeout=%d\n", client->timeoutTick, client->reconTimeout); +} + + +/** +* @brief Callback from SDK that socket is connected +* @param arg: contain the ip link information +* @retval None +*/ +static void ICACHE_FLASH_ATTR +mqtt_tcpclient_connect_cb(void* arg) { + struct espconn* pCon = (struct espconn *)arg; + MQTT_Client* client = (MQTT_Client *)pCon->reverse; + if (client == NULL) return; // aborted connection + + espconn_regist_disconcb(client->pCon, mqtt_tcpclient_discon_cb); + espconn_regist_recvcb(client->pCon, mqtt_tcpclient_recv); + espconn_regist_sentcb(client->pCon, mqtt_tcpclient_sent_cb); + os_printf("MQTT: TCP connected to %s:%d\n", client->host, client->port); + + // send MQTT connect message to broker + mqtt_msg_connect(&client->mqtt_connection, &client->connect_info); + PktBuf *buf = PktBuf_New(client->mqtt_connection.message.length); + os_memcpy(buf->data, client->mqtt_connection.message.data, + client->mqtt_connection.message.length); + buf->filled = client->mqtt_connection.message.length; + client->msgQueue = PktBuf_Unshift(client->msgQueue, buf); // prepend to send (rexmit) queue + mqtt_send_message(client); + client->connState = MQTT_CONNECTED; // v3.1.1 allows publishing while still connecting +} + +/** + * @brief Allocate and enqueue mqtt message, kick sending, if appropriate + */ +static void ICACHE_FLASH_ATTR +mqtt_enq_message(MQTT_Client *client, const uint8_t *data, uint16_t len) { + PktBuf *buf = PktBuf_New(len); + os_memcpy(buf->data, data, len); + buf->filled = len; + client->msgQueue = PktBuf_Push(client->msgQueue, buf); + + if (client->connState == MQTT_CONNECTED && !client->sending && client->pending_buffer == NULL) { + mqtt_send_message(client); + } +} + +/** + * @brief Send out top message in queue onto socket + */ +static void ICACHE_FLASH_ATTR +mqtt_send_message(MQTT_Client* client) { + //DBG_MQTT("MQTT: Send_message\n"); + PktBuf *buf = client->msgQueue; + if (buf == NULL || client->sending) return; // ahem... + client->msgQueue = PktBuf_Shift(client->msgQueue); + + // get some details about the message + uint16_t msg_type = mqtt_get_type(buf->data); + uint8_t msg_id = mqtt_get_id(buf->data, buf->filled); + msg_id = msg_id; +#ifdef MQTT_DBG + os_printf("MQTT: Send type=%s id=%04X len=%d\n", mqtt_msg_type[msg_type], msg_id, buf->filled); +#if 0 + for (int i=0; ifilled; i++) { + if (buf->data[i] >= ' ' && buf->data[i] <= '~') os_printf("%c", buf->data[i]); + else os_printf("\\x%02X", buf->data[i]); + } + os_printf("\n"); +#endif +#endif + + // send the message out + if (client->security) + espconn_secure_sent(client->pCon, buf->data, buf->filled); + else + espconn_sent(client->pCon, buf->data, buf->filled); + client->sending = true; + + // depending on whether it needs an ack we need to hold on to the message + bool needsAck = + (msg_type == MQTT_MSG_TYPE_PUBLISH && mqtt_get_qos(buf->data) > 0) || + msg_type == MQTT_MSG_TYPE_PUBREL || msg_type == MQTT_MSG_TYPE_PUBREC || + msg_type == MQTT_MSG_TYPE_SUBSCRIBE || msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE || + msg_type == MQTT_MSG_TYPE_PINGREQ; + if (msg_type == MQTT_MSG_TYPE_PINGREQ) { + client->pending_buffer = NULL; // we don't need to rexmit this one + client->sending_buffer = buf; + } else if (needsAck) { + client->pending_buffer = buf; // remeber for rexmit on disconnect/reconnect + client->sending_buffer = NULL; + client->timeoutTick = client->sendTimeout+1; // +1 to ensure full sendTireout seconds + } else { + client->pending_buffer = NULL; + client->sending_buffer = buf; + client->timeoutTick = 0; + } + client->keepAliveTick = client->connect_info.keepalive > 0 ? client->connect_info.keepalive+1 : 0; +} + +/** +* @brief DNS lookup for broker hostname completed, move to next phase +*/ +static void ICACHE_FLASH_ATTR +mqtt_dns_found(const char* name, ip_addr_t* ipaddr, void* arg) { + struct espconn* pConn = (struct espconn *)arg; + MQTT_Client* client = (MQTT_Client *)pConn->reverse; + + if (ipaddr == NULL) { + os_printf("MQTT: DNS lookup failed\n"); + client->timeoutTick = client->reconTimeout; + if (client->reconTimeout < 128) client->reconTimeout <<= 1; + client->connState = TCP_RECONNECT_REQ; // the timer will kick-off a reconnection + return; + } + DBG_MQTT("MQTT: ip %d.%d.%d.%d\n", + *((uint8 *)&ipaddr->addr), + *((uint8 *)&ipaddr->addr + 1), + *((uint8 *)&ipaddr->addr + 2), + *((uint8 *)&ipaddr->addr + 3)); + + if (client->ip.addr == 0 && ipaddr->addr != 0) { + os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); + uint8_t err; + if (client->security) + err = espconn_secure_connect(client->pCon); + else + err = espconn_connect(client->pCon); + if (err != 0) { + os_printf("MQTT ERROR: Failed to connect\n"); + client->timeoutTick = client->reconTimeout; + if (client->reconTimeout < 128) client->reconTimeout <<= 1; + client->connState = TCP_RECONNECT_REQ; + } else { + DBG_MQTT("MQTT: connecting...\n"); + } + } +} + +//===== publish / subscribe + +static void ICACHE_FLASH_ATTR +msg_conn_init(mqtt_connection_t *new_msg, mqtt_connection_t *old_msg, + uint8_t *buf, uint16_t buflen) { + new_msg->message_id = old_msg->message_id; + new_msg->buffer = buf; + new_msg->buffer_length = buflen; +} + +/** +* @brief MQTT publish function. +* @param client: MQTT_Client reference +* @param topic: string topic will publish to +* @param data: buffer data send point to +* @param data_length: length of data +* @param qos: qos +* @param retain: retain +* @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) { + // 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); + if (buf == NULL) { + os_printf("MQTT ERROR: Cannot allocate buffer for %d byte publish\n", buf_len); + return FALSE; + } + // use a temporary mqtt_message_t pointing to our buffer, this is a bit of a mess because we + // need to keep track of the message_id that is embedded in it + mqtt_connection_t msg; + msg_conn_init(&msg, &client->mqtt_connection, buf->data, buf_len); + uint16_t msg_id; + if (!mqtt_msg_publish(&msg, topic, data, data_length, qos, retain, &msg_id)){ + os_printf("MQTT ERROR: Queuing Publish failed\n"); + os_free(buf); + return FALSE; + } + client->mqtt_connection.message_id = msg.message_id; + if (msg.message.data != buf->data) + os_memcpy(buf->data, msg.message.data, msg.message.length); + buf->filled = msg.message.length; + + DBG_MQTT("MQTT: Publish, topic: \"%s\", length: %d\n", topic, msg.message.length); + //dumpMem(buf, buf_len); + client->msgQueue = PktBuf_Push(client->msgQueue, buf); + + if (!client->sending && client->pending_buffer == NULL) { + mqtt_send_message(client); + } + return TRUE; +} + +/** +* @brief MQTT subscribe function. +* @param client: MQTT_Client reference +* @param topic: string topic will subscribe +* @param qos: qos +* @retval TRUE if success queue +*/ +bool ICACHE_FLASH_ATTR +MQTT_Subscribe(MQTT_Client* client, char* topic, uint8_t qos) { + uint16_t msg_id; + if (!mqtt_msg_subscribe(&client->mqtt_connection, topic, 0, &msg_id)) { + os_printf("MQTT ERROR: Queuing Subscribe failed (too long)\n"); + return FALSE; + } + DBG_MQTT("MQTT: Subscribe, topic: \"%s\"\n", topic); + mqtt_enq_message(client, client->mqtt_connection.message.data, + client->mqtt_connection.message.length); + return TRUE; +} + +//===== Initialization and connect/disconnect + +/** +* @brief MQTT initialization mqtt client function +* @param client: MQTT_Client reference +* @param host: Domain or IP string +* @param port: Port to connect +* @param security: 1 for ssl, 0 for none +* @param clientid: MQTT client id +* @param client_user: MQTT client user +* @param client_pass: MQTT client password +* @param keepAliveTime: MQTT keep alive timer, in second +* @param cleanSession: On connection, a client sets the "clean session" flag, which is sometimes also known as the "clean start" flag. +* If clean session is set to false, then the connection is treated as durable. This means that when the client +* disconnects, any subscriptions it has will remain and any subsequent QoS 1 or 2 messages will be stored until +* it connects again in the future. If clean session is true, then all subscriptions will be removed for the client +* when it disconnects. +* @retval None +*/ +void ICACHE_FLASH_ATTR +MQTT_Init(MQTT_Client* client, char* host, uint32 port, uint8_t security, uint8_t sendTimeout, + char* client_id, char* client_user, char* client_pass, + uint8_t keepAliveTime) { + DBG_MQTT("MQTT_Init\n"); + + os_memset(client, 0, sizeof(MQTT_Client)); + + client->host = (char*)os_zalloc(os_strlen(host) + 1); + os_strcpy(client->host, host); + + client->port = port; + client->security = !!security; + + // timeouts with sanity checks + client->sendTimeout = sendTimeout == 0 ? 1 : sendTimeout; + client->reconTimeout = 1; // reset reconnect back-off + + os_memset(&client->connect_info, 0, sizeof(mqtt_connect_info_t)); + + client->connect_info.client_id = (char*)os_zalloc(os_strlen(client_id) + 1); + os_strcpy(client->connect_info.client_id, client_id); + + client->connect_info.username = (char*)os_zalloc(os_strlen(client_user) + 1); + os_strcpy(client->connect_info.username, client_user); + + client->connect_info.password = (char*)os_zalloc(os_strlen(client_pass) + 1); + os_strcpy(client->connect_info.password, client_pass); + + client->connect_info.keepalive = keepAliveTime; + client->connect_info.clean_session = 1; + + client->in_buffer = (uint8_t *)os_zalloc(MQTT_MAX_RCV_MESSAGE); + client->in_buffer_size = MQTT_MAX_RCV_MESSAGE; + + uint8_t *out_buffer = (uint8_t *)os_zalloc(MQTT_MAX_SHORT_MESSAGE); + mqtt_msg_init(&client->mqtt_connection, out_buffer, MQTT_MAX_SHORT_MESSAGE); +} + +/** + * @brief MQTT Set Last Will Topic, must be called before MQTT_Connect + */ +void ICACHE_FLASH_ATTR +MQTT_InitLWT(MQTT_Client* client, char* will_topic, char* will_msg, + uint8_t will_qos, uint8_t will_retain) { + + client->connect_info.will_topic = (char*)os_zalloc(os_strlen(will_topic) + 1); + os_strcpy((char*)client->connect_info.will_topic, will_topic); + + client->connect_info.will_message = (char*)os_zalloc(os_strlen(will_msg) + 1); + os_strcpy((char*)client->connect_info.will_message, will_msg); + + client->connect_info.will_qos = will_qos; + client->connect_info.will_retain = will_retain; + + // TODO: if we're connected we should disconnect and reconnect to establish the new LWT +} + +/** +* @brief Begin connect to MQTT broker +* @param client: MQTT_Client reference +* @retval None +*/ +void ICACHE_FLASH_ATTR +MQTT_Connect(MQTT_Client* client) { + //MQTT_Disconnect(client); + client->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + client->pCon->type = ESPCONN_TCP; + client->pCon->state = ESPCONN_NONE; + client->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); + client->pCon->proto.tcp->local_port = espconn_port(); + client->pCon->proto.tcp->remote_port = client->port; + client->pCon->reverse = client; + espconn_regist_connectcb(client->pCon, mqtt_tcpclient_connect_cb); + espconn_regist_reconcb(client->pCon, mqtt_tcpclient_recon_cb); + + // start timer function to tick every second + os_timer_disarm(&client->mqttTimer); + os_timer_setfn(&client->mqttTimer, (os_timer_func_t *)mqtt_timer, client); + os_timer_arm(&client->mqttTimer, 1000, 1); + + // initiate the TCP connection or DNS lookup + os_printf("MQTT: Connect to %s:%d %p\n", client->host, client->port, client->pCon); + if (UTILS_StrToIP((const char *)client->host, + (void*)&client->pCon->proto.tcp->remote_ip)) { + uint8_t err; + if (client->security) + err = espconn_secure_connect(client->pCon); + else + err = espconn_connect(client->pCon); + if (err != 0) { + os_printf("MQTT ERROR: Failed to connect\n"); + os_free(client->pCon->proto.tcp); + os_free(client->pCon); + client->pCon = NULL; + return; + } + } else { + espconn_gethostbyname(client->pCon, (const char *)client->host, &client->ip, + mqtt_dns_found); + } + + client->connState = TCP_CONNECTING; + client->timeoutTick = 20; // generous timeout to allow for DNS, etc + client->sending = FALSE; +} + +static void ICACHE_FLASH_ATTR +mqtt_doAbort(MQTT_Client* client) { + os_printf("MQTT: Disconnecting from %s:%d (%p)\n", client->host, client->port, client->pCon); + client->pCon->reverse = NULL; // ensure we jettison this pCon... + if (client->security) + espconn_secure_disconnect(client->pCon); + else + espconn_disconnect(client->pCon); + + if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client); + if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client); + + if (client->sending_buffer != NULL) { + os_free(client->sending_buffer); + client->sending_buffer = NULL; + } + client->pCon = NULL; // it will be freed in disconnect callback + client->connState = TCP_RECONNECT_REQ; + client->timeoutTick = client->reconTimeout; // reconnect in a few seconds + if (client->reconTimeout < 128) client->reconTimeout <<= 1; +} + +void ICACHE_FLASH_ATTR +MQTT_Reconnect(MQTT_Client* client) { + DBG_MQTT("MQTT: Reconnect requested\n"); + if (client->connState == MQTT_DISCONNECTED) + MQTT_Connect(client); + else if (client->connState == MQTT_CONNECTED) + mqtt_doAbort(client); + // in other cases we're already in the reconnecting process +} + +void ICACHE_FLASH_ATTR +MQTT_Disconnect(MQTT_Client* client) { + DBG_MQTT("MQTT: Disconnect requested\n"); + os_timer_disarm(&client->mqttTimer); + if (client->connState == MQTT_DISCONNECTED) return; + if (client->connState == TCP_RECONNECT_REQ) { + client->connState = MQTT_DISCONNECTED; + return; + } + mqtt_doAbort(client); + //void *out_buffer = client->mqtt_connection.buffer; + //if (out_buffer != NULL) os_free(out_buffer); + client->connState = MQTT_DISCONNECTED; // ensure we don't automatically reconnect +} + +void ICACHE_FLASH_ATTR +MQTT_Free(MQTT_Client* client) { + DBG_MQTT("MQTT: Free requested\n"); + MQTT_Disconnect(client); + + if (client->host) os_free(client->host); + client->host = NULL; + + if (client->connect_info.client_id) os_free(client->connect_info.client_id); + if (client->connect_info.username) os_free(client->connect_info.username); + if (client->connect_info.password) os_free(client->connect_info.password); + os_memset(&client->connect_info, 0, sizeof(mqtt_connect_info_t)); + + if (client->in_buffer) os_free(client->in_buffer); + client->in_buffer = NULL; + + if (client->mqtt_connection.buffer) os_free(client->mqtt_connection.buffer); + os_memset(&client->mqtt_connection, 0, sizeof(client->mqtt_connection)); +} + +void ICACHE_FLASH_ATTR +MQTT_OnConnected(MQTT_Client* client, MqttCallback connectedCb) { + client->connectedCb = connectedCb; +} + +void ICACHE_FLASH_ATTR +MQTT_OnDisconnected(MQTT_Client* client, MqttCallback disconnectedCb) { + client->disconnectedCb = disconnectedCb; +} + +void ICACHE_FLASH_ATTR +MQTT_OnData(MQTT_Client* client, MqttDataCallback dataCb) { + client->dataCb = dataCb; +} + +void ICACHE_FLASH_ATTR +MQTT_OnPublished(MQTT_Client* client, MqttCallback publishedCb) { + client->publishedCb = publishedCb; +} diff --git a/mqtt/mqtt.h b/mqtt/mqtt.h new file mode 100644 index 0000000..0e98474 --- /dev/null +++ b/mqtt/mqtt.h @@ -0,0 +1,134 @@ +/* mqtt.h +* +* Copyright (c) 2014-2015, Tuan PM +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of Redis nor the names of its contributors may be used +* to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef MQTT_H_ +#define MQTT_H_ + +#include "mqtt_msg.h" +#include "pktbuf.h" + +// in rest.c +uint8_t UTILS_StrToIP(const char* str, void *ip); + +// State of MQTT connection +typedef enum { + MQTT_DISCONNECTED, // we're in disconnected state + TCP_RECONNECT_REQ, // connect failed, needs reconnecting + TCP_CONNECTING, // in TCP connection process + MQTT_CONNECTED, // conneted (or connecting) +} tConnState; + +// Simple notification callback +typedef void (*MqttCallback)(uint32_t* args); +// Callback with data messge +typedef void (*MqttDataCallback)(uint32_t* args, const char* topic, uint32_t topic_len, + const char* data, uint32_t data_len); + +// MQTTY client data structure +typedef struct { + struct espconn* pCon; // socket + // connection information + char* host; // MQTT server + uint16_t port; + uint8_t security; // 0=tcp, 1=ssl + ip_addr_t ip; // MQTT server IP address + mqtt_connect_info_t connect_info; // info to connect/reconnect + // protocol state and message assembly + tConnState connState; // connection state + bool sending; // espconn_send is pending + mqtt_connection_t mqtt_connection; // message assembly descriptor + PktBuf* msgQueue; // queued outbound messages + // TCP input buffer + uint8_t* in_buffer; + int in_buffer_size; // length allocated + int in_buffer_filled; // number of bytes held + // outstanding message when we expect an ACK + PktBuf* pending_buffer; // buffer sent and awaiting ACK + PktBuf* sending_buffer; // buffer sent not awaiting ACK + // timer and associated timeout counters + ETSTimer mqttTimer; // timer for this connection + uint8_t keepAliveTick; // seconds 'til keep-alive is required (0=no k-a) + uint8_t keepAliveAckTick; // seconds 'til keep-alive ack is overdue (0=no k-a) + uint8_t timeoutTick; // seconds 'til other timeout + uint8_t sendTimeout; // value of send timeout setting + uint8_t reconTimeout; // timeout to reconnect (back-off) + // callbacks + MqttCallback connectedCb; + MqttCallback cmdConnectedCb; + MqttCallback disconnectedCb; + MqttCallback cmdDisconnectedCb; + MqttCallback publishedCb; + MqttCallback cmdPublishedCb; + MqttDataCallback dataCb; + MqttDataCallback cmdDataCb; + // misc + void* user_data; +} MQTT_Client; + +// Initialize client data structure +void MQTT_Init(MQTT_Client* mqttClient, char* host, uint32 port, + uint8_t security, uint8_t sendTimeout, + char* client_id, char* client_user, char* client_pass, + uint8_t keepAliveTime); + +// Completely free buffers associated with client data structure +// This does not free the mqttClient struct itself, it just readies the struct so +// it can be freed or MQTT_Init can be called on it again +void MQTT_Free(MQTT_Client* mqttClient); + +// Set Last Will Topic on client, must be called before MQTT_InitConnection +void MQTT_InitLWT(MQTT_Client* mqttClient, char* will_topic, char* will_msg, + uint8_t will_qos, uint8_t will_retain); + +// Disconnect and reconnect in order to change params (such as LWT) +void MQTT_Reconnect(MQTT_Client* mqttClient); + +// Kick of a persistent connection to the broker, will reconnect anytime conn breaks +void MQTT_Connect(MQTT_Client* mqttClient); + +// Kill persistent connection +void MQTT_Disconnect(MQTT_Client* mqttClient); + +// Subscribe to a topic +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, + uint8_t qos, uint8_t retain); + +// Callback when connected +void MQTT_OnConnected(MQTT_Client* mqttClient, MqttCallback connectedCb); +// Callback when disconnected +void MQTT_OnDisconnected(MQTT_Client* mqttClient, MqttCallback disconnectedCb); +// Callback when publish succeeded +void MQTT_OnPublished(MQTT_Client* mqttClient, MqttCallback publishedCb); +// Callback when data arrives for subscription +void MQTT_OnData(MQTT_Client* mqttClient, MqttDataCallback dataCb); + +#endif /* USER_AT_MQTT_H_ */ diff --git a/mqtt/mqtt_cmd.c b/mqtt/mqtt_cmd.c new file mode 100644 index 0000000..503b41e --- /dev/null +++ b/mqtt/mqtt_cmd.c @@ -0,0 +1,377 @@ +// +// MQTT Commands coming in from the attache microcontrollver over the serial port +// + +#include +#include "mqtt.h" +#include "mqtt_client.h" +#include "mqtt_cmd.h" + +#ifdef MQTTCMD_DBG +#define DBG_MQTTCMD(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG_MQTTCMD(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; + MqttCmdCb* cb = (MqttCmdCb*)client->user_data; + DBG_MQTTCMD("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); +} + +void ICACHE_FLASH_ATTR +cmdMqttDisconnectedCb(uint32_t* args) { + MQTT_Client* client = (MQTT_Client*)args; + MqttCmdCb* cb = (MqttCmdCb*)client->user_data; + DBG_MQTTCMD("MQTT: Disconnected\n"); + uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->disconnectedCb, 0, 0); + CMD_ResponseEnd(crc); +} + +void ICACHE_FLASH_ATTR +cmdMqttPublishedCb(uint32_t* args) { + MQTT_Client* client = (MQTT_Client*)args; + MqttCmdCb* cb = (MqttCmdCb*)client->user_data; + DBG_MQTTCMD("MQTT: Published\n"); + uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->publishedCb, 0, 0); + CMD_ResponseEnd(crc); +} + +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; + MqttCmdCb* cb = (MqttCmdCb*)client->user_data; + + 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); +} + +uint32_t ICACHE_FLASH_ATTR +MQTTCMD_Lwt(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&req, cmd); + + if (CMD_GetArgc(&req) != 5) + return 0; + + // get mqtt client + uint32_t client_ptr; + CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); +#ifdef MQTT_1_CLIENT + MQTT_Client* client = &mqttClient; +#else + MQTT_Client* client = (MQTT_Client*)client_ptr; + DBG_MQTTCMD("MQTT: MQTTCMD_Lwt client ptr=%p\n", (void*)client_ptr); +#endif + + // free old topic & message + if (client->connect_info.will_topic) + os_free(client->connect_info.will_topic); + if (client->connect_info.will_message) + os_free(client->connect_info.will_message); + + uint16_t len; + + // get topic + len = CMD_ArgLen(&req); + if (len > 128) return 0; // safety check + client->connect_info.will_topic = (char*)os_zalloc(len + 1); + CMD_PopArg(&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 + client->connect_info.will_message = (char*)os_zalloc(len + 1); + CMD_PopArg(&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); + + // get retain + CMD_PopArg(&req, (uint8_t*)&client->connect_info.will_retain, 4); + + DBG_MQTTCMD("MQTT: MQTTCMD_Lwt topic=%s, message=%s, qos=%d, retain=%d\n", + client->connect_info.will_topic, + client->connect_info.will_message, + client->connect_info.will_qos, + client->connect_info.will_retain); + + // trigger a reconnect to set the LWT + MQTT_Reconnect(client); + return 1; +} + +uint32_t ICACHE_FLASH_ATTR +MQTTCMD_Publish(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&req, cmd); + + if (CMD_GetArgc(&req) != 6) + return 0; + + // get mqtt client + uint32_t client_ptr; + CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); +#ifdef MQTT_1_CLIENT + MQTT_Client* client = &mqttClient; +#else + MQTT_Client* client = (MQTT_Client*)client_ptr; + DBG_MQTTCMD("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 + uint8_t *topic = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, topic, len); + topic[len] = 0; + + // get data + len = CMD_ArgLen(&req); + uint8_t *data = (uint8_t*)os_zalloc(len+1); + if (!data) { // safety check + os_free(topic); + return 0; + } + CMD_PopArg(&req, data, len); + data[len] = 0; + + uint32_t qos, retain, data_len; + + // get data length + // this isn't used but we have to pull it off the stack + CMD_PopArg(&req, (uint8_t*)&data_len, 4); + + // get qos + CMD_PopArg(&req, (uint8_t*)&qos, 4); + + // get retain + CMD_PopArg(&req, (uint8_t*)&retain, 4); + + DBG_MQTTCMD("MQTT: MQTTCMD_Publish topic=%s, data_len=%d, qos=%ld, retain=%ld\n", + topic, + os_strlen((char*)data), + qos, + retain); + + MQTT_Publish(client, (char*)topic, (char*)data, (uint8_t)qos, (uint8_t)retain); + os_free(topic); + os_free(data); + return 1; +} + +uint32_t ICACHE_FLASH_ATTR +MQTTCMD_Subscribe(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&req, cmd); + + if (CMD_GetArgc(&req) != 3) + return 0; + + // get mqtt client + uint32_t client_ptr; + CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); +#ifdef MQTT_1_CLIENT + MQTT_Client* client = &mqttClient; +#else + MQTT_Client* client = (MQTT_Client*)client_ptr; + DBG_MQTTCMD("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 + uint8_t* topic = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, topic, len); + topic[len] = 0; + + // get qos + uint32_t qos = 0; + CMD_PopArg(&req, (uint8_t*)&qos, 4); + + DBG_MQTTCMD("MQTT: MQTTCMD_Subscribe topic=%s, qos=%ld\n", topic, qos); + + MQTT_Subscribe(client, (char*)topic, (uint8_t)qos); + os_free(topic); + return 1; +} + +uint32_t ICACHE_FLASH_ATTR +MQTTCMD_Setup(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&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) + return 0; + + // create mqtt client + uint8_t clientLen = sizeof(MQTT_Client); + MQTT_Client* client = (MQTT_Client*)os_zalloc(clientLen); + if (client == NULL) return 0; + os_memset(client, 0, clientLen); + + uint16_t len; + uint8_t *client_id, *user_data, *pass_data; + uint32_t keepalive, clean_session; + + // get client id + len = CMD_ArgLen(&req); + if (len > 32) return 0; // safety check + client_id = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, client_id, len); + client_id[len] = 0; + + // get username + len = CMD_ArgLen(&req); + if (len > 32) return 0; // safety check + user_data = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, user_data, len); + user_data[len] = 0; + + // get password + len = CMD_ArgLen(&req); + if (len > 32) return 0; // safety check + pass_data = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, pass_data, len); + pass_data[len] = 0; + + // get keepalive + CMD_PopArg(&req, (uint8_t*)&keepalive, 4); + + // get clean session + CMD_PopArg(&req, (uint8_t*)&clean_session, 4); +#ifdef MQTTCMD_DBG + DBG_MQTTCMD("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 + + // init client + // TODO: why malloc these all here, pass to MQTT_InitClient to be malloc'd again? + MQTT_InitClient(client, (char*)client_id, (char*)user_data, (char*)pass_data, keepalive, clean_session); + + os_free(client_id); + os_free(user_data); + os_free(pass_data); +#endif + + // 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; + + client->user_data = callback; + + client->cmdConnectedCb = cmdMqttConnectedCb; + client->cmdDisconnectedCb = cmdMqttDisconnectedCb; + client->cmdPublishedCb = cmdMqttPublishedCb; + client->cmdDataCb = cmdMqttDataCb; + + return 0xf00df00d; //(uint32_t)client; +} + +uint32_t ICACHE_FLASH_ATTR +MQTTCMD_Connect(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&req, cmd); + +#ifdef MQTT_1_CLIENT + return 1; + +#else + if (CMD_GetArgc(&req) != 4) + return 0; + + // get mqtt client + uint32_t client_ptr; + CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); + MQTT_Client* client = (MQTT_Client*)client_ptr; + DBG_MQTTCMD("MQTT: MQTTCMD_Connect client ptr=%p\n", (void*)client_ptr); + + uint16_t len; + + // get host + if (client->host) + os_free(client->host); + len = CMD_ArgLen(&req); + if (len > 128) return 0; // safety check + client->host = (char*)os_zalloc(len + 1); + CMD_PopArg(&req, client->host, len); + client->host[len] = 0; + + // get port + CMD_PopArg(&req, (uint8_t*)&client->port, 4); + + // get security + CMD_PopArg(&req, (uint8_t*)&client->security, 4); + DBG_MQTTCMD("MQTT: MQTTCMD_Connect host=%s, port=%d, security=%d\n", + client->host, + client->port, + client->security); + + MQTT_Connect(client); + return 1; +#endif +} + +uint32_t ICACHE_FLASH_ATTR +MQTTCMD_Disconnect(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&req, cmd); + +#ifdef MQTT_1_CLIENT + return 1; + +#else + if (CMD_GetArgc(&req) != 1) + return 0; + + // get mqtt client + uint32_t client_ptr; + CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); + MQTT_Client* client = (MQTT_Client*)client_ptr; + DBG_MQTTCMD("MQTT: MQTTCMD_Disconnect client ptr=%p\n", (void*)client_ptr); + + // disconnect + MQTT_Disconnect(client); + return 1; +#endif +} diff --git a/mqtt/mqtt_cmd.h b/mqtt/mqtt_cmd.h new file mode 100644 index 0000000..69997d1 --- /dev/null +++ b/mqtt/mqtt_cmd.h @@ -0,0 +1,20 @@ +#ifndef MODULES_MQTT_CMD_H_ +#define MODULES_MQTT_CMD_H_ + +#include "cmd.h" + +typedef struct { + uint32_t connectedCb; + uint32_t disconnectedCb; + uint32_t publishedCb; + 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); + +#endif /* MODULES_MQTT_CMD_H_ */ diff --git a/mqtt/mqtt_msg.c b/mqtt/mqtt_msg.c new file mode 100644 index 0000000..867dadd --- /dev/null +++ b/mqtt/mqtt_msg.c @@ -0,0 +1,452 @@ +/* +* Copyright (c) 2014, Stephen Robinson +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ + +#include +#include "mqtt_msg.h" +#define MQTT_MAX_FIXED_HEADER_SIZE 3 + +enum mqtt_connect_flag { + MQTT_CONNECT_FLAG_USERNAME = 1 << 7, + MQTT_CONNECT_FLAG_PASSWORD = 1 << 6, + MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5, + MQTT_CONNECT_FLAG_WILL = 1 << 2, + MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1 +}; + +struct + __attribute__((__packed__)) mqtt_connect_variable_header { + uint8_t lengthMsb; + uint8_t lengthLsb; +#if defined(PROTOCOL_NAMEv31) + uint8_t magic[6]; +#elif defined(PROTOCOL_NAMEv311) + uint8_t magic[4]; +#else +#error "Please define protocol name" +#endif + uint8_t version; + uint8_t flags; + uint8_t keepaliveMsb; + uint8_t keepaliveLsb; +}; + +static int ICACHE_FLASH_ATTR +append_string(mqtt_connection_t* connection, const char* string, int len) { + if (connection->message.length + len + 2 > connection->buffer_length) + return -1; + + connection->buffer[connection->message.length++] = len >> 8; + connection->buffer[connection->message.length++] = len & 0xff; + memcpy(connection->buffer + connection->message.length, string, len); + connection->message.length += len; + + return len + 2; +} + +static uint16_t ICACHE_FLASH_ATTR +append_message_id(mqtt_connection_t* connection, uint16_t message_id) { + // If message_id is zero then we should assign one, otherwise + // we'll use the one supplied by the caller + while (message_id == 0) + message_id = ++connection->message_id; + + if (connection->message.length + 2 > connection->buffer_length) + return 0; + + connection->buffer[connection->message.length++] = message_id >> 8; + connection->buffer[connection->message.length++] = message_id & 0xff; + + return message_id; +} + +static int ICACHE_FLASH_ATTR +init_message(mqtt_connection_t* connection) { + connection->message.length = MQTT_MAX_FIXED_HEADER_SIZE; + return MQTT_MAX_FIXED_HEADER_SIZE; +} + +static mqtt_message_t* ICACHE_FLASH_ATTR +fail_message(mqtt_connection_t* connection) { + connection->message.data = connection->buffer; + connection->message.length = 0; + return &connection->message; +} + +static mqtt_message_t* ICACHE_FLASH_ATTR +fini_message(mqtt_connection_t* connection, int type, int dup, int qos, int retain) { + int remaining_length = connection->message.length - MQTT_MAX_FIXED_HEADER_SIZE; + + if (remaining_length > 127) { + connection->buffer[0] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + connection->buffer[1] = 0x80 | (remaining_length % 128); + connection->buffer[2] = remaining_length / 128; + connection->message.length = remaining_length + 3; + connection->message.data = connection->buffer; + } + else { + connection->buffer[1] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + connection->buffer[2] = remaining_length; + connection->message.length = remaining_length + 2; + connection->message.data = connection->buffer + 1; + } + + return &connection->message; +} + +void ICACHE_FLASH_ATTR +mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length) { + uint8_t len = sizeof(connection); + memset(connection, '\0', len); + connection->buffer = buffer; + connection->buffer_length = buffer_length; +} + +int ICACHE_FLASH_ATTR +mqtt_get_total_length(const uint8_t* buffer, uint16_t length) { + int i; + int totlen = 0; + + for (i = 1; i < length; ++i) { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + totlen += i; + + return totlen; +} + +const char* ICACHE_FLASH_ATTR +mqtt_get_publish_topic(const uint8_t* buffer, uint16_t* length) { + int i; + int totlen = 0; + int topiclen; + + for (i = 1; i < *length; ++i) { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + totlen += i; + + if (i + 2 >= *length) + return NULL; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if (i + topiclen > *length) + return NULL; + + *length = topiclen; + return (const char*)(buffer + i); +} + +const char* ICACHE_FLASH_ATTR +mqtt_get_publish_data(const uint8_t* buffer, uint16_t* length) { + int i; + int totlen = 0; + int topiclen; + + for (i = 1; i < *length; ++i) { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + totlen += i; + + if (i + 2 >= *length) + return NULL; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if (i + topiclen >= *length) { + *length = 0; + return NULL; + } + i += topiclen; + + if (mqtt_get_qos(buffer) > 0) { + if (i + 2 >= *length) + return NULL; + i += 2; + } + + if (totlen < i) + return NULL; + + if (totlen <= *length) + *length = totlen - i; + else + *length = *length - i; + return (const char*)(buffer + i); +} + +uint16_t ICACHE_FLASH_ATTR +mqtt_get_id(const uint8_t* buffer, uint16_t length) { + if (length < 1) + return 0; + + switch (mqtt_get_type(buffer)) { + case MQTT_MSG_TYPE_PUBLISH: { + int i; + int topiclen; + + for (i = 1; i < length; ++i) { + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + + if (i + 2 >= length) + return 0; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if (i + topiclen >= length) + return 0; + i += topiclen; + + if (mqtt_get_qos(buffer) > 0) { + if (i + 2 >= length) + return 0; + //i += 2; + } + else { + return 0; + } + + return (buffer[i] << 8) | buffer[i + 1]; + } + case MQTT_MSG_TYPE_PUBACK: + case MQTT_MSG_TYPE_PUBREC: + case MQTT_MSG_TYPE_PUBREL: + case MQTT_MSG_TYPE_PUBCOMP: + case MQTT_MSG_TYPE_SUBACK: + case MQTT_MSG_TYPE_UNSUBACK: + case MQTT_MSG_TYPE_SUBSCRIBE: { + // This requires the remaining length to be encoded in 1 byte, + // which it should be. + if (length >= 4 && (buffer[1] & 0x80) == 0) + return (buffer[2] << 8) | buffer[3]; + else + return 0; + } + + default: + return 0; + } +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info) { + struct mqtt_connect_variable_header* variable_header; + + init_message(connection); + + if (connection->message.length + sizeof(*variable_header) > connection->buffer_length) + return fail_message(connection); + variable_header = (void*)(connection->buffer + connection->message.length); + connection->message.length += sizeof(*variable_header); + + variable_header->lengthMsb = 0; +#if defined(PROTOCOL_NAMEv31) + variable_header->lengthLsb = 6; + memcpy(variable_header->magic, "MQIsdp", 6); + variable_header->version = 3; +#elif defined(PROTOCOL_NAMEv311) + variable_header->lengthLsb = 4; + memcpy(variable_header->magic, "MQTT", 4); + variable_header->version = 4; +#else +#error "Please define protocol name" +#endif + + variable_header->flags = 0; + variable_header->keepaliveMsb = info->keepalive >> 8; + variable_header->keepaliveLsb = info->keepalive & 0xff; + + if (info->clean_session) + variable_header->flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION; + + if (info->client_id != NULL && info->client_id[0] != '\0') { + if (append_string(connection, info->client_id, strlen(info->client_id)) < 0) + return fail_message(connection); + } + else + return fail_message(connection); + + if (info->will_topic != NULL && info->will_topic[0] != '\0') { + if (append_string(connection, info->will_topic, strlen(info->will_topic)) < 0) + return fail_message(connection); + + if (append_string(connection, info->will_message, strlen(info->will_message)) < 0) + return fail_message(connection); + + variable_header->flags |= MQTT_CONNECT_FLAG_WILL; + if (info->will_retain) + variable_header->flags |= MQTT_CONNECT_FLAG_WILL_RETAIN; + variable_header->flags |= (info->will_qos & 3) << 3; + } + + if (info->username != NULL && info->username[0] != '\0') { + if (append_string(connection, info->username, strlen(info->username)) < 0) + return fail_message(connection); + + variable_header->flags |= MQTT_CONNECT_FLAG_USERNAME; + } + + if (info->password != NULL && info->password[0] != '\0') { + if (append_string(connection, info->password, strlen(info->password)) < 0) + return fail_message(connection); + + variable_header->flags |= MQTT_CONNECT_FLAG_PASSWORD; + } + + return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id) { + init_message(connection); + + if (topic == NULL || topic[0] == '\0') + return fail_message(connection); + + if (append_string(connection, topic, strlen(topic)) < 0) + return fail_message(connection); + + if (qos > 0) { + if ((*message_id = append_message_id(connection, 0)) == 0) + return fail_message(connection); + } + else + *message_id = 0; + + if (connection->message.length + data_length > connection->buffer_length) + return fail_message(connection); + memcpy(connection->buffer + connection->message.length, data, data_length); + connection->message.length += data_length; + + return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id) { + init_message(connection); + if (append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id) { + init_message(connection); + if (append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id) { + init_message(connection); + if (append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id) { + init_message(connection); + if (append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id) { + init_message(connection); + + if (topic == NULL || topic[0] == '\0') + return fail_message(connection); + + if ((*message_id = append_message_id(connection, 0)) == 0) + return fail_message(connection); + + if (append_string(connection, topic, strlen(topic)) < 0) + return fail_message(connection); + + if (connection->message.length + 1 > connection->buffer_length) + return fail_message(connection); + connection->buffer[connection->message.length++] = qos; + + return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id) { + init_message(connection); + + if (topic == NULL || topic[0] == '\0') + return fail_message(connection); + + if ((*message_id = append_message_id(connection, 0)) == 0) + return fail_message(connection); + + if (append_string(connection, topic, strlen(topic)) < 0) + return fail_message(connection); + + return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_pingreq(mqtt_connection_t* connection) { + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_pingresp(mqtt_connection_t* connection) { + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0); +} + +mqtt_message_t* ICACHE_FLASH_ATTR +mqtt_msg_disconnect(mqtt_connection_t* connection) { + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); +} diff --git a/mqtt/mqtt_msg.h b/mqtt/mqtt_msg.h new file mode 100644 index 0000000..0ca5c6b --- /dev/null +++ b/mqtt/mqtt_msg.h @@ -0,0 +1,127 @@ +/* +* Copyright (c) 2014, Stephen Robinson +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ + +#ifndef MQTT_MSG_H +#define MQTT_MSG_H + +#define PROTOCOL_NAMEv311 + +enum mqtt_message_type { + MQTT_MSG_TYPE_CONNECT = 1, + MQTT_MSG_TYPE_CONNACK = 2, + MQTT_MSG_TYPE_PUBLISH = 3, + MQTT_MSG_TYPE_PUBACK = 4, + MQTT_MSG_TYPE_PUBREC = 5, + MQTT_MSG_TYPE_PUBREL = 6, + MQTT_MSG_TYPE_PUBCOMP = 7, + MQTT_MSG_TYPE_SUBSCRIBE = 8, + MQTT_MSG_TYPE_SUBACK = 9, + MQTT_MSG_TYPE_UNSUBSCRIBE = 10, + MQTT_MSG_TYPE_UNSUBACK = 11, + MQTT_MSG_TYPE_PINGREQ = 12, + MQTT_MSG_TYPE_PINGRESP = 13, + MQTT_MSG_TYPE_DISCONNECT = 14 +}; + +// Descriptor for a serialized MQTT message, this is returned by functions that compose a message +// (It's really an MQTT packet in v3.1.1 terminology) +typedef struct mqtt_message { + uint8_t* data; + uint16_t length; +} mqtt_message_t; + +// Descriptor for a connection with message assembly storage +typedef struct mqtt_connection { + mqtt_message_t message; // resulting message + uint16_t message_id; // id of assembled message and memo to calculate next message id + uint8_t* buffer; // buffer for assembling messages + uint16_t buffer_length; // buffer length +} mqtt_connection_t; + +// Descriptor for a connect request +typedef struct mqtt_connect_info { + char* client_id; + char* username; + char* password; + char* will_topic; + char* will_message; + uint8_t keepalive; + uint8_t will_qos; + uint8_t will_retain; + uint8_t clean_session; +} mqtt_connect_info_t; + +static inline int ICACHE_FLASH_ATTR mqtt_get_type(const uint8_t* buffer) { + return (buffer[0] & 0xf0) >> 4; +} + +static inline int ICACHE_FLASH_ATTR mqtt_get_dup(const uint8_t* buffer) { + return (buffer[0] & 0x08) >> 3; +} + +static inline int ICACHE_FLASH_ATTR mqtt_get_qos(const uint8_t* buffer) { + return (buffer[0] & 0x06) >> 1; +} + +static inline int ICACHE_FLASH_ATTR mqtt_get_retain(const uint8_t* buffer) { + return (buffer[0] & 0x01); +} + +// Init a connection descriptor +void mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length); + +// Returns the total length of a message including MQTT fixed header +int mqtt_get_total_length(const uint8_t* buffer, uint16_t length); + +// Return pointer to topic, length in in/out param: in=length of buffer, out=length of topic +const char* mqtt_get_publish_topic(const uint8_t* buffer, uint16_t* length); + +// Return pointer to data, length in in/out param: in=length of buffer, out=length of data +const char* mqtt_get_publish_data(const uint8_t* buffer, uint16_t* length); + +// Return message id +uint16_t mqtt_get_id(const uint8_t* buffer, uint16_t length); + +// The following functions construct an outgoing message +mqtt_message_t* mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info); +mqtt_message_t* mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id); +mqtt_message_t* mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id); +mqtt_message_t* mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id); +mqtt_message_t* mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id); +mqtt_message_t* mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id); +mqtt_message_t* mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id); +mqtt_message_t* mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id); +mqtt_message_t* mqtt_msg_pingreq(mqtt_connection_t* connection); +mqtt_message_t* mqtt_msg_pingresp(mqtt_connection_t* connection); +mqtt_message_t* mqtt_msg_disconnect(mqtt_connection_t* connection); + +#endif // MQTT_MSG_H + diff --git a/mqtt/pktbuf.c b/mqtt/pktbuf.c new file mode 100644 index 0000000..a3ac7af --- /dev/null +++ b/mqtt/pktbuf.c @@ -0,0 +1,66 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt + +#include +#include "pktbuf.h" + +#ifdef PKTBUF_DBG +static void ICACHE_FLASH_ATTR +PktBuf_Print(PktBuf *buf) { + os_printf("PktBuf:"); + for (int i=-16; i<0; i++) + os_printf(" %02X", ((uint8_t*)buf)[i]); + os_printf(" %p", buf); + for (int i=0; i<16; i++) + os_printf(" %02X", ((uint8_t*)buf)[i]); + os_printf("\n"); + os_printf("PktBuf: next=%p len=0x%04x\n", + ((void**)buf)[-4], ((uint16_t*)buf)[-6]); +} +#endif + + +PktBuf * ICACHE_FLASH_ATTR +PktBuf_New(uint16_t length) { + PktBuf *buf = os_zalloc(length+sizeof(PktBuf)); + buf->next = NULL; + buf->filled = 0; + //os_printf("PktBuf_New: %p l=%d->%d d=%p\n", + // buf, length, length+sizeof(PktBuf), buf->data); + return buf; +} + +PktBuf * ICACHE_FLASH_ATTR +PktBuf_Push(PktBuf *headBuf, PktBuf *buf) { + if (headBuf == NULL) { + //os_printf("PktBuf_Push: %p\n", buf); + return buf; + } + PktBuf *h = headBuf; + while (h->next != NULL) h = h->next; + h->next = buf; + //os_printf("PktBuf_Push: %p->..->%p\n", headBuf, buf); + return headBuf; +} + +PktBuf * ICACHE_FLASH_ATTR +PktBuf_Unshift(PktBuf *headBuf, PktBuf *buf) { + buf->next = headBuf; + //os_printf("PktBuf_Unshift: %p->%p\n", buf, buf->next); + return buf; +} + +PktBuf * ICACHE_FLASH_ATTR +PktBuf_Shift(PktBuf *headBuf) { + PktBuf *buf = headBuf->next; + headBuf->next = NULL; + //os_printf("PktBuf_Shift: (%p)->%p\n", headBuf, buf); + return buf; +} + +PktBuf * ICACHE_FLASH_ATTR +PktBuf_ShiftFree(PktBuf *headBuf) { + PktBuf *buf = headBuf->next; + //os_printf("PktBuf_ShiftFree: (%p)->%p\n", headBuf, buf); + os_free(headBuf); + return buf; +} diff --git a/mqtt/pktbuf.h b/mqtt/pktbuf.h new file mode 100644 index 0000000..bccd60a --- /dev/null +++ b/mqtt/pktbuf.h @@ -0,0 +1,27 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt + +#ifndef PKTBUF_H +#define PKTBUF_H + +typedef struct PktBuf { + struct PktBuf *next; // next buffer in chain + uint16_t filled; // number of bytes filled in buffer + uint8_t data[0]; // data in buffer +} PktBuf; + +// Allocate a new packet buffer of given length +PktBuf *PktBuf_New(uint16_t length); + +// Append a buffer to the end of a packet buffer queue, returns new head +PktBuf *PktBuf_Push(PktBuf *headBuf, PktBuf *buf); + +// Prepend a buffer to the beginning of a packet buffer queue, return new head +PktBuf * PktBuf_Unshift(PktBuf *headBuf, PktBuf *buf); + +// Shift first buffer off queue, returns new head (not shifted buffer!) +PktBuf *PktBuf_Shift(PktBuf *headBuf); + +// Shift first buffer off queue, free it, return new head +PktBuf *PktBuf_ShiftFree(PktBuf *headBuf); + +#endif diff --git a/rest/rest.c b/rest/rest.c new file mode 100644 index 0000000..4aec0a8 --- /dev/null +++ b/rest/rest.c @@ -0,0 +1,400 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// +// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh + +#include "esp8266.h" +#include "rest.h" +#include "cmd.h" + +#ifdef REST_DBG +#define DBG_REST(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG_REST(format, ...) do { } while(0) +#endif + + +// 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 +// any time, we cannot really rely on the attached MCU to call 'free' ever, so better do without. +// Instead, we allocate a fixed pool of connections an round-robin. What this means is that the +// attached MCU should really use at most as many REST connections as there are slots in the pool. +#define MAX_REST 4 +static RestClient restClient[MAX_REST]; +static uint8_t restNum = 0xff; // index into restClient for next slot to allocate +#define REST_CB 0xbeef0000 // fudge added to callback for arduino so we can detect problems + +// Receive HTTP response - this hacky function assumes that the full response is received in +// one go. Sigh... +static void ICACHE_FLASH_ATTR +tcpclient_recv(void *arg, char *pdata, unsigned short len) { + struct espconn *pCon = (struct espconn*)arg; + RestClient *client = (RestClient *)pCon->reverse; + + // parse status line + int pi = 0; + int32_t code = -1; + char statusCode[4] = "\0\0\0\0"; + int statusLen = 0; + bool inStatus = false; + while (pi < len) { + if (pdata[pi] == '\n') { + // end of status line + if (code == -1) code = 502; // BAD GATEWAY + break; + } else if (pdata[pi] == ' ') { + if (inStatus) code = atoi(statusCode); + inStatus = !inStatus; + } else if (inStatus) { + if (statusLen < 3) statusCode[statusLen] = pdata[pi]; + statusLen++; + } + pi++; + } + + // parse header, all this does is look for the end of the header + bool currentLineIsBlank = false; + while (pi < len) { + if (pdata[pi] == '\n') { + if (currentLineIsBlank) { + // body is starting + pi++; + break; + } + currentLineIsBlank = true; + } else if (pdata[pi] != '\r') { + currentLineIsBlank = false; + } + pi++; + } + //if (pi < len && pdata[pi] == '\r') pi++; // hacky! + + // collect body and send it + uint16_t crc; + int body_len = len-pi; + DBG_REST("REST: status=%ld, body=%d\n", code, body_len); + if (pi == len) { + crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 0); + } 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); +#if 0 + os_printf("REST: body="); + for (int j=pi; jsecurity) + // espconn_secure_disconnect(client->pCon); + //else + espconn_disconnect(client->pCon); + +} + +static void ICACHE_FLASH_ATTR +tcpclient_sent_cb(void *arg) { + struct espconn *pCon = (struct espconn *)arg; + RestClient* client = (RestClient *)pCon->reverse; + DBG_REST("REST: Sent\n"); + if (client->data_sent != client->data_len) { + // we only sent part of the buffer, send the rest + espconn_sent(client->pCon, (uint8_t*)(client->data+client->data_sent), + client->data_len-client->data_sent); + client->data_sent = client->data_len; + } else { + // we're done sending, free the memory + if (client->data) os_free(client->data); + client->data = 0; + } +} + +static void ICACHE_FLASH_ATTR +tcpclient_discon_cb(void *arg) { + struct espconn *pespconn = (struct espconn *)arg; + RestClient* client = (RestClient *)pespconn->reverse; + // free the data buffer, if we have one + if (client->data) os_free(client->data); + client->data = 0; +} + +static void ICACHE_FLASH_ATTR +tcpclient_recon_cb(void *arg, sint8 errType) { + struct espconn *pCon = (struct espconn *)arg; + RestClient* client = (RestClient *)pCon->reverse; + os_printf("REST #%d: conn reset, err=%d\n", client-restClient, errType); + // free the data buffer, if we have one + if (client->data) os_free(client->data); + client->data = 0; +} + +static void ICACHE_FLASH_ATTR +tcpclient_connect_cb(void *arg) { + struct espconn *pCon = (struct espconn *)arg; + RestClient* client = (RestClient *)pCon->reverse; + DBG_REST("REST #%d: connected\n", client-restClient); + espconn_regist_disconcb(client->pCon, tcpclient_discon_cb); + espconn_regist_recvcb(client->pCon, tcpclient_recv); + espconn_regist_sentcb(client->pCon, tcpclient_sent_cb); + + client->data_sent = client->data_len <= 1400 ? client->data_len : 1400; + DBG_REST("REST #%d: sending %d\n", client-restClient, client->data_sent); + //if(client->security){ + // espconn_secure_sent(client->pCon, client->data, client->data_sent); + //} + //else{ + espconn_sent(client->pCon, (uint8_t*)client->data, client->data_sent); + //} +} + +static void ICACHE_FLASH_ATTR +rest_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) { + struct espconn *pConn = (struct espconn *)arg; + RestClient* client = (RestClient *)pConn->reverse; + + if(ipaddr == NULL) { + os_printf("REST DNS: Got no ip, try to reconnect\n"); + return; + } + DBG_REST("REST DNS: found ip %d.%d.%d.%d\n", + *((uint8 *) &ipaddr->addr), + *((uint8 *) &ipaddr->addr + 1), + *((uint8 *) &ipaddr->addr + 2), + *((uint8 *) &ipaddr->addr + 3)); + if(client->ip.addr == 0 && ipaddr->addr != 0) { + os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); +#ifdef CLIENT_SSL_ENABLE + if(client->security) { + espconn_secure_connect(client->pCon); + } else +#endif + espconn_connect(client->pCon); + DBG_REST("REST: connecting...\n"); + } +} + +uint32_t ICACHE_FLASH_ATTR +REST_Setup(CmdPacket *cmd) { + CmdRequest req; + uint32_t port, security; + + // start parsing the command + CMD_Request(&req, cmd); + if(CMD_GetArgc(&req) != 3) return 0; + + // get the hostname + uint16_t len = CMD_ArgLen(&req); + if (len > 128) return 0; // safety check + uint8_t *rest_host = (uint8_t*)os_zalloc(len + 1); + if (CMD_PopArg(&req, rest_host, len)) return 0; + rest_host[len] = 0; + + // get the port + if (CMD_PopArg(&req, (uint8_t*)&port, 4)) { + os_free(rest_host); + return 0; + } + + // get the security mode + if (CMD_PopArg(&req, (uint8_t*)&security, 4)) { + os_free(rest_host); + return 0; + } + + // clear connection structures the first time + if (restNum == 0xff) { + os_memset(restClient, 0, MAX_REST * sizeof(RestClient)); + restNum = 0; + } + + // allocate a connection structure + RestClient *client = restClient + restNum; + uint8_t clientNum = restNum; + restNum = (restNum+1)%MAX_REST; + + // free any data structure that may be left from a previous connection + if (client->header) os_free(client->header); + if (client->content_type) os_free(client->content_type); + if (client->user_agent) os_free(client->user_agent); + if (client->data) os_free(client->data); + if (client->pCon) { + if (client->pCon->proto.tcp) os_free(client->pCon->proto.tcp); + os_free(client->pCon); + } + os_memset(client, 0, sizeof(RestClient)); + DBG_REST("REST: setup #%d host=%s port=%ld security=%ld\n", clientNum, rest_host, port, security); + + client->resp_cb = cmd->callback; + + client->host = (char *)rest_host; + client->port = port; + client->security = security; + + client->header = (char*)os_zalloc(4); + client->header[0] = 0; + + client->content_type = (char*)os_zalloc(22); + os_sprintf((char *)client->content_type, "x-www-form-urlencoded"); + + client->user_agent = (char*)os_zalloc(9); + os_sprintf((char *)client->user_agent, "esp-link"); + + client->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + client->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); + + client->pCon->type = ESPCONN_TCP; + client->pCon->state = ESPCONN_NONE; + client->pCon->proto.tcp->local_port = espconn_port(); + client->pCon->proto.tcp->remote_port = client->port; + + client->pCon->reverse = client; + + return REST_CB | (uint32_t)clientNum; +} + +uint32_t ICACHE_FLASH_ATTR +REST_SetHeader(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&req, cmd); + + if(CMD_GetArgc(&req) != 3) + return 0; + + // 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); + + // Get header selector + uint32_t header_index; + if (CMD_PopArg(&req, (uint8_t*)&header_index, 4)) return 0; + + // Get header value + uint16_t len = CMD_ArgLen(&req); + if (len > 256) return 0; //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); + client->header[len] = '\r'; + client->header[len+1] = '\n'; + client->header[len+2] = 0; + DBG_REST("REST: Set header: %s\r\n", client->header); + break; + 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); + client->content_type[len] = '\r'; + client->content_type[len+1] = '\n'; + client->content_type[len+2] = 0; + DBG_REST("REST: Set content_type: %s\r\n", client->content_type); + break; + 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); + client->user_agent[len] = '\r'; + client->user_agent[len+1] = '\n'; + client->user_agent[len+2] = 0; + DBG_REST("REST: Set user_agent: %s\r\n", client->user_agent); + break; + } + return 1; +} + +uint32_t ICACHE_FLASH_ATTR +REST_Request(CmdPacket *cmd) { + CmdRequest req; + CMD_Request(&req, cmd); + DBG_REST("REST: request"); + // 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; + DBG_REST(" #%ld", clientNum); + // Get HTTP method + uint16_t len = CMD_ArgLen(&req); + if (len > 15) goto fail; + char method[16]; + CMD_PopArg(&req, method, len); + method[len] = 0; + DBG_REST(" method=%s", method); + // Get HTTP path + len = CMD_ArgLen(&req); + if (len > 1023) goto fail; + char path[1024]; + CMD_PopArg(&req, path, len); + path[len] = 0; + DBG_REST(" path=%s", path); + // Get HTTP body + uint32_t realLen = 0; + if (CMD_GetArgc(&req) == 3) { + realLen = 0; + len = 0; + } else { + CMD_PopArg(&req, (uint8_t*)&realLen, 4); + + len = CMD_ArgLen(&req); + if (len > 2048 || realLen > len) goto fail; + } + DBG_REST(" bodyLen=%ld", realLen); + + // we need to allocate memory for the header plus the body. First we count the length of the + // header (including some extra counted "%s" and then we add the body length. We allocate the + // whole shebang and copy everything into it. + // BTW, use http/1.0 to avoid responses with transfer-encoding: chunked + char *headerFmt = "%s %s HTTP/1.0\r\n" + "Host: %s\r\n" + "%s" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "Content-Type: %s\r\n" + "User-Agent: %s\r\n\r\n"; + uint16_t headerLen = strlen(headerFmt) + strlen(method) + strlen(path) + strlen(client->host) + + strlen(client->header) + strlen(client->content_type) + strlen(client->user_agent); + DBG_REST(" hdrLen=%d", headerLen); + if (client->data) os_free(client->data); + client->data = (char*)os_zalloc(headerLen + realLen); + if (client->data == NULL) goto fail; + DBG_REST(" totLen=%ld data=%p", headerLen + realLen, client->data); + client->data_len = os_sprintf((char*)client->data, headerFmt, method, path, client->host, + client->header, realLen, client->content_type, client->user_agent); + DBG_REST(" hdrLen=%d", client->data_len); + + if (realLen > 0) { + CMD_PopArg(&req, client->data + client->data_len, realLen); + client->data_len += realLen; + } + DBG_REST("\n"); + + //DBG_REST("REST request: %s", (char*)client->data); + + DBG_REST("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); + + if(UTILS_StrToIP((char *)client->host, &client->pCon->proto.tcp->remote_ip)) { + DBG_REST("REST: Connect to ip %s:%ld\n",client->host, client->port); + //if(client->security){ + // espconn_secure_connect(client->pCon); + //} + //else { + espconn_connect(client->pCon); + //} + } else { + DBG_REST("REST: Connect to host %s:%ld\n", client->host, client->port); + espconn_gethostbyname(client->pCon, (char *)client->host, &client->ip, rest_dns_found); + } + + return 1; + +fail: + DBG_REST("\n"); + return 0; +} diff --git a/rest/rest.h b/rest/rest.h new file mode 100644 index 0000000..7161e1f --- /dev/null +++ b/rest/rest.h @@ -0,0 +1,40 @@ +/* + * api.h + * + * Created on: Mar 4, 2015 + * Author: Minh + */ + +#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); + +#endif /* MODULES_INCLUDE_API_H_ */ diff --git a/serial/console.c b/serial/console.c index d38e73a..d0d0dd7 100644 --- a/serial/console.c +++ b/serial/console.c @@ -25,108 +25,112 @@ static int console_pos; // offset since reset of buffer static void ICACHE_FLASH_ATTR console_write(char c) { - console_buf[console_wr] = c; - console_wr = (console_wr+1) % BUF_MAX; - if (console_wr == console_rd) { - // full, we write anyway and loose the oldest char - console_rd = (console_rd+1) % BUF_MAX; // full, eat first char - console_pos++; - } + console_buf[console_wr] = c; + console_wr = (console_wr+1) % BUF_MAX; + if (console_wr == console_rd) { + // full, we write anyway and loose the oldest char + console_rd = (console_rd+1) % BUF_MAX; // full, eat first char + console_pos++; + } } +#if 0 // return previous character in console, 0 if at start static char ICACHE_FLASH_ATTR console_prev(void) { - if (console_wr == console_rd) return 0; - return console_buf[(console_wr-1+BUF_MAX)%BUF_MAX]; + if (console_wr == console_rd) return 0; + return console_buf[(console_wr-1+BUF_MAX)%BUF_MAX]; } +#endif void ICACHE_FLASH_ATTR console_write_char(char c) { - if (c == '\n' && console_prev() != '\r') console_write('\r'); - console_write(c); + //if (c == '\n' && console_prev() != '\r') console_write('\r'); // does more harm than good + console_write(c); } int ICACHE_FLASH_ATTR ajaxConsoleReset(HttpdConnData *connData) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - jsonHeader(connData, 200); - console_rd = console_wr = console_pos = 0; - serbridgeReset(); - return HTTPD_CGI_DONE; + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + jsonHeader(connData, 200); + console_rd = console_wr = console_pos = 0; + serbridgeReset(); + return HTTPD_CGI_DONE; } int ICACHE_FLASH_ATTR ajaxConsoleBaud(HttpdConnData *connData) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - char buff[512]; - int len, status = 400; - len = httpdFindArg(connData->getArgs, "rate", buff, sizeof(buff)); - if (len > 0) { - int rate = atoi(buff); - if (rate >= 9600 && rate <= 1000000) { - uart0_baud(rate); - flashConfig.baud_rate = rate; - status = configSave() ? 200 : 400; - } - } else if (connData->requestType == HTTPD_METHOD_GET) { - status = 200; - } - - jsonHeader(connData, status); - os_sprintf(buff, "{\"rate\": %ld}", flashConfig.baud_rate); - httpdSend(connData, buff, -1); - return HTTPD_CGI_DONE; + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + char buff[512]; + int len, status = 400; + len = httpdFindArg(connData->getArgs, "rate", buff, sizeof(buff)); + if (len > 0) { + int rate = atoi(buff); + if (rate >= 9600 && rate <= 1000000) { + uart0_baud(rate); + flashConfig.baud_rate = rate; + status = configSave() ? 200 : 400; + } + } else if (connData->requestType == HTTPD_METHOD_GET) { + status = 200; + } + + jsonHeader(connData, status); + os_sprintf(buff, "{\"rate\": %ld}", flashConfig.baud_rate); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; } int ICACHE_FLASH_ATTR ajaxConsole(HttpdConnData *connData) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - char buff[2048]; - int len; // length of text in buff - int console_len = (console_wr+BUF_MAX-console_rd) % BUF_MAX; // num chars in console_buf - int start = 0; // offset onto console_wr to start sending out chars - - jsonHeader(connData, 200); - - // figure out where to start in buffer based on URI param - len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); - if (len > 0) { - start = atoi(buff); - if (start < console_pos) { - start = 0; - } else if (start >= console_pos+console_len) { - start = console_len; - } else { - start = start - console_pos; - } - } - - // start outputting - len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", - console_len-start, console_pos+start); - - int rd = (console_rd+start) % BUF_MAX; - while (len < 2040 && rd != console_wr) { - uint8_t c = console_buf[rd]; - if (c == '\\' || c == '"') { - buff[len++] = '\\'; - buff[len++] = c; - } else if (c < ' ') { - len += os_sprintf(buff+len, "\\u%04x", c); - } else { - buff[len++] = c; - } - rd = (rd + 1) % BUF_MAX; - } - os_strcpy(buff+len, "\"}"); len+=2; - httpdSend(connData, buff, len); - return HTTPD_CGI_DONE; + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + char buff[2048]; + int len; // length of text in buff + int console_len = (console_wr+BUF_MAX-console_rd) % BUF_MAX; // num chars in console_buf + int start = 0; // offset onto console_wr to start sending out chars + + jsonHeader(connData, 200); + + // figure out where to start in buffer based on URI param + len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); + if (len > 0) { + start = atoi(buff); + if (start < console_pos) { + start = 0; + } else if (start >= console_pos+console_len) { + start = console_len; + } else { + start = start - console_pos; + } + } + + // start outputting + len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", + console_len-start, console_pos+start); + + int rd = (console_rd+start) % BUF_MAX; + while (len < 2040 && rd != console_wr) { + uint8_t c = console_buf[rd]; + if (c == '\\' || c == '"') { + buff[len++] = '\\'; + buff[len++] = c; + } else if (c == '\r') { + // this is crummy, but browsers display a newline for \r\n sequences + } else if (c < ' ') { + len += os_sprintf(buff+len, "\\u%04x", c); + } else { + buff[len++] = c; + } + rd = (rd + 1) % BUF_MAX; + } + os_strcpy(buff+len, "\"}"); len+=2; + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; } void ICACHE_FLASH_ATTR consoleInit() { - console_wr = 0; - console_rd = 0; + console_wr = 0; + console_rd = 0; } diff --git a/serial/crc16.c b/serial/crc16.c new file mode 100644 index 0000000..22a6651 --- /dev/null +++ b/serial/crc16.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2005, Swedish Institute of Computer Science + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + */ + +/** \addtogroup crc16 + * @{ */ + +/** + * \file + * Implementation of the CRC16 calculcation + * \author + * Adam Dunkels + * + */ + +/* CITT CRC16 polynomial ^16 + ^12 + ^5 + 1 */ +/*---------------------------------------------------------------------------*/ +unsigned short +crc16_add(unsigned char b, unsigned short acc) +{ + /* + acc = (unsigned char)(acc >> 8) | (acc << 8); + acc ^= b; + acc ^= (unsigned char)(acc & 0xff) >> 4; + acc ^= (acc << 8) << 4; + acc ^= ((acc & 0xff) << 4) << 1; + */ + + acc ^= b; + acc = (acc >> 8) | (acc << 8); + acc ^= (acc & 0xff00) << 4; + acc ^= (acc >> 8) >> 4; + acc ^= (acc & 0xff00) >> 5; + return acc; +} +/*---------------------------------------------------------------------------*/ +unsigned short +crc16_data(const unsigned char *data, int len, unsigned short acc) +{ + int i; + + for(i = 0; i < len; ++i) { + acc = crc16_add(*data, acc); + ++data; + } + return acc; +} +/*---------------------------------------------------------------------------*/ + +/** @} */ diff --git a/serial/crc16.h b/serial/crc16.h new file mode 100644 index 0000000..bd4c52e --- /dev/null +++ b/serial/crc16.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2005, Swedish Institute of Computer Science + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * Header file for the CRC16 calculcation + * \author + * Adam Dunkels + * + */ + +/** \addtogroup lib + * @{ */ + +/** + * \defgroup crc16 Cyclic Redundancy Check 16 (CRC16) calculation + * + * The Cyclic Redundancy Check 16 is a hash function that produces a + * checksum that is used to detect errors in transmissions. The CRC16 + * calculation module is an iterative CRC calculator that can be used + * to cumulatively update a CRC checksum for every incoming byte. + * + * @{ + */ + +#ifndef CRC16_H_ +#define CRC16_H_ +#ifdef __cplusplus +extern "C" { +#endif +/** + * \brief Update an accumulated CRC16 checksum with one byte. + * \param b The byte to be added to the checksum + * \param crc The accumulated CRC that is to be updated. + * \return The updated CRC checksum. + * + * This function updates an accumulated CRC16 checksum + * with one byte. It can be used as a running checksum, or + * to checksum an entire data block. + * + * \note The algorithm used in this implementation is + * tailored for a running checksum and does not perform as + * well as a table-driven algorithm when checksumming an + * entire data block. + * + */ +unsigned short crc16_add(unsigned char b, unsigned short crc); + +/** + * \brief Calculate the CRC16 over a data area + * \param data Pointer to the data + * \param datalen The length of the data + * \param acc The accumulated CRC that is to be updated (or zero). + * \return The CRC16 checksum. + * + * This function calculates the CRC16 checksum of a data area. + * + * \note The algorithm used in this implementation is + * tailored for a running checksum and does not perform as + * well as a table-driven algorithm when checksumming an + * entire data block. + */ +unsigned short crc16_data(const unsigned char *data, int datalen, + unsigned short acc); +#ifdef __cplusplus +} +#endif +#endif /* CRC16_H_ */ + +/** @} */ +/** @} */ diff --git a/serial/serbridge.c b/serial/serbridge.c index 6d45610..9993cc9 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -1,87 +1,29 @@ // Copyright 2015 by Thorsten von Eicken, see LICENSE.txt -#include "espmissingincludes.h" -#include "c_types.h" -#include "user_interface.h" -#include "espconn.h" -#include "mem.h" -#include "osapi.h" -#include "gpio.h" +#include "esp8266.h" #include "uart.h" +#include "crc16.h" #include "serbridge.h" #include "serled.h" #include "config.h" #include "console.h" +#include "slip.h" +#include "cmd.h" -static struct espconn serbridgeConn; -static esp_tcp serbridgeTcp; +#define SKIP_AT_RESET + +static struct espconn serbridgeConn1; // plain bridging port +static struct espconn serbridgeConn2; // programming port +static esp_tcp serbridgeTcp1, serbridgeTcp2; static int8_t mcu_reset_pin, mcu_isp_pin; -sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len); +extern uint8_t slip_disabled; // disable slip to allow flashing of attached MCU // Connection pool serbridgeConnData connData[MAX_CONN]; -// Transmit buffers for the connection pool -static char txbuffer[MAX_CONN][MAX_TXBUFFER]; - -// Given a pointer to an espconn struct find the connection that correcponds to it -static serbridgeConnData ICACHE_FLASH_ATTR *serbridgeFindConnData(void *arg) { - for (int i=0; itxbuffer -// returns result from espconn_sent if data in buffer or ESPCONN_OK (0) -// Use only internally from espbuffsend and serbridgeSentCb -static sint8 ICACHE_FLASH_ATTR sendtxbuffer(serbridgeConnData *conn) { - sint8 result = ESPCONN_OK; - if (conn->txbufferlen != 0) { - //os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen); - conn->readytosend = false; - result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen); - conn->txbufferlen = 0; - if (result != ESPCONN_OK) { - os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn); - } - } - return result; -} - -// espbuffsend adds data to the send buffer. If the previous send was completed it calls -// sendtxbuffer and espconn_sent. -// Returns ESPCONN_OK (0) for success, -128 if buffer is full or error from espconn_sent -// Use espbuffsend instead of espconn_sent as it solves the problem that espconn_sent must -// only be called *after* receiving an espconn_sent_callback for the previous packet. -sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) { - if (conn->txbufferlen + len > MAX_TXBUFFER) { - os_printf("espbuffsend: txbuffer full on conn %p\n", conn); - return -128; - } - os_memcpy(conn->txbuffer + conn->txbufferlen, data, len); - conn->txbufferlen += len; - if (conn->readytosend) { - return sendtxbuffer(conn); - } else { - //os_printf("%d QU %d\n", system_get_time(), conn->txbufferlen); - } - return ESPCONN_OK; -} - -//callback after the data are sent -static void ICACHE_FLASH_ATTR serbridgeSentCb(void *arg) { - serbridgeConnData *conn = serbridgeFindConnData(arg); - //os_printf("Sent callback on conn %p\n", conn); - if (conn == NULL) return; - //os_printf("%d ST\n", system_get_time()); - conn->readytosend = true; - sendtxbuffer(conn); // send possible new data in txbuffer -} +//===== TCP -> UART // Telnet protocol characters #define IAC 255 // escape @@ -102,252 +44,449 @@ enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl } static uint8_t ICACHE_FLASH_ATTR telnetUnwrap(uint8_t *inBuf, int len, uint8_t state) { - for (int i=0; i write one to outbuf and go normal again - state = TN_normal; - uart0_write_char(c); - break; - case WILL: // negotiation - state = TN_will; - break; - case SB: // command sequence begin - state = TN_start; - break; - case SE: // command sequence end - state = TN_normal; - break; - default: // not sure... let's ignore - uart0_write_char(IAC); - uart0_write_char(c); - } - break; - case TN_will: - state = TN_normal; // yes, we do COM port options, let's go back to normal - break; - case TN_start: // in command seq, now comes the type of cmd - if (c == ComPortOpt) state = TN_comPort; - else state = TN_end; // an option we don't know, skip 'til the end seq - break; - case TN_end: // wait for end seq - if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq - break; - case TN_comPort: - if (c == SetControl) state = TN_setControl; - else state = TN_end; - break; - case TN_setControl: // switch control line and delay a tad - switch (c) { - case DTR_ON: - if (mcu_reset_pin >= 0) { - os_printf("MCU reset gpio%d\n", mcu_reset_pin); - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - } else os_printf("MCU reset: no pin\n"); - break; - case DTR_OFF: - if (mcu_reset_pin >= 0) { - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - os_delay_us(100L); - } - break; - case RTS_ON: - if (mcu_isp_pin >= 0) { - os_printf("MCU ISP gpio%d\n", mcu_isp_pin); - GPIO_OUTPUT_SET(mcu_isp_pin, 0); - os_delay_us(100L); - } else os_printf("MCU isp: no pin\n"); - break; - case RTS_OFF: - if (mcu_isp_pin >= 0) { - GPIO_OUTPUT_SET(mcu_isp_pin, 1); - os_delay_us(100L); - } - break; - } - state = TN_end; - break; - } - } - return state; + for (int i=0; i write one to outbuf and go normal again + state = TN_normal; + uart0_write_char(c); + break; + case WILL: // negotiation + state = TN_will; + break; + case SB: // command sequence begin + state = TN_start; + break; + case SE: // command sequence end + state = TN_normal; + break; + default: // not sure... let's ignore + uart0_write_char(IAC); + uart0_write_char(c); + } + break; + case TN_will: + state = TN_normal; // yes, we do COM port options, let's go back to normal + break; + case TN_start: // in command seq, now comes the type of cmd + if (c == ComPortOpt) state = TN_comPort; + else state = TN_end; // an option we don't know, skip 'til the end seq + break; + case TN_end: // wait for end seq + if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq + break; + case TN_comPort: + if (c == SetControl) state = TN_setControl; + else state = TN_end; + break; + case TN_setControl: // switch control line and delay a tad + switch (c) { + case DTR_ON: + if (mcu_reset_pin >= 0) { +#ifdef SERBR_DBG + os_printf("MCU reset gpio%d\n", mcu_reset_pin); +#endif + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + } +#ifdef SERBR_DBG + else os_printf("MCU reset: no pin\n"); +#endif + break; + case DTR_OFF: + if (mcu_reset_pin >= 0) { + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + os_delay_us(100L); + } + break; + case RTS_ON: + if (mcu_isp_pin >= 0) { +#ifdef SERBR_DBG + os_printf("MCU ISP gpio%d\n", mcu_isp_pin); +#endif + GPIO_OUTPUT_SET(mcu_isp_pin, 0); + os_delay_us(100L); + } +#ifdef SERBR_DBG + else os_printf("MCU isp: no pin\n"); +#endif + slip_disabled++; + break; + case RTS_OFF: + if (mcu_isp_pin >= 0) { + GPIO_OUTPUT_SET(mcu_isp_pin, 1); + os_delay_us(100L); + } + if (slip_disabled > 0) slip_disabled--; + break; + } + state = TN_end; + break; + } + } + return state; } -void ICACHE_FLASH_ATTR serbridgeReset() { - if (mcu_reset_pin >= 0) { - os_printf("MCU reset gpio%d\n", mcu_reset_pin); - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - } else os_printf("MCU reset: no pin\n"); +// Generate a reset pulse for the attached microcontroller +void ICACHE_FLASH_ATTR +serbridgeReset() +{ + if (mcu_reset_pin >= 0) { +#ifdef SERBR_DBG + os_printf("MCU reset gpio%d\n", mcu_reset_pin); +#endif + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + } +#ifdef SERBR_DBG + else os_printf("MCU reset: no pin\n"); +#endif } - // Receive callback -static void ICACHE_FLASH_ATTR serbridgeRecvCb(void *arg, char *data, unsigned short len) { - serbridgeConnData *conn = serbridgeFindConnData(arg); - //os_printf("Receive callback on conn %p\n", conn); - if (conn == NULL) return; - - // at the start of a connection we're in cmInit mode and we wait for the first few characters - // to arrive in order to decide what type of connection this is.. The following if statements - // do this dispatch. An issue here is that we assume that the first few characters all arrive - // in the same TCP packet, which is true if the sender is a program, but not necessarily - // if the sender is a person typing (although in that case the line-oriented TTY input seems - // to make it work too). If this becomes a problem we need to buffer the first few chars... - if (conn->conn_mode == cmInit) { - - // If the connection starts with the Arduino or ARM reset sequence we perform a RESET - if ((len == 2 && strncmp(data, "0 ", 2) == 0) || - (len == 2 && strncmp(data, "?\n", 2) == 0) || - (len == 3 && strncmp(data, "?\r\n", 3) == 0)) { - os_printf("MCU Reset=%d ISP=%d\n", mcu_reset_pin, mcu_isp_pin); - os_delay_us(2*1000L); // time for os_printf to happen - // send reset to arduino/ARM - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0); - os_delay_us(100L); - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); - os_delay_us(100L); - if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); - os_delay_us(1000L); - conn->conn_mode = cmAVR; - - - // If the connection starts with a telnet negotiation we will do telnet - } else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) { - conn->conn_mode = cmTelnet; - conn->telnet_state = TN_normal; - // note that the three negotiation chars will be gobbled-up by telnetUnwrap - os_printf("telnet mode\n"); - - // looks like a plain-vanilla connection! - } else { - conn->conn_mode = cmTransparent; - } - } - - // write the buffer to the uart - if (conn->conn_mode == cmTelnet) { - conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); - } else { - uart0_tx_buffer(data, len); - } - - serledFlash(50); // short blink on serial LED +static void ICACHE_FLASH_ATTR +serbridgeRecvCb(void *arg, char *data, unsigned short len) +{ + serbridgeConnData *conn = ((struct espconn*)arg)->reverse; + //os_printf("Receive callback on conn %p\n", conn); + if (conn == NULL) return; + + bool startPGM = false; + + // at the start of a connection we're in cmInit mode and we wait for the first few characters + // to arrive in order to decide what type of connection this is.. The following if statements + // do this dispatch. An issue here is that we assume that the first few characters all arrive + // in the same TCP packet, which is true if the sender is a program, but not necessarily + // if the sender is a person typing (although in that case the line-oriented TTY input seems + // to make it work too). If this becomes a problem we need to buffer the first few chars... + if (conn->conn_mode == cmInit) { + + // If the connection starts with the Arduino or ARM reset sequence we perform a RESET + if ((len == 2 && strncmp(data, "0 ", 2) == 0) || + (len == 2 && strncmp(data, "?\n", 2) == 0) || + (len == 3 && strncmp(data, "?\r\n", 3) == 0)) { + startPGM = true; + conn->conn_mode = cmPGM; + + // If the connection starts with a telnet negotiation we will do telnet + } + else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) { + conn->conn_mode = cmTelnet; + conn->telnet_state = TN_normal; + // note that the three negotiation chars will be gobbled-up by telnetUnwrap +#ifdef SERBR_DBG + os_printf("telnet mode\n"); +#endif + + // looks like a plain-vanilla connection! + } + else { + conn->conn_mode = cmTransparent; + } + + // if we start out in cmPGM mode due to a connection to the second port we need to do the + // reset dance right away + } else if (conn->conn_mode == cmPGMInit) { + conn->conn_mode = cmPGM; + startPGM = true; + } + + // do the programming reset dance + if (startPGM) { +#ifdef SERBR_DBG + os_printf("MCU Reset=gpio%d ISP=gpio%d\n", mcu_reset_pin, mcu_isp_pin); + os_delay_us(2*1000L); // time for os_printf to happen +#endif + // send reset to arduino/ARM, send "ISP" signal for the duration of the programming + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0); + os_delay_us(100L); + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); + //os_delay_us(100L); + //if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); + os_delay_us(1000L); // wait a millisecond before writing to the UART below + conn->conn_mode = cmPGM; + slip_disabled++; // disable SLIP so it doesn't interfere with flashing +#ifdef SKIP_AT_RESET + serledFlash(50); // short blink on serial LED + return; +#endif + } + + + // write the buffer to the uart + if (conn->conn_mode == cmTelnet) { + conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); + } else { + uart0_tx_buffer(data, len); + } + + serledFlash(50); // short blink on serial LED } -// Error callback (it's really poorly named, it's not a "connection reconnected" callback, -// it's really a "connection broken, please reconnect" callback) -static void ICACHE_FLASH_ATTR serbridgeReconCb(void *arg, sint8 err) { - serbridgeConnData *conn=serbridgeFindConnData(arg); - if (conn == NULL) return; - // Close the connection - espconn_disconnect(conn->conn); - conn->conn = NULL; +//===== UART -> TCP + +// Send all data in conn->txbuffer +// returns result from espconn_sent if data in buffer or ESPCONN_OK (0) +// Use only internally from espbuffsend and serbridgeSentCb +static sint8 ICACHE_FLASH_ATTR +sendtxbuffer(serbridgeConnData *conn) +{ + sint8 result = ESPCONN_OK; + if (conn->txbufferlen != 0) { + //os_printf("TX %p %d\n", conn, conn->txbufferlen); + conn->readytosend = false; + result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen); + conn->txbufferlen = 0; + if (result != ESPCONN_OK) { + os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn); + conn->txbufferlen = 0; + if (!conn->txoverflow_at) conn->txoverflow_at = system_get_time(); + } else { + conn->sentbuffer = conn->txbuffer; + conn->txbuffer = NULL; + conn->txbufferlen = 0; + } + } + return result; } -// Disconnection callback -static void ICACHE_FLASH_ATTR serbridgeDisconCb(void *arg) { - // Iterate through all the connections and deallocate the ones that are in a state that - // indicates that they're closed - for (int i=0; istate == ESPCONN_NONE || connData[i].conn->state == ESPCONN_CLOSE)) - { - if (connData[i].conn_mode == cmAVR) { - // send reset to arduino/ARM - if (mcu_reset_pin >= 0) { - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - } - } - connData[i].conn = NULL; - } - } +// espbuffsend adds data to the send buffer. If the previous send was completed it calls +// sendtxbuffer and espconn_sent. +// Returns ESPCONN_OK (0) for success, -128 if buffer is full or error from espconn_sent +// Use espbuffsend instead of espconn_sent as it solves the problem that espconn_sent must +// only be called *after* receiving an espconn_sent_callback for the previous packet. +static sint8 ICACHE_FLASH_ATTR +espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) +{ + if (conn->txbufferlen >= MAX_TXBUFFER) goto overflow; + + // make sure we indeed have a buffer + if (conn->txbuffer == NULL) conn->txbuffer = os_zalloc(MAX_TXBUFFER); + if (conn->txbuffer == NULL) { + os_printf("espbuffsend: cannot alloc tx buffer\n"); + return -128; + } + + // add to send buffer + uint16_t avail = conn->txbufferlen+len > MAX_TXBUFFER ? MAX_TXBUFFER-conn->txbufferlen : len; + os_memcpy(conn->txbuffer + conn->txbufferlen, data, avail); + conn->txbufferlen += avail; + + // try to send + sint8 result = ESPCONN_OK; + if (conn->readytosend) result = sendtxbuffer(conn); + + if (avail < len) { + // some data didn't fit into the buffer + if (conn->txbufferlen == 0) { + // we sent the prior buffer, so try again + return espbuffsend(conn, data+avail, len-avail); + } + goto overflow; + } + return result; + +overflow: + if (conn->txoverflow_at) { + // we've already been overflowing + if (system_get_time() - conn->txoverflow_at > 10*1000*1000) { + // no progress in 10 seconds, kill the connection + os_printf("serbridge: killing overlowing stuck conn %p\n", conn); + espconn_disconnect(conn->conn); + } + // else be silent, we already printed an error + } else { + // print 1-time message and take timestamp + os_printf("serbridge: txbuffer full, conn %p\n", conn); + conn->txoverflow_at = system_get_time(); + } + return -128; } -// New connection callback, use one of the connection descriptors, if we have one left. -static void ICACHE_FLASH_ATTR serbridgeConnectCb(void *arg) { - struct espconn *conn = arg; - //Find empty conndata in pool - int i; - for (i=0; ireverse; + os_printf("Sent CB %p\n", conn); + if (conn == NULL) return; + //os_printf("%d ST\n", system_get_time()); + if (conn->sentbuffer != NULL) os_free(conn->sentbuffer); + conn->sentbuffer = NULL; + conn->readytosend = true; + conn->txoverflow_at = 0; + sendtxbuffer(conn); // send possible new data in txbuffer +} + +void ICACHE_FLASH_ATTR +console_process(char *buf, short len) +{ + // push buffer into web-console + for (short i=0; i 0) { + //os_printf("SLIP: disabled got %d\n", length); + console_process(buf, length); + } else { + slip_parse_buf(buf, length); + } + + serledFlash(50); // short blink on serial LED +} + +//===== Connect / disconnect + +// Disconnection callback +static void ICACHE_FLASH_ATTR +serbridgeDisconCb(void *arg) +{ + serbridgeConnData *conn = ((struct espconn*)arg)->reverse; + if (conn == NULL) return; + // Free buffers + if (conn->sentbuffer != NULL) os_free(conn->sentbuffer); + conn->sentbuffer = NULL; + if (conn->txbuffer != NULL) os_free(conn->txbuffer); + conn->txbuffer = NULL; + conn->txbufferlen = 0; + // Send reset to attached uC if it was in programming mode + if (conn->conn_mode == cmPGM && mcu_reset_pin >= 0) { + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); + os_delay_us(100L); + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + } + conn->conn = NULL; +} + +// Connection reset callback (note that there will be no DisconCb) +static void ICACHE_FLASH_ATTR +serbridgeResetCb(void *arg, sint8 err) +{ + os_printf("serbridge: connection reset err=%d\n", err); + serbridgeDisconCb(arg); +} + +// New connection callback, use one of the connection descriptors, if we have one left. +static void ICACHE_FLASH_ATTR +serbridgeConnectCb(void *arg) +{ + struct espconn *conn = arg; + // Find empty conndata in pool + int i; + for (i=0; ireverse = connData+i; + connData[i].readytosend = true; + connData[i].conn_mode = cmInit; + // if it's the second port we start out in programming mode + if (conn->proto.tcp->local_port == serbridgeConn2.proto.tcp->local_port) + connData[i].conn_mode = cmPGMInit; + + espconn_regist_recvcb(conn, serbridgeRecvCb); + espconn_regist_disconcb(conn, serbridgeDisconCb); + espconn_regist_reconcb(conn, serbridgeResetCb); + espconn_regist_sentcb(conn, serbridgeSentCb); + + espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY); } -void ICACHE_FLASH_ATTR serbridgeInitPins() { - mcu_reset_pin = flashConfig.reset_pin; - mcu_isp_pin = flashConfig.isp_pin; - os_printf("Serbridge pins: reset=%d isp=%d\n", mcu_reset_pin, mcu_isp_pin); - - // set both pins to 1 before turning them on so we don't cause a reset - if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); - // switch pin mux to make these pins GPIO pins - if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin); - if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin); +//===== Initialization + +void ICACHE_FLASH_ATTR +serbridgeInitPins() +{ + mcu_reset_pin = flashConfig.reset_pin; + mcu_isp_pin = flashConfig.isp_pin; +#ifdef SERBR_DBG + os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n", + mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart); +#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); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U); + system_uart_swap(); + } else { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0); + system_uart_de_swap(); + } + + // set both pins to 1 before turning them on so we don't cause a reset + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); + // switch pin mux to make these pins GPIO pins + if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin); + if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin); } // Start transparent serial bridge TCP server on specified port (typ. 23) -void ICACHE_FLASH_ATTR serbridgeInit(int port) { - serbridgeInitPins(); - - int i; - for (i = 0; i < MAX_CONN; i++) { - connData[i].conn = NULL; - connData[i].txbuffer = txbuffer[i]; - } - serbridgeConn.type = ESPCONN_TCP; - serbridgeConn.state = ESPCONN_NONE; - serbridgeTcp.local_port = port; - serbridgeConn.proto.tcp = &serbridgeTcp; - - espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb); - espconn_accept(&serbridgeConn); - espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN); - espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0); +void ICACHE_FLASH_ATTR +serbridgeInit(int port1, int port2) +{ + serbridgeInitPins(); + + os_memset(connData, 0, sizeof(connData)); + os_memset(&serbridgeTcp1, 0, sizeof(serbridgeTcp1)); + os_memset(&serbridgeTcp2, 0, sizeof(serbridgeTcp2)); + + // set-up the primary port for plain bridging + serbridgeConn1.type = ESPCONN_TCP; + serbridgeConn1.state = ESPCONN_NONE; + serbridgeTcp1.local_port = port1; + serbridgeConn1.proto.tcp = &serbridgeTcp1; + + espconn_regist_connectcb(&serbridgeConn1, serbridgeConnectCb); + espconn_accept(&serbridgeConn1); + espconn_tcp_set_max_con_allow(&serbridgeConn1, MAX_CONN); + espconn_regist_time(&serbridgeConn1, SER_BRIDGE_TIMEOUT, 0); + + // set-up the secondary port for programming + serbridgeConn2.type = ESPCONN_TCP; + serbridgeConn2.state = ESPCONN_NONE; + serbridgeTcp2.local_port = port2; + serbridgeConn2.proto.tcp = &serbridgeTcp2; + + espconn_regist_connectcb(&serbridgeConn2, serbridgeConnectCb); + espconn_accept(&serbridgeConn2); + espconn_tcp_set_max_con_allow(&serbridgeConn2, MAX_CONN); + espconn_regist_time(&serbridgeConn2, SER_BRIDGE_TIMEOUT, 0); } diff --git a/serial/serbridge.h b/serial/serbridge.h index 364f833..c389961 100644 --- a/serial/serbridge.h +++ b/serial/serbridge.h @@ -6,35 +6,34 @@ #include #define MAX_CONN 4 -#define SER_BRIDGE_TIMEOUT 28799 +#define SER_BRIDGE_TIMEOUT 300 // 300 seconds = 5 minutes -//Max send buffer len -#define MAX_TXBUFFER 1024 - -typedef struct serbridgeConnData serbridgeConnData; +// Send buffer size +#define MAX_TXBUFFER (2*1460) enum connModes { - cmInit = 0, // initialization mode: nothing received yet - cmTransparent, // transparent mode - cmAVR, // Arduino/AVR programming mode - cmARM, // ARM (LPC8xx) programming - cmEcho, // simply echo characters (used for debugging latency) - cmCommand, // AT command mode + cmInit = 0, // initialization mode: nothing received yet + cmPGMInit, // initialization mode for programming + cmTransparent, // transparent mode + cmPGM, // Arduino/AVR/ARM programming mode cmTelnet, // use telnet escape sequences for programming mode }; -struct serbridgeConnData { +typedef struct serbridgeConnData { struct espconn *conn; - enum connModes conn_mode; // connection mode - char *txbuffer; // buffer for the data to send - uint16 txbufferlen; // length of data in txbuffer - bool readytosend; // true, if txbuffer can send by espconn_sent + enum connModes conn_mode; // connection mode uint8_t telnet_state; -}; - -void ICACHE_FLASH_ATTR serbridgeInit(int port); + uint16 txbufferlen; // length of data in txbuffer + char *txbuffer; // buffer for the data to send + char *sentbuffer; // buffer sent, awaiting callback to get freed + uint32_t txoverflow_at; // when the transmitter started to overflow + bool readytosend; // true, if txbuffer can be sent by espconn_sent +} serbridgeConnData; + +// port1 is transparent&programming, second port is programming only +void ICACHE_FLASH_ATTR serbridgeInit(int port1, int port2); void ICACHE_FLASH_ATTR serbridgeInitPins(void); -void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, int len); +void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len); void ICACHE_FLASH_ATTR serbridgeReset(); #endif /* __SER_BRIDGE_H__ */ diff --git a/serial/serled.c b/serial/serled.c index a87896b..9a7a66f 100644 --- a/serial/serled.c +++ b/serial/serled.c @@ -35,7 +35,9 @@ void ICACHE_FLASH_ATTR serledInit(void) { gpio_output_set(0, 0, (1< 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); + +#ifdef SLIP_DBG + for (short i=0; i= ' ' && slip_buf[i] <= '~') + os_printf("%c", slip_buf[i]); + else + os_printf("\\%02X", slip_buf[i]); + } + os_printf("\n"); +#endif + } + } + } +} + +#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_escaped = false; + slip_len = 0; +} + +// 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; +#ifdef SLIP_DBG + os_printf("SLIP: start\n"); +#endif + return; + } + } else if (slip_escaped) { + // prev char was SLIP_REPL + c = SLIP_ESC(c); + slip_escaped = false; + } 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 0) { + slip_process(); + slip_reset(); + } +} + diff --git a/serial/slip.h b/serial/slip.h new file mode 100644 index 0000000..a78011e --- /dev/null +++ b/serial/slip.h @@ -0,0 +1,6 @@ +#ifndef SLIP_H +#define SLIP_H + +void slip_parse_buf(char *buf, short length); + +#endif diff --git a/serial/uart.c b/serial/uart.c index 57761cf..9b517c4 100644 --- a/serial/uart.c +++ b/serial/uart.c @@ -17,13 +17,16 @@ * ---------------------------------------------------------------------------- * Heavily modified and enhanced by Thorsten von Eicken in 2015 */ -#include "espmissingincludes.h" -#include "ets_sys.h" -#include "osapi.h" -#include "user_interface.h" +#include "esp8266.h" #include "uart.h" -#define recvTaskPrio 0 +#ifdef UART_DBG +#define DBG_UART(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG_UART(format, ...) do { } while(0) +#endif + +#define recvTaskPrio 1 #define recvTaskQueueLen 64 // UartDev is defined and initialized in rom code. @@ -206,7 +209,7 @@ uart0_rx_intr_handler(void *para) if (UART_RXFIFO_FULL_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_FULL_INT_ST) || UART_RXFIFO_TOUT_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_TOUT_INT_ST)) { - //os_printf("stat:%02X",*(uint8 *)UART_INT_ENA(uart_no)); + //DBG_UART("stat:%02X",*(uint8 *)UART_INT_ENA(uart_no)); ETS_UART_INTR_DISABLE(); system_os_post(recvTaskPrio, 0, 0); } @@ -229,7 +232,7 @@ uart_recvTask(os_event_t *events) (length < 128)) { buf[length++] = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; } - //os_printf("%d ix %d\n", system_get_time(), length); + //DBG_UART("%d ix %d\n", system_get_time(), length); for (int i=0; i wrote this file. As long as you retain - * this notice you can do whatever you want with this stuff. If we meet some day, - * and you think this stuff is worth it, you can buy me a beer in return. - * ---------------------------------------------------------------------------- - * Heavily modified and enhanced by Thorsten von Eicken in 2015 - * ---------------------------------------------------------------------------- - */ - - -#include -#include "cgi.h" -#include "espfs.h" - -void ICACHE_FLASH_ATTR -jsonHeader(HttpdConnData *connData, int code) { - httpdStartResponse(connData, code); - httpdHeader(connData, "Cache-Control", "no-cache, no-store, must-revalidate"); - httpdHeader(connData, "Pragma", "no-cache"); - httpdHeader(connData, "Expires", "0"); - httpdHeader(connData, "Content-Type", "application/json"); - httpdEndHeaders(connData); -} - -#define TOKEN(x) (os_strcmp(token, x) == 0) -#if 0 -// Handle system information variables and print their value, returns the number of -// characters appended to buff -int ICACHE_FLASH_ATTR printGlobalInfo(char *buff, int buflen, char *token) { - if (TOKEN("si_chip_id")) { - return os_sprintf(buff, "0x%x", system_get_chip_id()); - } else if (TOKEN("si_freeheap")) { - return os_sprintf(buff, "%dKB", system_get_free_heap_size()/1024); - } else if (TOKEN("si_uptime")) { - uint32 t = system_get_time() / 1000000; // in seconds - return os_sprintf(buff, "%dd%dh%dm%ds", t/(24*3600), (t/(3600))%24, (t/60)%60, t%60); - } else if (TOKEN("si_boot_version")) { - return os_sprintf(buff, "%d", system_get_boot_version()); - } else if (TOKEN("si_boot_address")) { - return os_sprintf(buff, "0x%x", system_get_userbin_addr()); - } else if (TOKEN("si_cpu_freq")) { - return os_sprintf(buff, "%dMhz", system_get_cpu_freq()); - } else { - return 0; - } -} -#endif - -extern char *esp_link_version; // in user_main.c - -int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - char buff[1024]; - // don't use jsonHeader so the response does get cached - httpdStartResponse(connData, 200); - httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); - httpdHeader(connData, "Content-Type", "application/json"); - httpdEndHeaders(connData); - // construct json response - os_sprintf(buff, - "{\"menu\": [\"Home\", \"/home.html\", \"Wifi\", \"/wifi/wifi.html\"," - "\"\xC2\xB5" "C Console\", \"/console.html\", \"Debug log\", \"/log.html\" ],\n" - " \"version\": \"%s\" }", esp_link_version); - httpdSend(connData, buff, -1); - return HTTPD_CGI_DONE; -} diff --git a/user/cgi.h b/user/cgi.h deleted file mode 100644 index b5690cf..0000000 --- a/user/cgi.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef CGI_H -#define CGI_H - -#include "httpd.h" - -void jsonHeader(HttpdConnData *connData, int code); -int cgiMenu(HttpdConnData *connData); - -#endif diff --git a/user/cgiwifi.c b/user/cgiwifi.c deleted file mode 100644 index 0d96e96..0000000 --- a/user/cgiwifi.c +++ /dev/null @@ -1,577 +0,0 @@ -/* -Cgi/template routines for the /wifi url. -*/ - -/* - * ---------------------------------------------------------------------------- - * "THE BEER-WARE LICENSE" (Revision 42): - * Jeroen Domburg wrote this file. As long as you retain - * this notice you can do whatever you want with this stuff. If we meet some day, - * and you think this stuff is worth it, you can buy me a beer in return. - * ---------------------------------------------------------------------------- - * Heavily modified and enhanced by Thorsten von Eicken in 2015 - * ---------------------------------------------------------------------------- - */ - - -#include -#include "cgiwifi.h" -#include "cgi.h" -#include "status.h" -#include "config.h" -#include "log.h" - -//#define SLEEP_MODE LIGHT_SLEEP_T -#define SLEEP_MODE MODEM_SLEEP_T - -// ===== wifi status change callback - -uint8_t wifiState = wifiIsDisconnected; -// reasons for which a connection failed -uint8_t wifiReason = 0; -static char *wifiReasons[] = { - "", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", - "not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", - "ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", - "ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", - "unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", - "beacon_timeout", "no_ap_found" }; - -static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; -static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; - -static char* ICACHE_FLASH_ATTR wifiGetReason(void) { - if (wifiReason <= 24) return wifiReasons[wifiReason]; - if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; - return wifiReasons[1]; -} - -// handler for wifi status change callback coming in from espressif library -static void ICACHE_FLASH_ATTR wifiHandleEventCb(System_Event_t *evt) { - switch (evt->event) { - case EVENT_STAMODE_CONNECTED: - wifiState = wifiIsConnected; - wifiReason = 0; - os_printf("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, - evt->event_info.connected.channel); - statusWifiUpdate(wifiState); - break; - case EVENT_STAMODE_DISCONNECTED: - wifiState = wifiIsDisconnected; - wifiReason = evt->event_info.disconnected.reason; - os_printf("Wifi disconnected from ssid %s, reason %s (%d)\n", - evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); - statusWifiUpdate(wifiState); - break; - case EVENT_STAMODE_AUTHMODE_CHANGE: - os_printf("Wifi auth mode: %d -> %d\n", - evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); - break; - case EVENT_STAMODE_GOT_IP: - wifiState = wifiGotIP; - wifiReason = 0; - os_printf("Wifi got ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR "\n", - IP2STR(&evt->event_info.got_ip.ip), IP2STR(&evt->event_info.got_ip.mask), - IP2STR(&evt->event_info.got_ip.gw)); - statusWifiUpdate(wifiState); - break; - case EVENT_SOFTAPMODE_STACONNECTED: - os_printf("Wifi AP: station " MACSTR " joined, AID = %d\n", - MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); - break; - case EVENT_SOFTAPMODE_STADISCONNECTED: - os_printf("Wifi AP: station " MACSTR " left, AID = %d\n", - MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); - break; - default: - break; - } -} - -// ===== wifi scanning - -//WiFi access point data -typedef struct { - char ssid[32]; - sint8 rssi; - char enc; -} ApData; - -//Scan result -typedef struct { - char scanInProgress; //if 1, don't access the underlying stuff from the webpage. - ApData **apData; - int noAps; -} ScanResultData; - -//Static scan status storage. -static ScanResultData cgiWifiAps; - -//Callback the code calls when a wlan ap scan is done. Basically stores the result in -//the cgiWifiAps struct. -void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { - int n; - struct bss_info *bss_link = (struct bss_info *)arg; - - if (status!=OK) { - os_printf("wifiScanDoneCb status=%d\n", status); - cgiWifiAps.scanInProgress=0; - return; - } - - //Clear prev ap data if needed. - if (cgiWifiAps.apData!=NULL) { - for (n=0; nnext.stqe_next; - n++; - } - //Allocate memory for access point data - cgiWifiAps.apData=(ApData **)os_malloc(sizeof(ApData *)*n); - cgiWifiAps.noAps=n; - os_printf("Scan done: found %d APs\n", n); - - //Copy access point data to the static struct - n=0; - bss_link = (struct bss_info *)arg; - while (bss_link != NULL) { - if (n>=cgiWifiAps.noAps) { - //This means the bss_link changed under our nose. Shouldn't happen! - //Break because otherwise we will write in unallocated memory. - os_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); - break; - } - //Save the ap data. - cgiWifiAps.apData[n]=(ApData *)os_malloc(sizeof(ApData)); - cgiWifiAps.apData[n]->rssi=bss_link->rssi; - cgiWifiAps.apData[n]->enc=bss_link->authmode; - strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); - os_printf("bss%d: %s (%d)\n", n+1, (char*)bss_link->ssid, bss_link->rssi); - - bss_link = bss_link->next.stqe_next; - n++; - } - //We're done. - cgiWifiAps.scanInProgress=0; -} - -static ETSTimer scanTimer; -static void ICACHE_FLASH_ATTR scanStartCb(void *arg) { - os_printf("Starting a scan\n"); - wifi_station_scan(NULL, wifiScanDoneCb); -} - -static int ICACHE_FLASH_ATTR cgiWiFiStartScan(HttpdConnData *connData) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - jsonHeader(connData, 200); - if (!cgiWifiAps.scanInProgress) { - cgiWifiAps.scanInProgress = 1; - os_timer_disarm(&scanTimer); - os_timer_setfn(&scanTimer, scanStartCb, NULL); - os_timer_arm(&scanTimer, 1000, 0); - } - return HTTPD_CGI_DONE; -} - -static int ICACHE_FLASH_ATTR cgiWiFiGetScan(HttpdConnData *connData) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - char buff[2048]; - int len; - - jsonHeader(connData, 200); - - if (cgiWifiAps.scanInProgress==1) { - //We're still scanning. Tell Javascript code that. - len = os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n"); - httpdSend(connData, buff, len); - return HTTPD_CGI_DONE; - } - - len = os_sprintf(buff, "{\"result\": {\"inProgress\": \"0\", \"APs\": [\n"); - for (int pos=0; posssid, cgiWifiAps.apData[pos]->rssi, - cgiWifiAps.apData[pos]->enc, (pos==cgiWifiAps.noAps-1)?"":","); - } - len += os_sprintf(buff+len, "]}}\n"); - //os_printf("Sending %d bytes: %s\n", len, buff); - httpdSend(connData, buff, len); - return HTTPD_CGI_DONE; -} - -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; - } -} - -// ===== timers to change state and rescue from failed associations - -// reset timer changes back to STA+AP if we can't associate -#define RESET_TIMEOUT (15000) // 15 seconds -static ETSTimer resetTimer; - -// This routine is ran some time after a connection attempt to an access point. If -// the connect succeeds, this gets the module in STA-only mode. If it fails, it ensures -// that the module is in STA+AP mode so the user has a chance to recover. -static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { - int x = wifi_station_get_connect_status(); - int m = wifi_get_opmode() & 0x3; - os_printf("Wifi check: mode=%s status=%d\n", wifiMode[m], x); - - if (x == STATION_GOT_IP) { - if (m != 1) { -#ifdef CHANGE_TO_STA - // We're happily connected, go to STA mode - os_printf("Wifi got IP. Going into STA mode..\n"); - wifi_set_opmode(1); - wifi_set_sleep_type(SLEEP_MODE); - os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); -#endif - } - log_uart(false); - // no more resetTimer at this point, gotta use physical reset to recover if in trouble - } else { - if (m != 3) { - os_printf("Wifi connect failed. Going into STA+AP mode..\n"); - wifi_set_opmode(3); - } - log_uart(true); - os_printf("Enabling/continuing uart log\n"); - 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; - -// Callback actually doing reassociation -static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { - os_printf("Wifi changing association\n"); - wifi_station_disconnect(); - stconf.bssid_set = 0; - wifi_station_set_config(&stconf); - wifi_station_connect(); - // Schedule check - os_timer_disarm(&resetTimer); - os_timer_setfn(&resetTimer, resetTimerCb, NULL); - os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); -} - -// 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) { - char essid[128]; - char passwd[128]; - - if (connData->conn==NULL) return HTTPD_CGI_DONE; - - int el = httpdFindArg(connData->getArgs, "essid", essid, sizeof(essid)); - int pl = httpdFindArg(connData->getArgs, "passwd", passwd, sizeof(passwd)); - - if (el > 0 && pl >= 0) { - //Set to 0 if you want to disable the actual reconnecting bit - os_strncpy((char*)stconf.ssid, essid, 32); - os_strncpy((char*)stconf.password, passwd, 64); - os_printf("Wifi try to connect to AP %s pw %s\n", essid, passwd); - - //Schedule disconnect/connect - os_timer_disarm(&reassTimer); - os_timer_setfn(&reassTimer, reassTimerCb, NULL); - os_timer_arm(&reassTimer, 1000, 0); - jsonHeader(connData, 200); - } else { - jsonHeader(connData, 400); - httpdSend(connData, "Cannot parse ssid or password", -1); - } - return HTTPD_CGI_DONE; -} - -static bool parse_ip(char *buff, ip_addr_t *ip_ptr) { - char *next = buff; // where to start parsing next integer - int found = 0; // number of integers parsed - uint32_t ip = 0; // the ip addres parsed - for (int i=0; i<32; i++) { // 32 is just a safety limit - char c = buff[i]; - if (c == '.' || c == 0) { - // parse the preceding integer and accumulate into IP address - bool last = c == 0; - buff[i] = 0; - uint32_t v = atoi(next); - ip = ip | ((v&0xff)<<(found*8)); - next = buff+i+1; // next integer starts after the '.' - found++; - if (last) { // if at end of string we better got 4 integers - ip_ptr->addr = ip; - return found == 4; - } - continue; - } - if (c < '0' || c > '9') return false; - } - return false; -} - -#define DEBUGIP -#ifdef DEBUGIP -static void ICACHE_FLASH_ATTR debugIP() { - struct ip_info info; - if (wifi_get_ip_info(0, &info)) { - os_printf("\"ip\": \"%d.%d.%d.%d\"\n", IP2STR(&info.ip.addr)); - os_printf("\"netmask\": \"%d.%d.%d.%d\"\n", IP2STR(&info.netmask.addr)); - os_printf("\"gateway\": \"%d.%d.%d.%d\"\n", IP2STR(&info.gw.addr)); - os_printf("\"hostname\": \"%s\"\n", wifi_station_get_hostname()); - } else { - os_printf("\"ip\": \"-none-\"\n"); - } -} -#endif - -// configure Wifi, specifically DHCP vs static IP address based on flash config -static void ICACHE_FLASH_ATTR configWifiIP() { - if (flashConfig.staticip == 0) { - // let's DHCP! - wifi_station_set_hostname(flashConfig.hostname); - if (wifi_station_dhcpc_status() == DHCP_STARTED) - wifi_station_dhcpc_stop(); - wifi_station_dhcpc_start(); - os_printf("Wifi uses DHCP, hostname=%s\n", flashConfig.hostname); - } else { - // no DHCP, we got static network config! - wifi_station_dhcpc_stop(); - struct ip_info ipi; - ipi.ip.addr = flashConfig.staticip; - ipi.netmask.addr = flashConfig.netmask; - ipi.gw.addr = flashConfig.gateway; - wifi_set_ip_info(0, &ipi); - os_printf("Wifi uses static IP %d.%d.%d.%d\n", IP2STR(&ipi.ip.addr)); - } -#ifdef DEBUGIP - debugIP(); -#endif -} - -// Change special settings -int ICACHE_FLASH_ATTR cgiWiFiSpecial(HttpdConnData *connData) { - char dhcp[8]; - char hostname[32]; - char staticip[20]; - char netmask[20]; - char gateway[20]; - - if (connData->conn==NULL) return HTTPD_CGI_DONE; - - // get args and their string lengths - int dl = httpdFindArg(connData->getArgs, "dhcp", dhcp, sizeof(dhcp)); - int hl = httpdFindArg(connData->getArgs, "hostname", hostname, sizeof(hostname)); - int sl = httpdFindArg(connData->getArgs, "staticip", staticip, sizeof(staticip)); - int nl = httpdFindArg(connData->getArgs, "netmask", netmask, sizeof(netmask)); - int gl = httpdFindArg(connData->getArgs, "gateway", gateway, sizeof(gateway)); - - if (!(dl > 0 && hl >= 0 && sl >= 0 && nl >= 0 && gl >= 0)) { - jsonHeader(connData, 400); - httpdSend(connData, "Request is missing fields", -1); - return HTTPD_CGI_DONE; - } - - char url[64]; // redirect URL - if (os_strcmp(dhcp, "off") == 0) { - // parse static IP params - struct ip_info ipi; - bool ok = parse_ip(staticip, &ipi.ip); - if (nl > 0) ok = ok && parse_ip(netmask, &ipi.netmask); - else IP4_ADDR(&ipi.netmask, 255, 255, 255, 0); - if (gl > 0) ok = ok && parse_ip(gateway, &ipi.gw); - else ipi.gw.addr = 0; - if (!ok) { - jsonHeader(connData, 400); - httpdSend(connData, "Cannot parse static IP config", -1); - return HTTPD_CGI_DONE; - } - // save the params in flash - flashConfig.staticip = ipi.ip.addr; - flashConfig.netmask = ipi.netmask.addr; - flashConfig.gateway = ipi.gw.addr; - // construct redirect URL - os_sprintf(url, "{\"url\": \"http://%d.%d.%d.%d\"}", IP2STR(&ipi.ip)); - - } else { - // no static IP, set hostname - if (hl == 0) os_strcpy(hostname, "esp-link"); - flashConfig.staticip = 0; - os_strcpy(flashConfig.hostname, hostname); - os_sprintf(url, "{\"url\": \"http://%s\"}", hostname); - } - - configSave(); // ignore error... - // schedule change-over - os_timer_disarm(&reassTimer); - os_timer_setfn(&reassTimer, configWifiIP, NULL); - os_timer_arm(&reassTimer, 1000, 0); - // return redirect info - jsonHeader(connData, 200); - httpdSend(connData, url, -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]; - - 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); - os_printf("Wifi switching to mode %d\n", m); - wifi_set_opmode(m&3); - if (m == 1) { - wifi_set_sleep_type(SLEEP_MODE); - // STA-only mode, reset into STA+AP after a timeout - os_timer_disarm(&resetTimer); - os_timer_setfn(&resetTimer, resetTimerCb, NULL); - os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); - } - jsonHeader(connData, 200); - } else { - jsonHeader(connData, 400); - } - return HTTPD_CGI_DONE; -} - -static char *connStatuses[] = { "idle", "connecting", "wrong password", "AP not found", - "failed", "got IP address" }; - -static char *wifiWarn[] = { 0, - "Switch to STA+AP mode", - "Can't scan in this mode! Switch to STA+AP mode", - "Switch to STA mode", -}; - -#ifdef CHANGE_TO_STA -#define MODECHANGE "yes" -#else -#define MODECHANGE "no" -#endif - -// 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); - - 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; -} - -int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { - char buff[1024]; - int len; - - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - jsonHeader(connData, 200); - - len = os_sprintf(buff, "{"); - len += printWifiInfo(buff+len); - len += os_sprintf(buff+len, ", "); - - if (wifiReason != 0) { - len += os_sprintf(buff+len, "\"reason\": \"%s\", ", wifiGetReason()); - } - -#if 0 - // commented out 'cause often the client that requested the change can't get a request in to - // find out that it succeeded. Better to just wait the std 15 seconds... - int st=wifi_station_get_connect_status(); - if (st == STATION_GOT_IP) { - if (wifi_get_opmode() != 1) { - // Reset into AP-only mode sooner. - os_timer_disarm(&resetTimer); - os_timer_setfn(&resetTimer, resetTimerCb, NULL); - os_timer_arm(&resetTimer, 1000, 0); - } - } -#endif - - len += os_sprintf(buff+len, "\"x\":0}\n"); - - os_printf(" -> %s\n", buff); - httpdSend(connData, buff, len); - return HTTPD_CGI_DONE; -} - -// Cgi to return various Wifi information -int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) { - char buff[1024]; - - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - - os_strcpy(buff, "{"); - printWifiInfo(buff+1); - os_strcat(buff, "}"); - - jsonHeader(connData, 200); - httpdSend(connData, buff, -1); - 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); - int x = wifi_get_opmode() & 0x3; - os_printf("Wifi init, mode=%s\n", wifiMode[x]); - configWifiIP(); - - wifi_set_event_handler_cb(wifiHandleEventCb); - // check on the wifi in a few seconds to see whether we need to switch mode - os_timer_disarm(&resetTimer); - os_timer_setfn(&resetTimer, resetTimerCb, NULL); - os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); -} - diff --git a/user/config.c b/user/config.c deleted file mode 100644 index b840c8f..0000000 --- a/user/config.c +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt -/* Configuration stored in flash */ - -#include -#include -#include "config.h" -#include "espfs.h" - -// hack: this from LwIP -extern uint16_t inet_chksum(void *dataptr, uint16_t len); - -FlashConfig flashConfig; -FlashConfig flashDefault = { - 33, 0, 0, - MCU_RESET_PIN, MCU_ISP_PIN, LED_CONN_PIN, LED_SERIAL_PIN, - 115200, - "esp-link\0 ", // hostname - 0, 0x00ffffff, 0, // static ip, netmask, gateway - 0, // log mode -}; - -typedef union { - FlashConfig fc; - uint8_t block[128]; -} FlashFull; - -#define FLASH_MAGIC (0xaa55) - -#define FLASH_ADDR (0x3E000) -#define FLASH_SECT (4096) -static int flash_pri; // primary flash sector (0 or 1, or -1 for error) - -#if 0 -static void memDump(void *addr, int len) { - for (int i=0; i>12) != SPI_FLASH_RESULT_OK) - return false; // no harm done, give up - // calculate CRC - ff.fc.seq = seq; - ff.fc.magic = FLASH_MAGIC; - ff.fc.crc = 0; - //os_printf("cksum of: "); - //memDump(&ff, sizeof(ff)); - ff.fc.crc = inet_chksum(&ff, sizeof(ff)); - //os_printf("cksum is %04x\n", ff.fc.crc); - // write primary with incorrect seq - ff.fc.seq = 0xffffffff; - if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) - return false; // no harm done, give up - // fill in correct seq - ff.fc.seq = seq; - if (spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)) != SPI_FLASH_RESULT_OK) - return false; // most likely failed, but no harm if successful - // now that we have safely written the new version, erase old primary - addr = FLASH_ADDR + flash_pri*FLASH_SECT; - flash_pri = 1-flash_pri; - if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK) - return true; // no back-up but we're OK - // write secondary - ff.fc.seq = 0xffffffff; - if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) - return true; // no back-up but we're OK - ff.fc.seq = seq; - spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)); - return true; -} - -void ICACHE_FLASH_ATTR configWipe(void) { - spi_flash_erase_sector(FLASH_ADDR>>12); - spi_flash_erase_sector((FLASH_ADDR+FLASH_SECT)>>12); -} - -static uint32_t ICACHE_FLASH_ATTR selectPrimary(FlashFull *fc0, FlashFull *fc1); - -bool ICACHE_FLASH_ATTR configRestore(void) { - FlashFull ff0, ff1; - // read both flash sectors - if (spi_flash_read(FLASH_ADDR, (void *)&ff0, sizeof(ff0)) != SPI_FLASH_RESULT_OK) - memset(&ff0, 0, sizeof(ff0)); // clear in case of error - if (spi_flash_read(FLASH_ADDR+FLASH_SECT, (void *)&ff1, sizeof(ff1)) != SPI_FLASH_RESULT_OK) - memset(&ff1, 0, sizeof(ff1)); // clear in case of error - // figure out which one is good - flash_pri = selectPrimary(&ff0, &ff1); - // if neither is OK, we revert to defaults - if (flash_pri < 0) { - memcpy(&flashConfig, &flashDefault, sizeof(FlashConfig)); - flash_pri = 0; - return false; - } - // copy good one into global var and return - memcpy(&flashConfig, flash_pri == 0 ? &ff0.fc : &ff1.fc, sizeof(FlashConfig)); - return true; -} - -static uint32_t ICACHE_FLASH_ATTR selectPrimary(FlashFull *ff0, FlashFull *ff1) { - // check CRC of ff0 - uint16_t crc = ff0->fc.crc; - ff0->fc.crc = 0; - bool ff0_crc_ok = inet_chksum(ff0, sizeof(FlashFull)) == crc; - - // check CRC of ff1 - crc = ff1->fc.crc; - ff1->fc.crc = 0; - bool ff1_crc_ok = inet_chksum(ff1, sizeof(FlashFull)) == crc; - - // decided which we like better - if (ff0_crc_ok) - if (!ff1_crc_ok || ff0->fc.seq >= ff1->fc.seq) - return 0; // use first sector as primary - else - return 1; // second sector is newer - else - return ff1_crc_ok ? 1 : -1; -} diff --git a/user/config.h b/user/config.h deleted file mode 100644 index c326a37..0000000 --- a/user/config.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -typedef struct { - uint32_t seq; // flash write sequence number - uint16_t magic, crc; - int8_t reset_pin, isp_pin, conn_led_pin, ser_led_pin; - int32_t baud_rate; - char hostname[32]; // if using DHCP - uint32_t staticip, netmask, gateway; // using DHCP if staticip==0 - uint8_t log_mode; // UART log debug mode -} FlashConfig; -extern FlashConfig flashConfig; - -bool configSave(void); -bool configRestore(void); -void configWipe(void); - -#endif diff --git a/user/log.c b/user/log.c deleted file mode 100644 index 9103a41..0000000 --- a/user/log.c +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt - -#include -#include "uart.h" -#include "cgi.h" -#include "config.h" -#include "log.h" - -// Web log for the esp8266 to replace outputting to uart1. -// The web log has a 1KB circular in-memory buffer which os_printf prints into and -// the HTTP handler simply displays the buffer content on a web page. - -// see console.c for invariants (same here) -#define BUF_MAX (1400) -static char log_buf[BUF_MAX]; -static int log_wr, log_rd; -static int log_pos; -static bool log_no_uart; // start out printing to uart -static bool log_newline; // at start of a new line - -// called from wifi reset timer to turn UART on when we loose wifi and back off -// when we connect to wifi AP. Here this is gated by the flash setting -void ICACHE_FLASH_ATTR -log_uart(bool enable) { - if (!enable && !log_no_uart && flashConfig.log_mode != LOG_MODE_ON) { - // we're asked to turn uart off, and uart is on, and the flash setting isn't always-on -#if 1 - os_printf("Turning OFF uart log\n"); - os_delay_us(4*1000L); // time for uart to flush - log_no_uart = !enable; -#endif - } else if (enable && log_no_uart && flashConfig.log_mode != LOG_MODE_OFF) { - // we're asked to turn uart on, and uart is off, and the flash setting isn't always-off - log_no_uart = !enable; - os_printf("Turning ON uart log\n"); - } -} - -static void ICACHE_FLASH_ATTR -log_write(char c) { - log_buf[log_wr] = c; - log_wr = (log_wr+1) % BUF_MAX; - if (log_wr == log_rd) { - log_rd = (log_rd+1) % BUF_MAX; // full, eat first char - log_pos++; - } -} - -#if 0 -static char ICACHE_FLASH_ATTR -log_read(void) { - char c = 0; - if (log_rd != log_wr) { - c = log_buf[log_rd]; - log_rd = (log_rd+1) % BUF_MAX; - } - return c; -} -#endif - -static void ICACHE_FLASH_ATTR -log_write_char(char c) { - // Uart output unless disabled - if (!log_no_uart) { - if (log_newline) { - char buff[16]; - int l = os_sprintf(buff, "%6d> ", (system_get_time()/1000)%1000000); - for (int i=0; iconn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - jsonHeader(connData, 200); - - // figure out where to start in buffer based on URI param - len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); - if (len > 0) { - start = atoi(buff); - if (start < log_pos) { - start = 0; - } else if (start >= log_pos+log_len) { - start = log_len; - } else { - start = start - log_pos; - } - } - - // start outputting - len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", - log_len-start, log_pos+start); - - int rd = (log_rd+start) % BUF_MAX; - while (len < 2040 && rd != log_wr) { - uint8_t c = log_buf[rd]; - if (c == '\\' || c == '"') { - buff[len++] = '\\'; - buff[len++] = c; - } else if (c < ' ') { - len += os_sprintf(buff+len, "\\u%04x", c); - } else { - buff[len++] = c; - } - rd = (rd + 1) % BUF_MAX; - } - os_strcpy(buff+len, "\"}"); len+=2; - httpdSend(connData, buff, len); - return HTTPD_CGI_DONE; -} - -static char *dbg_mode[] = { "auto", "off", "on" }; - -int ICACHE_FLASH_ATTR -ajaxLogDbg(HttpdConnData *connData) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - char buff[512]; - int len, status = 400; - len = httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); - if (len > 0) { - int8_t mode = -1; - if (os_strcmp(buff, "auto") == 0) mode = LOG_MODE_AUTO; - if (os_strcmp(buff, "off") == 0) mode = LOG_MODE_OFF; - if (os_strcmp(buff, "on") == 0) mode = LOG_MODE_ON; - if (mode >= 0) { - flashConfig.log_mode = mode; - if (mode != LOG_MODE_AUTO) log_uart(mode == LOG_MODE_ON); - status = configSave() ? 200 : 400; - } - } else if (connData->requestType == HTTPD_METHOD_GET) { - status = 200; - } - - jsonHeader(connData, status); - os_sprintf(buff, "{\"mode\": \"%s\"}", dbg_mode[flashConfig.log_mode]); - httpdSend(connData, buff, -1); - return HTTPD_CGI_DONE; -} - - -void ICACHE_FLASH_ATTR logInit() { - log_no_uart = flashConfig.log_mode == LOG_MODE_OFF; // ON unless set to always-off - log_wr = 0; - log_rd = 0; - os_install_putc1((void *)log_write_char); -} - - diff --git a/user/status.c b/user/status.c deleted file mode 100644 index 23f693f..0000000 --- a/user/status.c +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt - -#include -#include "config.h" -#include "serled.h" -#include "cgiwifi.h" - -static ETSTimer ledTimer; - -static void ICACHE_FLASH_ATTR setLed(int on) { - int8_t pin = flashConfig.conn_led_pin; - if (pin < 0) return; // disabled - // LED is active-low - if (on) { - gpio_output_set(0, (1<= 0) { - makeGpio(flashConfig.conn_led_pin); - setLed(1); - } - os_printf("CONN led=%d\n", flashConfig.conn_led_pin); - - os_timer_disarm(&ledTimer); - os_timer_setfn(&ledTimer, ledTimerCb, NULL); - os_timer_arm(&ledTimer, 2000, 0); -} - - diff --git a/user/user_main.c b/user/user_main.c index 645d215..5a4331d 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -1,162 +1,7 @@ -/* - * ---------------------------------------------------------------------------- - * "THE BEER-WARE LICENSE" (Revision 42): - * Jeroen Domburg wrote this file. As long as you retain - * this notice you can do whatever you want with this stuff. If we meet some day, - * and you think this stuff is worth it, you can buy me a beer in return. - * ---------------------------------------------------------------------------- - * Heavily modified and enhanced by Thorsten von Eicken in 2015 - * ---------------------------------------------------------------------------- - */ - - #include -#include "httpd.h" -#include "httpdespfs.h" -#include "cgi.h" -#include "cgiwifi.h" -#include "cgipins.h" -#include "cgiflash.h" -#include "auth.h" -#include "espfs.h" -#include "uart.h" -#include "serbridge.h" -#include "status.h" -#include "serled.h" -#include "console.h" -#include "config.h" -#include "log.h" -#include - -//#define SHOW_HEAP_USE - -//Function that tells the authentication system what users/passwords live on the system. -//This is disabled in the default build; if you want to try it, enable the authBasic line in -//the builtInUrls below. -int myPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen) { - if (no==0) { - os_strcpy(user, "admin"); - os_strcpy(pass, "s3cr3t"); - return 1; -//Add more users this way. Check against incrementing no for each user added. -// } else if (no==1) { -// os_strcpy(user, "user1"); -// os_strcpy(pass, "something"); -// return 1; - } - return 0; -} - - -/* -This is the main url->function dispatching data struct. -In short, it's a struct with various URLs plus their handlers. The handlers can -be 'standard' CGI functions you wrote, or 'special' CGIs requiring an argument. -They can also be auth-functions. An asterisk will match any url starting with -everything before the asterisks; "*" matches everything. The list will be -handled top-down, so make sure to put more specific rules above the more -general ones. Authorization things (like authBasic) act as a 'barrier' and -should be placed above the URLs they protect. -*/ -HttpdBuiltInUrl builtInUrls[]={ - {"/", cgiRedirect, "/home.html"}, - {"/menu", cgiMenu, NULL}, - {"/flash/next", cgiGetFirmwareNext, NULL}, - {"/flash/upload", cgiUploadFirmware, NULL}, - {"/flash/reboot", cgiRebootFirmware, NULL}, - //{"/home.html", cgiEspFsHtml, NULL}, - //{"/log.html", cgiEspFsHtml, NULL}, - {"/log/text", ajaxLog, NULL}, - {"/log/dbg", ajaxLogDbg, NULL}, - //{"/console.html", cgiEspFsHtml, NULL}, - {"/console/reset", ajaxConsoleReset, NULL}, - {"/console/baud", ajaxConsoleBaud, NULL}, - {"/console/text", ajaxConsole, NULL}, - - //Routines to make the /wifi URL and everything beneath it work. - -//Enable the line below to protect the WiFi configuration with an username/password combo. -// {"/wifi/*", authBasic, myPassFn}, - - {"/wifi", cgiRedirect, "/wifi/wifi.html"}, - {"/wifi/", cgiRedirect, "/wifi/wifi.html"}, - //{"/wifi/wifi.html", cgiEspFsHtml, NULL}, - {"/wifi/info", cgiWifiInfo, NULL}, - {"/wifi/scan", cgiWiFiScan, NULL}, - {"/wifi/connect", cgiWiFiConnect, NULL}, - {"/wifi/connstatus", cgiWiFiConnStatus, NULL}, - {"/wifi/setmode", cgiWiFiSetMode, NULL}, - {"/wifi/special", cgiWiFiSpecial, NULL}, - {"/pins", cgiPins, NULL}, - - {"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem - {NULL, NULL, NULL} -}; - - -//#define SHOW_HEAP_USE - -#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()); -} -#endif - -void user_rf_pre_init(void) { -} - -// address of espfs binary blob -extern uint32_t _binary_espfs_img_start; - -static char *rst_codes[] = { - "normal", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "???", -}; - -# define VERS_STR_STR(V) #V -# define VERS_STR(V) VERS_STR_STR(V) -char *esp_link_version = VERS_STR(VERSION); - -//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done. -void user_init(void) { - // get the flash config so we know how to init things - //configWipe(); // uncomment to reset the config for testing purposes - bool restoreOk = configRestore(); - // init gpio pin registers - gpio_init(); - // 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 - os_delay_us(10000L); - os_printf("\n\n** %s\n", esp_link_version); - os_printf("Flash config restore %s\n", restoreOk ? "ok" : "*FAILED*"); - // Status LEDs - statusInit(); - serledInit(); - // Wifi - wifiInit(); - // init the flash filesystem with the html stuff - EspFsInitResult res = espFsInit(&_binary_espfs_img_start); - os_printf("espFsInit %s\n", res?"ERR":"ok"); - // mount the http handlers - httpdInit(builtInUrls, 80); - // init the wifi-serial transparent bridge (port 23) - serbridgeInit(23); - uart_add_recv_cb(&serbridgeUartCb); -#ifdef SHOW_HEAP_USE - os_timer_disarm(&prHeapTimer); - os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL); - os_timer_arm(&prHeapTimer, 10000, 1); -#endif +#include - struct rst_info *rst_info = system_get_rst_info(); - os_printf("Reset cause: %d=%s\n", rst_info->reason, rst_codes[rst_info->reason]); - os_printf("exccause=%d epc1=0x%x epc2=0x%x epc3=0x%x excvaddr=0x%x depc=0x%x\n", - rst_info->exccause, rst_info->epc1, rst_info->epc2, rst_info->epc3, - rst_info->excvaddr, rst_info->depc); - os_printf("Flash map %d, chip %08X\n", system_get_flash_size_map(), spi_flash_get_id()); +// initialize the custom stuff that goes beyond esp-link +void app_init() { - os_printf("** esp-link ready\n"); }