Merge branch 'master' into rs485

pull/138/head
Alastair D'Silva 8 years ago committed by GitHub
commit acc7c90575
  1. 1
      .gitignore
  2. 31
      .travis.yml
  3. 112
      BUILDING.md
  4. 29
      CONTRIBUTING.md
  5. 31
      Dockerfile
  6. 204
      FLASHING.md
  7. 106
      Makefile
  8. 535
      README.adoc
  9. 175
      README.md
  10. 14
      RESTMQTT.md
  11. 36
      TROUBLESHOOTING.md
  12. 183
      UC-FLASHING.md
  13. 69
      WEB-SERVER.md
  14. 87
      WIFI-CONFIG.md
  15. 8
      cmd/cmd.c
  16. 19
      cmd/cmd.h
  17. 30
      cmd/handlers.c
  18. 11
      esp-link/cgi.c
  19. 19
      esp-link/cgimqtt.c
  20. 4
      esp-link/cgioptiboot.c
  21. 8
      esp-link/cgiservices.c
  22. 189
      esp-link/cgiwebserversetup.c
  23. 8
      esp-link/cgiwebserversetup.h
  24. 29
      esp-link/cgiwifi.c
  25. 61
      esp-link/config.c
  26. 9
      esp-link/config.h
  27. 53
      esp-link/main.c
  28. 9
      esp-link/mqtt_client.c
  29. 2
      esp-link/task.h
  30. 202
      espfs/espfs.c
  31. 27
      espfs/espfs.h
  32. 3
      espfs/mkespfsimage/Makefile
  33. 31
      html/console.html
  34. 43
      html/flash.html
  35. 33
      html/flash.js
  36. 12
      html/home.html
  37. 2
      html/services.js
  38. 4
      html/ui.js
  39. 242
      html/userpage.js
  40. 29
      html/web-server.html
  41. 42
      html/wifi/wifiAp.html
  42. 12
      html/wifi/wifiAp.js
  43. 14
      html/wifi/wifiSta.js
  44. 51
      httpd/httpd.c
  45. 3
      httpd/httpd.h
  46. 17
      httpd/httpdespfs.c
  47. 307
      httpd/multipart.c
  48. 34
      httpd/multipart.h
  49. 3
      include/esp8266.h
  50. 6
      include/espmissingincludes.h
  51. 5
      include/uart_hw.h
  52. 3
      include/user_config.h
  53. 3
      mqtt/mqtt.c
  54. 142
      mqtt/mqtt_cmd.c
  55. 3
      mqtt/mqtt_cmd.h
  56. 33
      serial/console.c
  57. 1
      serial/console.h
  58. 144
      serial/serbridge.c
  59. 2
      serial/serbridge.h
  60. 1
      serial/serled.c
  61. 2
      serial/slip.c
  62. 36
      serial/uart.c
  63. 4
      serial/uart.h
  64. 432
      socket/socket.c
  65. 38
      socket/socket.h
  66. 11
      syslog/syslog.c
  67. 3
      syslog/syslog.h
  68. BIN
      tools/htmlcompressor-1.5.3.jar
  69. BIN
      tools/yuicompressor-2.4.8.jar
  70. 489
      web-server/web-server.c
  71. 38
      web-server/web-server.h

1
.gitignore vendored

@ -15,6 +15,5 @@ esp-link.opensdf
esp-link.sdf esp-link.sdf
espfs/mkespfsimage/mman-win32/libmman.a espfs/mkespfsimage/mman-win32/libmman.a
.localhistory/ .localhistory/
tools/
local.conf local.conf
*.tgz *.tgz

@ -0,0 +1,31 @@
# Travis-CI file for Esp-Link
language: c
before_install:
- curl -Ls http://s3.voneicken.com/xtensa-lx106-elf-20160330.tgx | tar Jxf -
- curl -Ls http://s3.voneicken.com/esp_iot_sdk_v2.0.0.p1.tgx | tar -C .. -Jxf -
after_script:
# upload to an S3 bucket, requires S3_BUCKET, AWS_ACCESS_KEY_ID and AWS_SECRET_KEY to be set
# in environment using travis' repository settings
- "if [[ -n \"$S3_BUCKET\" && -n \"$AWS_ACCESS_KEY_ID\" ]]; then
echo Uploading *.tgz to $S3_BUCKET;
curl -Ls https://github.com/rlmcpherson/s3gof3r/releases/download/v0.5.0/gof3r_0.5.0_linux_amd64.tar.gz | tar zxf - gof3r_0.5.0_linux_amd64/gof3r;
mv gof3r*/gof3r .;
ls *.tgz | xargs -I {} ./gof3r put -b $S3_BUCKET -k esp-link/{} --acl public-read -p {};
ls *.tgz | xargs -I {} echo \"URL: http://$S3_BUCKET/esp-link/{}\";
fi"
compiler: gcc
env:
script:
- export XTENSA_TOOLS_ROOT=$PWD/xtensa-lx106-elf/bin/
- export BRANCH=$TRAVIS_BRANCH
#- export SDK_BASE=$PWD/esp_iot_sdk_v2.0.0.p1
- make release
notifications:
email: false

@ -0,0 +1,112 @@
Building esp-link
=================
Before you build esp-link, consider that you can download ready-made firmware images!
Just head over to the [release section](https://github.com/jeelabs/esp-link/releases)
and download the tgz archive.
If you decide to build your own, there are a number of options:
- On linux x86 download the ready-built toolchain and patched SDK like the automated build does
and compile the firmware
- On linux use a docker image with the toolchain and the SDK to compile the firmware
- On linux download and build the toolchain, download and patch the SDK, then compile the firmware
- On windows use a docker image with the toolchain and the SDK to compile the firmware
- On windows install mingw, python, java, and a slew of other tools and then build the
firmware
Once you have built the firmware you will want to flash it to your esp8266 module.
Assuming you already have esp-link running you can either go back to the initial flashing
via the serial port or you can use the over-the-air (i.e. Wifi) update method, which is faster
and more reliable (unless you have a non-booting version of esp-link).
The OTA flashing is described at the end of this page,
the serial flashing is described in [FLASHING.md](FLASHING.md).
### Automated builds
For every commit on github an automated build is made. This means that every branch, including
master, and every pull request always has an up-to-date build. These builds are made by Travis
using the instructions in `.travis.yml`, which basically consist of cloning the esp-link repo,
downloading the compiler toolchain, downloading the Espressif SDK, and running `make`.
If you're looking for how to build esp-link the travis instructions will always give you
accurate pointers to what to download.
### Docker (linux or windows)
The [esp-link docker image](https://hub.docker.com/r/jeelabs/esp-link/) contains all the
tools to build esp-link as well as the appropriate Espressif SDK. *It does not contain the
esp-link source code!*. You use the docker image just to build the firmware, you don't have
to do your editing in there. The steps are:
- clone the esp-link github repo
- checkout the branch or tag you want (for example the tag `v2.2.3` for that release)
- cd into the esp-link top directory
- run `make` in docker while mounting your esp-link directory into the container:
- linux: `docker run -v $PWD:/esp-link jeelabs/esp-link:latest`
- windows: `docker run -v c:\somepath\esp-link:/esp-link jeelabs/esp-link:latest`,
where `somepath` is the path to where you cloned esp-link, you probably end up with
something like `-v c:\Users\tve\source\esp-link:/esp-link`
- if you are not building esp-link `master` then read the release notes to see which version of
the Espressif SDK you need and use that as tag for the container image, such as
`jeelabs/esp-link:SDK2.0.0.p1`; you can see the list of available SDKs on
[dockerhub](https://hub.docker.com/r/jeelabs/esp-link/tags/)o
Sample steps to build esp-link v2.2.3 on a Win7 Pro x64 (these use the docker terminal, there
are multiple way to skin the proverbial cat...):
1) Install Docker Toolbox ( http://www.docker.com/products/docker-toolbox )
2) Install Git Desktop ( https://desktop.github.com/ )
3) Clone esp-link from Github master to local repository ( https://github.com/jeelabs/esp-link )
4) Open Docker Quickstart Terminal
5) cd to local esp-link git repository ( C:\Users\xxxxx\Documents\GitHub\esp-link )
6) Run "docker run -v $PWD:/esp-link jeelabs/esp-link" command in Docker Quickstart Terminal window
Note: there has been one report of messed-up timestamps on windows, the symptom is that `make`
complains about file modification times being in the future. This may be due to the different
way Windows and Linux handle time zones and daylight savings time. PLease report if you
encounter this or know a solution.
### Linux
The firmware has been built using the https://github.com/pfalcon/esp-open-sdk[esp-open-sdk]
on a Linux system. Create an esp8266 directory, install the esp-open-sdk into a sub-directory
using the *non-standalone* install (i.e., there should not be an sdk directory in the esp-open-sdk
dir when done installing, *if you use the standalone install you will get compilation errors*
with std types, such as `uint32_t`).
Download the Espressif "NONOS" SDK (use the version mentioned in the release notes) from their
http://bbs.espressif.com/viewforum.php?f=5[download forum] and also expand it into a
sub-directory. Often there are patches to apply, in that case you need to download the patches
from the same source and apply them.
You can simplify your life (and avoid the hour-long build time for esp-open-sdk) if you are
on an x86 box by downloading the packaged and built esp-open-sdk and the fully patches SDKfrom the
links used in the `.travis.yaml`.
Clone the esp-link repository into a third sub-directory and check out the tag you would like,
such as `git checkout v2.2.3`.
This way the relative paths in the Makefile will work.
If you choose a different directory structure look at the top of the Makefile for the
appropriate environment variables to define.
Do not use the source tarballs from the release page on github,
these will give you trouble compiling because the Makefile uses git to determine the esp-link
version being built.
In order to OTA-update the esp8266 you should `export ESP_HOSTNAME=...` with the hostname or
IP address of your module.
Now, build the code: `make` in the top-level of esp-link. If you want to se the commands being
issued, use `VERBOSE=1 make`.
A few notes from others (I can't fully verify these):
- You may need to install `zlib1g-dev` and `python-serial`
- Make sure you have the correct version of the SDK
- Make sure the paths at the beginning of the makefile are correct
- Make sure `esp-open-sdk/xtensa-lx106-elf/bin` is in the PATH set in the Makefile
### Windows
Please consider installing docker and using the docker image to save yourself grief getting all
the tools installed and working.
If you do want to compile "natively" on Windows it certainly is possible.
It is possible to build esp-link on Windows, but it requires a
[gaggle of software to be installed](WINDOWS.md)

@ -0,0 +1,29 @@
Contributing to Esp-Link
========================
Esp-link is not the work of a single individual, rather many people have contributed directly or indirectly.
Your contribution is very much appreciated, but please follow these guidelines to make the task easier on
everyone.
- Contributions do not have to be in the form of code: often documentation, how-tos are very valuable and answering questions
in github issues as well as gitter is also very valuable and welcome!
- Before you make a change or submit a change via a pull request, **open an issue and discuss your proposed change**. Gitter
is a good alternative to a github issue. This ensures that you don't spend time doing work that ultimately won't be accepted.
There's nothing more frustrating than receiving a pull-request that has lots of goodies but doesn't fit because it wasn't
discussed and agreed upon up-front.
- Keep your pull request as small as practical, if you have 3 things you want to change, please create 3 pull requests,
or at the very least, make sure your 3 changes are in different commits. This makes the review and testing easier
and ensures that if one feature is good to go it can be merged even if another feature needs more tweaking.
- The esp-link codebase is not uniform, it comes from a variety of sources, in particular esphttpd. A result of this is
that there is more than one coding style in use. If you make changes to existing files, please respect the file's
coding style (yes, sometimes that's not even totally uniform). Your overall goal should be for your code or changes to
look as if the original author had made them, not how you would like them to look.
- Changes that reformat or reorganize code will generally not be accepted, please do not mix them with other functionality
changes you are making and certainly discuss them first. Accept the fact that some people prefer bastards over pure-breads ;-).
- Esp-link has a mission stated in the readme.md, changes that deviate from that mission will generally be rejected. The reason
is that at the end of the day focusing on doing one thing well has a higher chance of succeeding than doing many things.
In that sense, esp-link is not a swiss-army knife firmware. (This being said, many people have used esp-link as a basis to add
their own functionality, which is very cool.)
I believe the above guidelines are pretty standard across a very large number of open source projects and not unique to esp-link,
so please do not get discouraged. Thank you for taking a look at esp-link!

@ -0,0 +1,31 @@
# Dockerfile for github.com/jeelabs/esp-link
#
# This dockerfile is intended to be used to compile esp-link as it's checked out on
# your desktop/laptop. You can git clone esp-link, and then compile it using
# a commandline of `docker run -v $PWD:/esp-link jeelabs/esp-link`. The -v mounts
# your esp-link source directory onto /esp-link in the container and the default command is
# to run make.
# If you would like to create your own container image, use `docker build -t esp-link .`
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y software-properties-common build-essential python curl git \
zlib1g-dev openjdk-8-jre-headless
RUN curl -Ls http://s3.voneicken.com/xtensa-lx106-elf-20160330.tgx | tar Jxf -
RUN curl -Ls http://s3.voneicken.com/esp_iot_sdk_v2.0.0.p1.tgx | tar -Jxf -
ENV XTENSA_TOOLS_ROOT /xtensa-lx106-elf/bin/
# This could be used to create an image with esp-link in it from github:
#RUN git clone https://github.com/jeelabs/esp-link
# This could be used to create an image with esp-link in it from the local dir:
#COPY . esp-link/
# Expect the esp-link source/home dir to be mounted here:
VOLUME /esp-link
WORKDIR /esp-link
# Default command is to run a build, can be overridden on the docker commandline:
CMD make

@ -0,0 +1,204 @@
Flashing esp-link
=================
### Hardware configuration for normal operation
This firmware is designed for any esp8266 module.
The recommended connections for an esp-01 module are:
- URXD: connect to TX of microcontroller
- UTXD: connect to RX of microcontroller
- GPIO0: connect to RESET of microcontroller
- GPIO2: optionally connect green LED to 3.3V (indicates wifi status)
The recommended connections for an esp-12 module are:
- URXD: connect to TX of microcontroller
- UTXD: connect to RX of microcontroller
- GPIO12: connect to RESET of microcontroller
- GPIO13: connect to ISP of LPC/ARM microcontroller (not used with Arduino/AVR)
- GPIO0: either a 1k-10k pull-up resistor to 3.3v or a green "conn" LED via a 1k-2.2k
resistor to 3.3V (indicates wifi status)
- GPIO2: either a 1k-10k pull-up resistor to 3.3v or a yellow "ser" LED via a 1k-2.2k
resistor to 3.3V (indicates serial activity)
At boot time the esp8266 ROM outputs a boot message on UTXD, this can cause problems to the attached
microcontroller. If you need to avoid this, you can configure esp-link to swap the uart pins.
You should then connect the esp-12 module as follows and choose the "swap_uart" pin assignment
in the esp-link web interface:
- GPIO13: connect to TX of microcontroller
- GPIO15: connect to RX of microcontroller and use a pull-down to ensure proper booting
- GPIO12: connect to RESET of microcontroller
- GPIO14: connect to ISP of LPC/ARM microcontroller (not used with Arduino/AVR)
- GPIO0: either a 1k-10k pull-up resistor to 3.3v or a green "conn" LED via a 1k-2.2k
resistor to 3.3V (indicates wifi status)
- GPIO2: either a 1k-10k pull-up resistor to 3.3v or a yellow "ser" LED via a 1k-2.2k
resistor to 3.3V (indicates serial activity)
The GPIO pin assignments can be changed dynamically in the web UI and are saved in flash.
### Hardware configuration for flashing
To flash firmware onto the esp8266 via the serial port the following must be observed:
- GPIO0 must be low when reset ends to put the esp8266 into flash programming mode, it must be high
to enter normal run mode
- GPIO2 must be high (pull-up resistor)
- GPIO15 must be low (pull-down resistor)
### Initial serial flashing
Download the latest [release](https://github.com/jeelabs/esp-link/releases) or use the
`user1.bin` file that is produced by the build process.
You will need to flash the bootloader, the `user1.bin` firmware, blank wifi settings, and init data
as described below.
_Important_: the firmware adapts to the size of the flash chip using information
stored in the boot sector (address 0). This is the standard way that the esp8266 SDK detects
the flash size. What this means is that you need to set this properly when you flash the bootloader.
If you use esptool.py you can do it using the -ff and -fs options. See the end of this page for
instructions on installing esptool.py.
The short version for the serial flashing is:
- flash `boot_v1.X.bin` from the official SDK or from the release tgz to `0x00000`
- flash `blank.bin` from the official SDK or from the tgz to `0x3FE000`
- flash `esp_init_data_default.bin` from the official SDK or from the tgz to `0x3FC000`
- flash `user1.bin` to `0x01000`
- be sure to use the commandline flags to set the correct flash size when flashing the bootloader
- some of the addresses vary with flash chip size
After the initial flashing if you want to update the firmware it is recommended to use the
over-the-air update described further down. If you want to update serially you only need to
reflash `user1.bin`.
### 32Mbit / 4Mbyte module
On Linux using esptool.py this turns into the following for a 32mbit=4MByte flash chip,
such as an esp-12 module typically has (_substitute the appropriate release number and bootloader
version number_):
```
curl -L https://github.com/jeelabs/esp-link/releases/download/v2.2.3/esp-link-v2.2.3.tgz | \
tar xzf -
cd esp-link-v2.2.3
esptool.py --port /dev/ttyUSB0 --baud 230400 write_flash -fs 32m -ff 80m \
0x00000 boot_v1.5.bin 0x1000 user1.bin \
0x3FC000 esp_init_data_default.bin 0x3FE000 blank.bin
```
I use a high baud rate as shown above because I'm impatient, but that's not required.
### 4Mbit / 512Kbyte module
```
curl -L https://github.com/jeelabs/esp-link/releases/download/v2.2.3/esp-link-v2.2.3.tgz | \
tar xzf -
cd esp-link-v2.2.3
esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash -fs 4m -ff 40m \
0x00000 boot_v1.5.bin 0x1000 user1.bin \
0x7C000 esp_init_data_default.bin 0x7E000 blank.bin
```
The `-fs 4m -ff40m` options say 4Mbits and 40Mhz as opposed to 32Mbits at 80Mhz for the 4MByte
flash modules. Note the different address for esp_init_data_default.bin and blank.bin
(the SDK stores its wifi settings near the end of flash, so it changes with flash size).
For __8Mbit / 1MByte__ modules the addresses are 0xFC000 and 0xFE000.
__Warning__: there is a bug in boot_v1.5.bin which causes it to only boot into user1 once.
If that fails it gets stuck trying to boot into user2. If this happens (can be seen in the
boot output on uart2 at 76600 baud) reflash just blank.bin at 0x7E000 (4Mbit module). (Sigh)
## Updating the firmware over-the-air
This firmware supports over-the-air (OTA) flashing for modules with 1MByte or more flash,
so you do not have to deal with serial flashing again after the initial one!
The recommended way to flash is to use `make wiflash`
if you are also building the firmware and `./wiflash` if you are downloading firmware binaries.
The resulting commandlines are:
```
ESP_HOSTNAME=192.168.1.5 make wiflash
```
or assuming mDNS is working:
```
ESP_HOSTNAME=esp-link.local make wiflash
```
or using wiflash.sh:
```
./wiflash.sh <esp-hostname> user1.bin user2.bin
```
The flashing, restart, and re-associating with your wireless network takes about 15 seconds
and is fully automatic. The first 1MB of flash are divided into two 512KB partitions allowing for new
code to be uploaded into one partition while running from the other. This is the official
OTA upgrade method supported by the SDK, except that the firmware is POSTed to the module
using curl as opposed to having the module download it from a cloud server. On a module with
512KB flash there is only space for one partition and thus no way to do an OTA update.
If you need to clear the wifi settings you need to reflash the `blank.bin`
using the serial method.
The flash configuration and the OTA upgrade process is described in more detail
in [FLASH.md](FLASH.md).
## Installing esptool.py on Linux
On Linux use [esptool.py](https://github.com/themadinventor/esptool) to flash the esp8266.
If you're a little python challenged then the following install instructions might help:
- Install ez_setup with the following two commands (I believe this will do something
reasonable if you already have it):
wget https://bootstrap.pypa.io/ez_setup.py
python ez_setup.py
- Install esptool.py:
git clone https://github.com/themadinventor/esptool.git
cd esptool
python setup.py install
cd ..
esptool.py -h
## Installing esptool.py on Windows
Esptool is a pythin pgm that works just fine on windows. These instructions assume that git and
python are available from the commandline.
Start a command line, clone esptool, and run `python setup.py install` in esptool's
directory (this step needs to be done only once):
```
> git clone https://github.com/themadinventor/esptool.git
Cloning into 'esptool'...
remote: Counting objects: 268, done.
emote: Total 268 (delta 0), reused 0 (delta 0), pack-reused 268
Receiving objects: 100% (268/268), 99.66 KiB | 0 bytes/s, done.
Resolving deltas: 100% (142/142), done.
Checking connectivity... done.
> cd esptool
> python setup.py install
running install
...
...
...
Finished processing dependencies for esptool==0.1.0
```
Download and unzip the latest esp-link release package, and start a commandline
in that directory. The command to run is pretty much the same as for linux.
Adjust the path to esptool and the COM port if you don't have the ESP on COM12. 460800
baud worked just fine for me, writing at ~260kbit/s instead of ~80kbit/s.
```
>python "../esptool/esptool.py" --port COM12 --baud 115200 write_flash \
--flash_freq 80m --flash_mode qio --flash_size 32m \
0x0000 boot_v1.6.bin 0x1000 user1.bin \
0x3FC000 esp_init_data_default.bin 0x3FE000 blank.bin
Connecting...
Erasing flash...
Wrote 3072 bytes at 0x00000000 in 0.3 seconds (79.8 kbit/s)...
Erasing flash...
Wrote 438272 bytes at 0x00001000 in 43.4 seconds (80.7 kbit/s)...
Erasing flash...
Wrote 1024 bytes at 0x003fc000 in 0.1 seconds (83.6 kbit/s)...
Erasing flash...
Wrote 4096 bytes at 0x003fe000 in 0.4 seconds (83.4 kbit/s)...
Leaving...
```

@ -4,7 +4,6 @@
# Makefile heavily adapted to esp-link and wireless flashing by Thorsten von Eicken # Makefile heavily adapted to esp-link and wireless flashing by Thorsten von Eicken
# Lots of work, in particular to support windows, by brunnels # Lots of work, in particular to support windows, by brunnels
# Original from esphttpd and others... # Original from esphttpd and others...
# VERBOSE=1
# #
# Start by setting the directories for the toolchain a few lines down # Start by setting the directories for the toolchain a few lines down
# the default target will build the firmware images # the default target will build the firmware images
@ -52,35 +51,38 @@ ESP_HOSTNAME ?= esp-link
# Base directory for the compiler. Needs a / at the end. # Base directory for the compiler. Needs a / at the end.
# Typically you'll install https://github.com/pfalcon/esp-open-sdk # Typically you'll install https://github.com/pfalcon/esp-open-sdk
# IMPORTANT: use esp-open-sdk `make STANDALONE=n`: the SDK bundled with esp-open-sdk will *not* work!
XTENSA_TOOLS_ROOT ?= $(abspath ../esp-open-sdk/xtensa-lx106-elf/bin)/ XTENSA_TOOLS_ROOT ?= $(abspath ../esp-open-sdk/xtensa-lx106-elf/bin)/
# Firmware version # Firmware version
# WARNING: if you change this expect to make code adjustments elsewhere, don't expect # WARNING: if you change this expect to make code adjustments elsewhere, don't expect
# that esp-link will magically work with a different version of the SDK!!! # that esp-link will magically work with a different version of the SDK!!!
SDK_VERS ?= esp_iot_sdk_v1.5.4 SDK_VERS ?= esp_iot_sdk_v2.0.0.p1
# Try to find the firmware manually extracted, e.g. after downloading from Espressif's BBS, # Try to find the firmware manually extracted, e.g. after downloading from Espressif's BBS,
# http://bbs.espressif.com/viewforum.php?f=46 # http://bbs.espressif.com/viewforum.php?f=46
# USING THE SDK BUNDLED WITH ESP-OPEN-SDK WILL NOT WORK!!!
SDK_BASE ?= $(wildcard ../$(SDK_VERS)) SDK_BASE ?= $(wildcard ../$(SDK_VERS))
# If the firmware isn't there, see whether it got downloaded as part of esp-open-sdk # If the firmware isn't there, see whether it got downloaded as part of esp-open-sdk
ifeq ($(SDK_BASE),) # This used to work at some point, but is not supported, uncomment if you feel lucky ;-)
SDK_BASE := $(wildcard $(XTENSA_TOOLS_ROOT)/../../$(SDK_VERS)) #ifeq ($(SDK_BASE),)
endif #SDK_BASE := $(wildcard $(XTENSA_TOOLS_ROOT)/../../$(SDK_VERS))
#endif
# Clean up SDK path # Clean up SDK path
SDK_BASE := $(abspath $(SDK_BASE)) SDK_BASE := $(abspath $(SDK_BASE))
$(warning Using SDK from $(SDK_BASE)) $(info SDK is $(SDK_BASE))
# Path to bootloader file # Path to bootloader file
BOOTFILE ?= $(SDK_BASE/bin/boot_v1.5.bin) BOOTFILE ?= $(SDK_BASE/bin/boot_v1.6.bin)
# Esptool.py path and port, only used for 1-time serial flashing # Esptool.py path and port, only used for 1-time serial flashing
# Typically you'll use https://github.com/themadinventor/esptool # Typically you'll use https://github.com/themadinventor/esptool
# Windows users use the com port i.e: ESPPORT ?= com3 # Windows users use the com port i.e: ESPPORT ?= com3
ESPTOOL ?= $(abspath ../esp-open-sdk/esptool/esptool.py) ESPTOOL ?= $(abspath ../esp-open-sdk/esptool/esptool.py)
ESPPORT ?= /dev/ttyUSB0 ESPPORT ?= /dev/ttyUSB0
ESPBAUD ?= 460800 ESPBAUD ?= 230400
# --------------- chipset configuration --------------- # --------------- chipset configuration ---------------
@ -100,8 +102,8 @@ LED_SERIAL_PIN ?= 14
# --------------- esp-link modules config options --------------- # --------------- esp-link modules config options ---------------
# Optional Modules mqtt # Optional Modules: mqtt rest socket web-server syslog
MODULES ?= mqtt rest #syslog MODULES ?= mqtt rest socket web-server syslog
# --------------- esphttpd config options --------------- # --------------- esphttpd config options ---------------
@ -117,8 +119,6 @@ MODULES ?= mqtt rest #syslog
# #
# Adding JPG or PNG files (and any other compressed formats) is not recommended, because GZIP # Adding JPG or PNG files (and any other compressed formats) is not recommended, because GZIP
# compression does not work effectively on compressed files. # compression does not work effectively on compressed files.
#Static gzipping is disabled by default.
GZIP_COMPRESSION ?= yes GZIP_COMPRESSION ?= yes
# If COMPRESS_W_HTMLCOMPRESSOR is set to "yes" then the static css and js files will be compressed with # If COMPRESS_W_HTMLCOMPRESSOR is set to "yes" then the static css and js files will be compressed with
@ -182,17 +182,35 @@ endif
# --------------- esp-link version --------------- # --------------- esp-link version ---------------
# Version-fu :-) This code assumes that a new maj.minor is started using a "vN.M.0" tag on master
# and that thereafter the desired patchlevel number is just the number of commits since the tag.
#
# Get the current branch name if not using travis
TRAVIS_BRANCH?=$(shell git symbolic-ref --short HEAD --quiet)
# Use git describe to get the latest version tag, commits since then, sha and dirty flag, this
# results is something like "v1.2.0-13-ab6cedf-dirty"
VERSION := $(shell (git describe --tags --match 'v*' --long --dirty || echo "no-tag") | sed -re 's/(\.0)?-/./')
# If not on master then insert the branch name
ifneq ($(TRAVIS_BRANCH),master)
ifneq ($(findstring V%,$(TRAVIS_BRANCH)),)
VERSION := $(shell echo $(VERSION) | sed -e 's/-/-$(TRAVIS_BRANCH)-/')
endif
endif
VERSION :=$(VERSION)
$(info VERSION is $(VERSION))
# OLD - DEPRECATED
# This queries git to produce a version string like "esp-link v0.9.0 2015-06-01 34bc76" # 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 # 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 # 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 # on the release tag, make release, upload esp-link.tgz into the release files
#VERSION ?= "esp-link custom version" #VERSION ?= "esp-link custom version"
DATE := $(shell date '+%F %T') #DATE := $(shell date '+%F %T')
BRANCH ?= $(shell if git diff --quiet HEAD; then git describe --tags; \ #BRANCH ?= $(shell if git diff --quiet HEAD; then git describe --tags; \
else git symbolic-ref --short HEAD; fi) # else git symbolic-ref --short HEAD; fi)
SHA := $(shell if git diff --quiet HEAD; then git rev-parse --short HEAD | cut -d"/" -f 3; \ #SHA := $(shell if git diff --quiet HEAD; then git rev-parse --short HEAD | cut -d"/" -f 3; \
else echo "development"; fi) # else echo "development"; fi)
VERSION ?=esp-link $(BRANCH) - $(DATE) - $(SHA) #VERSION ?=esp-link $(BRANCH) - $(DATE) - $(SHA)
# Output directors to store intermediate compiled files # Output directors to store intermediate compiled files
# relative to the project directory # relative to the project directory
@ -220,6 +238,14 @@ ifneq (,$(findstring syslog,$(MODULES)))
CFLAGS += -DSYSLOG CFLAGS += -DSYSLOG
endif endif
ifneq (,$(findstring web-server,$(MODULES)))
CFLAGS += -DWEBSERVER
endif
ifneq (,$(findstring socket,$(MODULES)))
CFLAGS += -DSOCKET
endif
# which modules (subdirectories) of the project to include in compiling # which modules (subdirectories) of the project to include in compiling
LIBRARIES_DIR = libraries LIBRARIES_DIR = libraries
MODULES += espfs httpd user serial cmd esp-link MODULES += espfs httpd user serial cmd esp-link
@ -227,7 +253,7 @@ MODULES += $(foreach sdir,$(LIBRARIES_DIR),$(wildcard $(sdir)/*))
EXTRA_INCDIR = include . EXTRA_INCDIR = include .
# libraries used in this project, mainly provided by the SDK # libraries used in this project, mainly provided by the SDK
LIBS = c gcc hal phy pp net80211 wpa main lwip crypto LIBS = c gcc hal phy pp net80211 wpa main lwip_536 crypto
# compiler flags using during compilation of source files # 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 \
@ -235,7 +261,7 @@ CFLAGS += -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-
-D__ets__ -DICACHE_FLASH -Wno-address -DFIRMWARE_SIZE=$(ESP_FLASH_MAX) \ -D__ets__ -DICACHE_FLASH -Wno-address -DFIRMWARE_SIZE=$(ESP_FLASH_MAX) \
-DMCU_RESET_PIN=$(MCU_RESET_PIN) -DMCU_ISP_PIN=$(MCU_ISP_PIN) \ -DMCU_RESET_PIN=$(MCU_RESET_PIN) -DMCU_ISP_PIN=$(MCU_ISP_PIN) \
-DLED_CONN_PIN=$(LED_CONN_PIN) -DLED_SERIAL_PIN=$(LED_SERIAL_PIN) \ -DLED_CONN_PIN=$(LED_CONN_PIN) -DLED_SERIAL_PIN=$(LED_SERIAL_PIN) \
-DVERSION="$(VERSION)" -DVERSION="esp-link $(VERSION)"
# linker flags used to generate the main object file # linker flags used to generate the main object file
LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static -Wl,--gc-sections LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static -Wl,--gc-sections
@ -258,7 +284,6 @@ LD := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc
OBJCP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objcopy OBJCP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objcopy
OBJDP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objdump OBJDP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objdump
#### ####
SRC_DIR := $(MODULES) SRC_DIR := $(MODULES)
BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES)) BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES))
@ -339,10 +364,7 @@ endef
.PHONY: all checkdirs clean webpages.espfs wiflash .PHONY: all checkdirs clean webpages.espfs wiflash
all: echo_version checkdirs $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin all: checkdirs $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin
echo_version:
@echo VERSION: $(VERSION)
$(USER1_OUT): $(APP_AR) $(LD_SCRIPT1) $(USER1_OUT): $(APP_AR) $(LD_SCRIPT1)
$(vecho) "LD $@" $(vecho) "LD $@"
@ -366,7 +388,7 @@ $(FW_BASE)/user1.bin: $(USER1_OUT) $(FW_BASE)
$(Q) $(OBJCP) --only-section .rodata -O binary $(USER1_OUT) eagle.app.v6.rodata.bin $(Q) $(OBJCP) --only-section .rodata -O binary $(USER1_OUT) eagle.app.v6.rodata.bin
$(Q) $(OBJCP) --only-section .irom0.text -O binary $(USER1_OUT) eagle.app.v6.irom0text.bin $(Q) $(OBJCP) --only-section .irom0.text -O binary $(USER1_OUT) eagle.app.v6.irom0text.bin
ls -ls eagle*bin ls -ls eagle*bin
$(Q) COMPILE=gcc PATH=$(XTENSA_TOOLS_ROOT):$(PATH) python $(APPGEN_TOOL) $(USER1_OUT) 2 $(ESP_FLASH_MODE) $(ESP_FLASH_FREQ_DIV) $(ESP_SPI_SIZE) 0 $(Q) COMPILE=gcc PATH=$(XTENSA_TOOLS_ROOT):$(PATH) python $(APPGEN_TOOL) $(USER1_OUT) 2 $(ESP_FLASH_MODE) $(ESP_FLASH_FREQ_DIV) $(ESP_SPI_SIZE) 0 >/dev/null
$(Q) rm -f eagle.app.v6.*.bin $(Q) rm -f eagle.app.v6.*.bin
$(Q) mv eagle.app.flash.bin $@ $(Q) mv eagle.app.flash.bin $@
@echo "** user1.bin uses $$(stat -c '%s' $@) bytes of" $(ESP_FLASH_MAX) "available" @echo "** user1.bin uses $$(stat -c '%s' $@) bytes of" $(ESP_FLASH_MAX) "available"
@ -377,7 +399,7 @@ $(FW_BASE)/user2.bin: $(USER2_OUT) $(FW_BASE)
$(Q) $(OBJCP) --only-section .data -O binary $(USER2_OUT) eagle.app.v6.data.bin $(Q) $(OBJCP) --only-section .data -O binary $(USER2_OUT) eagle.app.v6.data.bin
$(Q) $(OBJCP) --only-section .rodata -O binary $(USER2_OUT) eagle.app.v6.rodata.bin $(Q) $(OBJCP) --only-section .rodata -O binary $(USER2_OUT) eagle.app.v6.rodata.bin
$(Q) $(OBJCP) --only-section .irom0.text -O binary $(USER2_OUT) eagle.app.v6.irom0text.bin $(Q) $(OBJCP) --only-section .irom0.text -O binary $(USER2_OUT) eagle.app.v6.irom0text.bin
$(Q) COMPILE=gcc PATH=$(XTENSA_TOOLS_ROOT):$(PATH) python $(APPGEN_TOOL) $(USER2_OUT) 2 $(ESP_FLASH_MODE) $(ESP_FLASH_FREQ_DIV) $(ESP_SPI_SIZE) 0 $(Q) COMPILE=gcc PATH=$(XTENSA_TOOLS_ROOT):$(PATH) python $(APPGEN_TOOL) $(USER2_OUT) 2 $(ESP_FLASH_MODE) $(ESP_FLASH_FREQ_DIV) $(ESP_SPI_SIZE) 1 >/dev/null
$(Q) rm -f eagle.app.v6.*.bin $(Q) rm -f eagle.app.v6.*.bin
$(Q) mv eagle.app.flash.bin $@ $(Q) mv eagle.app.flash.bin $@
$(Q) if [ $$(stat -c '%s' $@) -gt $$(( $(ESP_FLASH_MAX) )) ]; then echo "$@ too big!"; false; fi $(Q) if [ $$(stat -c '%s' $@) -gt $$(( $(ESP_FLASH_MAX) )) ]; then echo "$@ too big!"; false; fi
@ -402,17 +424,12 @@ flash: all
0x00000 "$(SDK_BASE)/bin/boot_v1.5.bin" 0x01000 $(FW_BASE)/user1.bin \ 0x00000 "$(SDK_BASE)/bin/boot_v1.5.bin" 0x01000 $(FW_BASE)/user1.bin \
$(ET_BLANK) $(SDK_BASE)/bin/blank.bin $(ET_BLANK) $(SDK_BASE)/bin/blank.bin
ifeq ($(OS),Windows_NT)
tools/$(HTML_COMPRESSOR): tools/$(HTML_COMPRESSOR):
$(Q) mkdir -p tools $(Q) echo "The jar files in the tools dir are missing, they should be in the source repo"
cd tools; wget --no-check-certificate https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR) -O $(YUI_COMPRESSOR) $(Q) echo "The following commands can be used to fetch them, but the URLs have changed..."
cd tools; wget --no-check-certificate https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) -O $(HTML_COMPRESSOR) $(Q) echo mkdir -p tools
else $(Q) echo "cd tools; wget --no-check-certificate https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR) -O $(YUI_COMPRESSOR)"
tools/$(HTML_COMPRESSOR): $(Q) echo "cd tools; wget --no-check-certificate https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) -O $(HTML_COMPRESSOR)"
$(Q) mkdir -p tools
cd tools; wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR)
cd tools; wget https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR)
endif
ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes")
$(BUILD_BASE)/espfs_img.o: tools/$(HTML_COMPRESSOR) $(BUILD_BASE)/espfs_img.o: tools/$(HTML_COMPRESSOR)
@ -426,7 +443,7 @@ $(BUILD_BASE)/espfs_img.o: html/ html/wifi/ espfs/mkespfsimage/mkespfsimage
$(Q) cp -r html/wifi/*.png html_compressed/wifi; $(Q) cp -r html/wifi/*.png html_compressed/wifi;
$(Q) cp -r html/wifi/*.js html_compressed/wifi; $(Q) cp -r html/wifi/*.js html_compressed/wifi;
ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes")
$(Q) echo "Compression assets with htmlcompressor. This may take a while..." $(Q) echo "Compressing assets with htmlcompressor. This may take a while..."
$(Q) java -jar tools/$(HTML_COMPRESSOR) \ $(Q) java -jar tools/$(HTML_COMPRESSOR) \
-t html --remove-surrounding-spaces max --remove-quotes --remove-intertag-spaces \ -t html --remove-surrounding-spaces max --remove-quotes --remove-intertag-spaces \
-o $(abspath ./html_compressed)/ \ -o $(abspath ./html_compressed)/ \
@ -436,7 +453,7 @@ ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes")
-t html --remove-surrounding-spaces max --remove-quotes --remove-intertag-spaces \ -t html --remove-surrounding-spaces max --remove-quotes --remove-intertag-spaces \
-o $(abspath ./html_compressed)/wifi/ \ -o $(abspath ./html_compressed)/wifi/ \
$(WIFI_PATH)*.html $(WIFI_PATH)*.html
$(Q) echo "Compression assets with yui-compressor. This may take a while..." $(Q) echo "Compressing assets with yui-compressor. This may take a while..."
$(Q) for file in `find html_compressed -type f -name "*.js"`; do \ $(Q) for file in `find html_compressed -type f -name "*.js"`; do \
java -jar tools/$(YUI_COMPRESSOR) $$file --line-break 0 -o $$file; \ java -jar tools/$(YUI_COMPRESSOR) $$file --line-break 0 -o $$file; \
done done
@ -477,15 +494,18 @@ espfs/mkespfsimage/mkespfsimage: espfs/mkespfsimage/
$(Q) $(MAKE) -C espfs/mkespfsimage GZIP_COMPRESSION="$(GZIP_COMPRESSION)" $(Q) $(MAKE) -C espfs/mkespfsimage GZIP_COMPRESSION="$(GZIP_COMPRESSION)"
release: all release: all
$(Q) rm -rf release; mkdir -p release/esp-link-$(BRANCH) $(Q) rm -rf release; mkdir -p release/esp-link-$(VERSION)
$(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)/user1.bin | cut -b 1-80
$(Q) egrep -a 'esp-link [a-z0-9.]+ - 201' $(FW_BASE)/user2.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 \ $(Q) cp $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin $(SDK_BASE)/bin/blank.bin \
"$(SDK_BASE)/bin/boot_v1.5.bin" wiflash avrflash release/esp-link-$(BRANCH) "$(SDK_BASE)/bin/boot_v1.6.bin" "$(SDK_BASE)/bin/esp_init_data_default.bin" \
$(Q) tar zcf esp-link-$(BRANCH).tgz -C release esp-link-$(BRANCH) wiflash avrflash release/esp-link-$(VERSION)
$(Q) echo "Release file: esp-link-$(BRANCH).tgz" $(Q) tar zcf esp-link-$(VERSION).tgz -C release esp-link-$(VERSION)
$(Q) echo "Release file: esp-link-$(VERSION).tgz"
$(Q) rm -rf release $(Q) rm -rf release
docker:
$(Q) docker build -t jeelabs/esp-link .
clean: clean:
$(Q) rm -f $(APP_AR) $(Q) rm -f $(APP_AR)
$(Q) rm -f $(TARGET_OUT) $(Q) rm -f $(TARGET_OUT)

@ -1,535 +0,0 @@
ESP-LINK: Wifi-Serial Bridge w/REST&MQTT
========================================
Thorsten von Eicken
:toc:
:toc-title!:
:toc-placement!:
This firmware connects an attached micro-controller to the internet using a ESP8266 Wifi module.
It implements a number of features:
[options="compact"]
- transparent bridge between Wifi and serial, useful for debugging or inputting into a uC
- flash-programming attached Arduino/AVR microcontrollers, esp8266 modules, as well as
LPC800-series and other ARM microcontrollers via Wifi
- built-in stk500v1 programmer for AVR uC's: program using HTTP upload of hex file
- outbound REST HTTP requests from the attached micro-controller to the internet
- MQTT client pub/sub from the attached micro-controller to the internet
The firmware includes a tiny HTTP server based on
http://www.esp8266.com/viewforum.php?f=34[esphttpd]
with a simple web interface, many thanks to Jeroen Domburg for making it available!
The REST and MQTT functionality are loosely based on https://github.com/tuanpmt/espduino
but significantly rewritten and no longer protocol compatible, thanks to tuanpmt for the
inspiration!
Many thanks to https://github.com/brunnels for contributions in particular around the espduino
functionality. Thank you also to https://github.com/susisstrolch, https://github.com/bc547,
and https://github.com/katast for additional contributions!
[float]
Table of Contents
-----------------
toc::[]
Releases & Downloads
--------------------
- https://github.com/jeelabs/esp-link/releases/tag/v2.1.7[V2.1.7] is the most recent release.
It has the new built-in stk500v1 programmer and works on all modules (esp-01 through esp-12).
- https://github.com/jeelabs/esp-link/releases/tag/v2.2.beta2[V2.2.beta2] will be coming
up shortly with mDNS, sNTP, and syslog support, stay tuned...
- See https://github.com/jeelabs/esp-link/releases[all releases].
For quick support and questions chat at
image:https://badges.gitter.im/Join%20Chat.svg[link="https://gitter.im/jeelabs/esp-link"]
Intro
-----
### Esp-link goals
The goal of the esp-link project is to create an advanced Wifi co-processor. Esp-link assumes that
there is a "main processor" (also referred to as "attached uController") and that esp-link's role
is to facilitate communication over Wifi. Where esp-link is a bit unusual is that it's not really
just a Wifi interface or a slave co-processor. In some sense it's the master, because the main
processor can be reset, controlled and reprogrammed through esp-link. The three main areas of
functionality in esp-link are:
- reprogramming and debugging the attached uC
- letting the attached uC make outbound communication and offloading the protocol processing
- forwarding inbound communication and offloading the protocol processing (this part is the
least developed)
The goal of the project is also to remain focused on the above mission. In particular, esp-link
is not a platform for stand-alone applications and it does not support connecting sensors or
actuators directly to it. A few users have taken esp-link as a starting point for doing these
things and that's great, but there's also value in keeping the mainline esp-link project
focused on a clear mission.
### Esp-link uses
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 that through the initial
callback registration.
### Eye Candy
These screen shots show the Home page, the Wifi configuration page, the console for the
attached microcontroller, and the pin assignments card:
image:https://cloud.githubusercontent.com/assets/39480/8261425/6ca395a6-167f-11e5-8e92-77150371135a.png[width="45%"]
image:https://cloud.githubusercontent.com/assets/39480/8261427/6caf7326-167f-11e5-8085-bc8b20159b2b.png[width="45%"]
image:https://cloud.githubusercontent.com/assets/39480/8261426/6ca7f75e-167f-11e5-827d-9a1c582ad05d.png[width="45%"]
image:https://cloud.githubusercontent.com/assets/39480/8261658/11e6c64a-1681-11e5-82d0-ea5ec90a6ddb.png[width="45%"]
Getting Started
---------------
### Hardware configuration
This firmware is designed for any esp8266 module.
The recommended connections for an esp-01 module are:
- URXD: connect to TX of microcontroller
- UTXD: connect to RX of microcontroller
- GPIO0: connect to RESET of microcontroller
- GPIO2: optionally connect green LED to 3.3V (indicates wifi status)
The recommended connections for an esp-12 module are:
- URXD: connect to TX of microcontroller
- UTXD: connect to RX of microcontroller
- GPIO12: connect to RESET of microcontroller
- GPIO13: connect to ISP of LPC/ARM microcontroller or to GPIO0 of esp8266 being programmed
(not used with Arduino/AVR)
- GPIO0: optionally connect green "conn" LED to 3.3V (indicates wifi status)
- GPIO2: optionally connect yellow "ser" LED to 3.3V (indicates serial activity)
If your application has problems with the boot message that is output at ~74600 baud by the ROM
at boot time you can connect an esp-12 module as follows and choose the "swap_uart" pin assignment
in the esp-link web interface:
- GPIO13: connect to TX of microcontroller
- GPIO15: connect to RX of microcontroller
- GPIO1/UTXD: connect to RESET of microcontroller
- GPIO3/URXD: connect to ISP of LPC/ARM microcontroller or to GPIO0 of esp8266 being programmed
(not used with Arduino/AVR)
- GPIO0: optionally connect green "conn" LED to 3.3V (indicates wifi status)
- GPIO2: optionally connect yellow "ser" LED to 3.3V (indicates serial activity)
If you are using an FTDI connector, GPIO12 goes to DTR and GPIO13 goes to CTS (or vice-versa, I've
seen both used, sigh).
The GPIO pin assignments can be changed dynamically in the web UI and are saved in flash.
### Initial flashing
If you want to simply flash a pre-built firmware binary, you can download the latest
https://github.com/jeelabs/esp-link/releases[release] and use your favorite
ESP8266 flashing tool to flash the bootloader, the firmware, and blank settings.
Detailed instructions are provided in the release notes.
_Important_: the firmware adapts automatically to the size of the flash chip using information
stored in the boot sector (address 0). This is the standard way that the esp8266 SDK detects
the flash size. What this means is that you need to set this properly when you flash the bootloader.
If you use esptool.py you can do it using the -ff and -fs options.
### Wifi configuration overview
For proper operation the end state that esp-link needs to arrive at is to have it
join your pre-existing wifi network as a pure station.
However, in order to get there esp-link will start out as an access point and you'll have
to join its network to configure it. The short version is:
1. esp-link creates a wifi access point with an SSID of the form `ESP_012ABC` (some modules
use a different SSID form, such as `ai-thinker-012ABC`)
2. you join your laptop or phone to esp-link's network as a station and you configure
esp-link wifi with your network info by pointing your browser at `http://192.168.4.1/`
3. you set a hostname for esp-link on the "home" page, or leave the default ("esp-link")
4. esp-link starts to connect to your network while continuing to also be an access point
("AP+STA"), the esp-link may show up with a `${hostname}.local` hostname
(depends on your DHCP/DNS config)
4. esp-link succeeds in connecting and shuts down its own access point after 15 seconds,
you reconnect your laptop/phone to your normal network and access esp-link via its hostname
or IP address
### LED indicators
Assuming appropriate hardware attached to GPIO pins, the green "conn" LED will show the wifi
status as follows:
- Very short flash once a second: not connected to a network and running as AP+STA, i.e.
trying to connect to the configured network
- Very short flash once every two seconds: not connected to a network and running as AP-only
- Even on/off at 1HZ: connected to the configured network but no IP address (waiting on DHCP)
- Steady on with very short off every 3 seconds: connected to the configured network with an
IP address (esp-link shuts down its AP after 60 seconds)
The yellow "ser" LED will blink briefly every time serial data is sent or received by the esp-link.
### Troubleshooting
- verify that you have sufficient power, borderline power can cause the esp module to seemingly
function until it tries to transmit and the power rail collapses
- if you just cannot flash your esp8266 module (some people call it the zombie mode) make sure you
have gpio0 and gpio15 pulled to gnd with a 1K resistor, gpio2 tied to 3.3V with 1K resistor, and
RX/TX connected without anything in series. If you need to level shift the signal going into the
esp8266's RX use a 1K resistor. Use 115200 baud in the flasher.
(For a permanent set-up I would use higher resistor values but
when nothing seems to work these are the ones I try.)
- if the flashing succeeded, check the "conn" LED to see which mode esp-link is in (see LED info above)
- reset or power-cycle the esp-link to force it to become an access-point if it can't
connect to your network within 15-20 seconds
- if the LED says that esp-link is on your network but you can't get to it, make sure your
laptop is on the same network (and no longer on the esp's network)
- if you do not know the esp-link's IP address on your network, try `esp-link.local`, try to find
the lease in your DHCP server; if all fails, you may have to turn off your access point (or walk
far enough away) and reset/power-cycle esp-link, it will then fail to connect and start its
own AP after 15-20 seconds
Configuration details
---------------------
### Wifi
After you have serially flashed the module it will create a wifi access point (AP) with an
SSID of the form `ESP_012ABC` where 012ABC is a piece of the module's MAC address.
Using a laptop, phone, or tablet connect to this SSID and then open a browser pointed at
http://192.168.4.1/, you should then see the esp-link web site.
Now configure the wifi. The desired configuration is for the esp-link to be a
station on your local wifi network so you can communicate with it from all your computers.
To make this happen, navigate to the wifi page and you should see the esp-link scan
for available networks. You should then see a list of detected networks on the web page and you
can select yours.
Enter a password if your network is secure (highly recommended...) and hit the connect button.
You should now see that the esp-link has connected to your network and it should show you
its IP address. _Write it down_. You will then have to switch your laptop, phone, or tablet
back to your network and then you can connect to the esp-link's IP address or, depending on your
network's DHCP/DNS config you may be able to go to http://esp-link.local
At this point the esp-link will have switched to STA mode and be just a station on your
wifi network. These settings are stored in flash and thereby remembered through resets and
power cycles. They are also remembered when you flash new firmware. Only flashing `blank.bin`
via the serial port as indicated above will reset the wifi settings.
There is a fail-safe, which is that after a reset or a configuration change, if the esp-link
cannot connect to your network it will revert back to AP+STA mode after 15 seconds and thus
both present its `ESP_012ABC`-style network and continue trying to reconnect to the requested network.
You can then connect to the esp-link's AP and reconfigure the station part.
One open issue (#28) is that esp-link cannot always display the IP address it is getting to the browser
used to configure the ssid/password info. The problem is that the initial STA+AP mode may use
channel 1 and you configure it to connect to an AP on channel 6. This requires the ESP8266's AP
to also switch to channel 6 disconnecting you in the meantime.
### Hostname, description, DHCP, mDNS
You can set a hostname on the "home" page, this should be just the hostname and not a domain
name, i.e., something like "test-module-1" and not "test-module-1.mydomain.com".
This has a number of effects:
- you will see the first 12 chars of the hostname in the menu bar (top left of the page) so
if you have multiple modules you can distinguish them visually
- esp-link will use the hostname in its DHCP request, which allows you to identify the module's
MAC and IP addresses in your DHCP server (typ. your wifi router). In addition, some DHCP
servers will inject these names into the local DNS cache so you can use URLs like
`hostname.local`.
- someday, esp-link will inject the hostname into mDNS (multicast DNS, bonjour, etc...) so
URLs of the form `hostname.local` work for everyone (as of v2.1.beta5 mDNS is disabled due
to reliability issues with it)
You can also enter a description of up to 128 characters on the home page (bottom right). This
allows you to leave a memo for yourself, such as "installed in basement to control the heating
system". This descritpion is not used anywhere else.
Building the firmware
---------------------
### Linux
The firmware has been built using the https://github.com/pfalcon/esp-open-sdk[esp-open-sdk]
on a Linux system. Create an esp8266 directory, install the esp-open-sdk into a sub-directory
using the *non-standalone* install (i.e., there should not be an sdk directory in the esp-open-sdk
dir when done installing, if you use the standalone install you will get compilation errors
with std types, such as `uint32_t`).
Download the Espressif SDK (use the version mentioned in the release notes) from their
http://bbs.espressif.com/viewforum.php?f=5[download forum] and also expand it into a
sub-directory.
Clone the esp-link repository into a third sub-directory and check out the tag you would like,
such as `git checkout v2.1.7`.
This way the relative paths in the Makefile will work.
If you choose a different directory structure look at the Makefile for the appropriate environment
variables to define.
Do not use the source tarballs from the release page on github,
these will give you trouble compiling because the Makefile uses git to determine the esp-link
version being built.
In order to OTA-update the esp8266 you should `export ESP_HOSTNAME=...` with the hostname or
IP address of your module.
Now, build the code: `make` in the top-level of esp-link. If you want to se the commands being
issued, use `VERBOSE=1 make`.
A few notes from others (I can't fully verify these):
- You may need to install `zlib1g-dev` and `python-serial`
- Make sure you have the correct version of the esp_iot_sdk
- Make sure the paths at the beginning of the makefile are correct
- Make sure `esp-open-sdk/xtensa-lx106-elf/bin` is in the PATH set in the Makefile
### Windows
It is possible to build esp-link on Windows, but it requires a gaggle of software to be installed:
- Install the unofficial sdk, mingw, SourceTree (gui git client), python 2.7, git cli, Java
- Use SourceTree to checkout under C:\espressif or wherever you installed the unofficial sdk,
(see this thread for the unofficial sdk http://www.esp8266.com/viewtopic.php?t=820)
- Create a symbolic link under c:/espressif for the git bin directory under program files and
the java bin directory under program files.
- ...
### Updating the firmware over-the-air
This firmware supports over-the-air (OTA) flashing, so you do not have to deal with serial
flashing again after the initial one! The recommended way to flash is to use `make wiflash`
if you are also building the firmware.
If you are downloading firmware binaries use `./wiflash`.
`make wiflash` assumes that you set `ESP_HOSTNAME` to the hostname or IP address of your esp-link.
You can easily do that using something like `ESP_HOSTNAME=192.168.1.5 make wiflash`.
The flashing, restart, and re-associating with your wireless network takes about 15 seconds
and is fully automatic. The first 1MB of flash are divided into two 512KB partitions allowing for new
code to be uploaded into one partition while running from the other. This is the official
OTA upgrade method supported by the SDK, except that the firmware is POSTed to the module
using curl as opposed to having the module download it from a cloud server. On a module with
512KB flash there is only space for one partition and thus no way to do an OTA update.
If you are downloading the binary versions of the firmware (links forthcoming) you need to have
both `user1.bin` and `user2.bin` handy and run `wiflash.sh <esp-hostname> user1.bin user2.bin`.
This will query the esp-link for which file it needs, upload the file, and then reconnect to
ensure all is well.
Note that when you flash the firmware the wifi settings are all preserved so the esp-link should
reconnect to your network within a few seconds and the whole flashing process should take 15-30
from beginning to end. If you need to clear the wifi settings you need to reflash the `blank.bin`
using the serial port.
The flash configuration and the OTA upgrade process is described in more detail in [FLASH.md](FLASH.md)
Serial bridge and connections to Arduino, AVR, ARM, LPC microcontrollers
------------------------------------------------------------------------
In order to connect through the esp-link to a microcontroller use port 23. For example,
on linux you can use `nc esp-hostname 23` or `telnet esp-hostname 23`.
Note that multiple connections to port 23 and 2323 can be made simultaneously. Esp-link will
intermix characters received on all these connections onto the serial TX and it will
broadcast incoming characters from the serial RX to all connections. Use with caution!
### Flashing an attached AVR/Arduino
There are three options for reprogramming an attached AVR/Arduino microcontroller:
- Use avrdude and point it at port 23 of esp-link. Esp-link automatically detects the programming
sequence and issues a reset to the AVR.
- Use avrdude and point it at port 2323 of esp-link. This is the same as port 23 except that the
autodectection is not used and the reset happens because port 2323 is used
- Use curl or a similar tool to HTTP POST the firmware to esp-link. This uses the built-in
programmer, which only works for AVRs/Arduinos with the optiboot bootloader (which is std).
To reprogram an Arduino / AVR microcontroller by pointing avrdude at port 23 or 2323 you
specify a serial port of the form `net:esp-link:23` in avrdude's -P option, where
`esp-link` is either the hostname of your esp-link or its IP address).
This is instead of specifying a serial port of the form /dev/ttyUSB0.
Esp-link detects that avrdude starts its connection with a flash synchronization sequence
and sends a reset to the AVR microcontroller so it can switch into flash programming mode.
To reprogram using the HTTP POST method you need to first issue a POST to put optiboot into
programming mode: POST to `http://esp-link/pgm/sync`, this starts the process. Then check that
synchronization with optiboot has been achieved by issuing a GET to the same URL
(`http://esp-link/pgm/sync`). Repeat until you have sync (takes <500ms normally). Finally
issue a POST request to `http://esp-link/pgm/upload` with your hex file as POST data (raw,
not url-encoded or multipart-mime. Please look into the avrflash script for the curl command-line
details or use that script directly (`./avrflash esp-link.local my_sketch.hex`).
_Important_: after the initial sync request that resets the AVR you have 10 seconds to get to the
upload post or esp-link will time-out. So if you're manually entering curl commands have them
prepared so you can copy&paste!
Beware of the baud rate, which you can set on the uC Console page. Sometimes you may be using
115200 baud in sketches but the bootloader may use 57600 baud. When you use port 23 or 2323 you
need to set the baud rate correctly. If you use the built-in programmer (HTTP POST method) then
esp-link will try the configured baud rate and also 9600, 57600, and 115200 baud, so it should
work even if you have the wrong baud rate configured...
When to use which method? If port 23 works then go with that. If you have trouble getting sync
or it craps out in the middle too often then try the built-in programmer with the HTTP POST.
If your AVR doesn't use optiboot then use port 2323 since esp-link may not recognize the programming
sequence and not issue a reset if you use port 23.
If you are having trouble with the built-in programmer and see something like this:
--------------------
# ./avrflash 192.168.3.104 blink.hex
Error checking sync: FAILED to SYNC: abandoned after timeout, got:
:\xF/\x00\xCj\xCz\xCJ\xCZ\xC\xAÜ\xC\xAä\xC\xAÜ\xC\xAä\xC\xBì\xC\xBô\xC\xBì\xC\xBô\xC\xAÜ\xC\xAä\xC
--------------------
the most likely cause is a baud rate mismatch and/or a bad connection from the esp8266 to the
AVRs reset line.
The baud rate used by esp-link is set on the uC Console web page and, as mentioned above, it will
automatically try 9600, 57600, and 115200 as well.
The above garbage characters are most likely due to optiboot timing out and starting the sketch
and then the sketch sending data at a different baud rate than configured into esp-link.
Note that sketches don't necessarily use the same baud rate as optiboot, so you may have the
correct baud rate configured but reset isn't functioning, or reset may be functioning but the
baud rate may be incorrect.
The output of a successful flash using the built-in programmer looks like this:
--------------------
Success. 3098 bytes at 57600 baud in 0.8s, 3674B/s 63% efficient
--------------------
This says that the sketch comprises 3098 bytes of flash, was written in 0.8 seconds
(excludes the initial sync time) at 57600 baud,
and the 3098 bytes were flashed at a rate of 3674 bytes per second.
The efficiency measure is the ratio of the actual rate to the serial baud rate,
thus 3674/5760 = 0.63 (there are 10 baud per character).
The efficiency is not 100% because there is protocol overhead (such as sync, record type, and
length characters)
and there is dead time waiting for an ack or preparing the next record to be sent.
### Details of built-in AVR flash algorithm
The built-in flashing algorithm differs a bit from what avrdude does. The programming protocol
states that STK_GET_SYNC+CRC_EOP (0x30 0x20) should be sent to synchronize, but that works poorly
because the AVR's UART only buffers one character. This means that if STK_GET_SYNC+CRC_EOP is
sent twice there is a high chance that only the last character (CRC_EOP) is actually
received. If that is followed by another STK_GET_SYNC+CRC_EOP sequence then optiboot receives
CRC_EOP+STK_GET_SYNC+CRC_EOP which causes it to abort and run the old sketch. Ending up in that
situation is quite likely because optiboot initializes the UART as one of the first things, but
then goes off an flashes an LED for ~300ms during which it doesn't empty the UART.
Looking at the optiboot code, the good news is that CRC_EOP+CRC_EOP can be used to get an initial
response without the overrun danger of the normal sync sequence and this is what esp-link does.
The programming sequence runs as follows:
- esp-link sends a brief reset pulse (1ms)
- esp-link sends CRC_EOP+CRC_EOP ~50ms later
- esp-link sends CRC_EOP+CRC_EOP every ~70-80ms
- eventually optiboot responds with STK_INSYNC+STK_OK (0x14;0x10)
- esp-link sends one CRC_EOP to sort out the even/odd issue
- either optiboot responds with STK_INSYNC+STK_OK or nothing happens for 70-80ms, in which case
esp-link sends another CRC_EOP
- esp-link sends STK_GET_SYNC+CRC_EOP and optiboot responds with STK_INSYNC+STK_OK and we're in
sync now
- esp-link sends the next command (starts with 'u') and programming starts...
If no sync is achieved, esp-link changes baud rate and the whole thing starts over with a reset
pulse about 600ms, esp-link gives up after about 5 seconds and reports an error.
### Flashing an attached ARM processor
You can reprogram NXP's LPC800-series and many other ARM processors as well by pointing your
programmer similarly at the esp-link's port 23. For example, if you are using
https://github.com/jeelabs/embello/tree/master/tools/uploader a command line like
`uploader -t -s -w esp-link:23 build/firmware.bin` does the trick.
The way it works is that the uploader uses telnet protocol escape sequences in order to
make esp-link issue the appropriate "ISP" and reset sequence to the microcontroller to start the
flash programming. If you use a different ARM programming tool it will work as well as long as
it starts the connection with the `?\r\n` synchronization sequence.
### Flashing an attached esp8266
Yes, you can use esp-link running on one esp8266 module to flash another esp8266 module,
however it is rather tricky! The problem is not electric, it is wifi interference.
The basic idea is to use some method to direct the esp8266 flash program to port 2323 of
esp-link. Using port 2323 with the appropriate wiring will cause the esp8266's reset and
gpio0 pins to be toggled such that the chip enters the flash programming mode.
One option for connecting the programmer with esp-link is to use my version of esptool.py
at http://github.com/tve/esptool, which supports specifying a URL instead of a port. Thus
instead of specifying something like `--port /dev/ttyUSB0` or `--port COM1` you specify
`--port socket://esp-link.local:2323`. Important: the baud rate specified on the esptool.py
command-line is irrelevant as the baud rate used by esp-link will be the one set in the
uC console page. Fortunately the esp8266 bootloader does auto-baud detection. (Setting the
baud rate to 115200 is recommended.)
Another option is to use a serial-to-tcp port forwarding driver and point that to port 2323
of esp-link. On windows users have reported success with
http://www.hw-group.com/products/hw_vsp/hw_vsp2_en.html[HW Virtual Serial Port]
Now to the interference problem: once the attached esp8266 is reset it
starts outputting its 26Mhz clock on gpio0, which needs to be attached to
the esp8266 running esp-link (since it needs to drive gpio0 low during
the reset to enter flash mode). This 26Mhz signal on gpio0 causes a
significant amount of radio interference with the result that the esp8266
running esp-link has trouble receiving Wifi packets. You can observe this
by running a ping to esp-link in another window: as soon as the target
esp8266 is reset, the pings become very slow or stop altogetehr. As soon
as you remove power to the attached esp8266 the pings resume beautifully.
To try and get the interference under control, try some of the following:
add a series 100ohm resistor and 100pf capacitor to ground as close to
the gpio0 pin as possible (basically a low pass filter); and/or pass
the cable connecting the two esp8266's through a ferrite bead.
### Debug log
The esp-link web UI can display the esp-link debug log (os_printf statements in the code). This
is handy but sometimes not sufficient. Esp-link also prints the debug info to the UART where
it is sometimes more convenient and sometimes less... For this reason three UART debug log
modes are supported that can be set in the web UI (and the mode is saved in flash):
- auto: the UART log starts enabled at boot using uart0 and disables itself when esp-link
associates with an AP. It re-enables itself if the association is lost.
- off: the UART log is always off
- on0: the UART log is always on using uart0
- on1: the UART log is always on using uart1 (gpio2 pin)
Note that even if the UART log is always off the ROM prints to uart0 whenever the
esp8266 comes out of reset. This cannot be disabled.
Outbound HTTP REST requests and MQTT client
-------------------------------------------
The V2 versions of esp-link use the SLIP protocol over the serial link to support simple outbound
HTTP REST requests as well as an MQTT client. The SLIP protocol consists of commands with
binary arguments sent from the
attached microcontroller to the esp8266, which then performs the command and responds back.
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.
You can find REST and MQTT libraries as well as demo sketches in the
https://github.com/jeelabs/el-client[el-client] repository.
Contact
-------
If you find problems with esp-link, please create a github issue. If you have a question, please
use the gitter chat link at the top of this page.

@ -0,0 +1,175 @@
ESP-LINK: Wifi-Serial Bridge w/REST&MQTT
========================================
<img src="https://cloud.githubusercontent.com/assets/39480/19333951/73fcdcbe-90ad-11e6-8572-5e654377275a.png">
The esp-link firmware connects a micro-controller to the internet using an 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 and
LPC800-series and other ARM microcontrollers via Wifi
- built-in stk500v1 programmer for AVR uC's: program using HTTP upload of hex file
- outbound REST HTTP requests from the attached micro-controller to the internet
- MQTT client pub/sub from the attached micro-controller to the internet
- serve custom web pages containing data that is dynamically pulled from the attached uC and
that contain buttons and fields that are transmitted to the attached uC (feature not
fully ready yet)
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!
The REST and MQTT functionality are loosely based on [espduino](https://github.com/tuanpmt/espduino)
but significantly rewritten and no longer protocol compatible, thanks to tuanpmt for the
inspiration!
The following people contributed significant functionality to esp-link:
[brunnels](https://github.com/brunnels) (espduino integration),
[cskarai](https://github.com/cskarai) (custom dynamic web pages),
[beegee-tokyo](https://github.com/beegee-tokyo) (lots of code documentation),
[susisstrolch](https://github.com/susisstrolch) (syslog feature),
[bc547](https://github.com/bc547) and [katast](https://github.com/katast) (misc contributions).
Esp-link is the work of many contributors!
Note that http://github.com/jeelabs/esp-link is the original esp-link software which has
notably been forked by arduino.org as [Esp-Link](https://github.com/arduino-org/Esp-Link) and shipped
with the initial Arduino Uno Wifi. The JeeLabs esp-link has evolved significantly since the
fork and added cool new features as well as bug fixes.
### Quick links
In this document: [goals](#esp-link-goals), [uses](#esp-link-uses), [eye candy](#eye-candy),
[getting-started](#getting-started), [serial-bridge](#serial-bridge), [contact](#contact).
Separate documents:
- [hardware configuration](FLASHING.md), [serial flashing](FLASHING.md#initial-serial-flashing)
- [wifi configuration](WIFI-CONFIG.md)
- [troubleshooting](TROUBLESHOOTING.md), [LED indicators](TROUBLESHOOTING.md#led-indicators)
- [flashing an attached uC](UC-FLASHING.md)
- [MQTT and outbound REST requests](RESTMQTT.md)
- [service web pages](WEB-SERVER.md)
- [building esp-link](BUILDING.md), [over-the-air flashing](BUILDING.md#updating-the-firmware-over-the-air)
- [flash layout](FLASH.md)
For quick support and questions chat at
[![Chat at https://gitter.im/jeelabs/esp-link](https://badges.gitter.im/esp-link.svg)](https://gitter.im/jeelabs/esp-link)
or (a little slower) open a github issue.
Releases & Downloads
--------------------
Esp-link uses semantic versioning. The main change between versions 1.x and 2.x was the
addition of MQTT and outbound REST requests from the attached uC. The main change between 2.x
and 3.x will be the addition of custom web pages (this is not ready yet).
- The master branch is currently unstable as we integrate a number of new features to get
to version 3.0. Please use v2.2.3 unless you want to hack up the latest code!
This being said, the older functionality seems to work fine on master, YMMV...
- [V2.2.3](https://github.com/jeelabs/esp-link/releases/tag/v2.2.3) is the most recent release.
It has a built-in stk500v1 programmer (for AVRs), work on all modules, and supports mDNS,
sNTP, and syslog. It is built using the Espressif SDK 1.5.4.
- [V2.1.7](https://github.com/jeelabs/esp-link/releases/tag/v2.1.7) is the previous release.
- See [all releases](https://github.com/jeelabs/esp-link/releases).
## Esp-link goals
The goal of the esp-link project is to create an advanced Wifi co-processor. Esp-link assumes that
there is a "main processor" (also referred to as "attached uController") and that esp-link's role
is to facilitate communication over Wifi. This means that esp-link does not just connect TCP/UDP
sockets through to the attached uC, rather it implements mostly higher-level functionality to
offload the attached uC, which often has much less flash and memory than esp-link.
Where esp-link is a bit unusual is that it's not really
just a Wifi interface or a slave co-processor. In some sense it's the master, because the main
processor can be reset, controlled and reprogrammed through esp-link. The three main areas of
functionality in esp-link are:
- reprogramming and debugging the attached uC
- letting the attached uC make outbound communication and offloading the protocol processing
- forwarding inbound communication and offloading the protocol processing
The goal of the project is also to remain focused on the above mission. In particular, esp-link
is not a platform for stand-alone applications and it does not support connecting sensors or
actuators directly to it. A few users have taken esp-link as a starting point for doing these
things and that's great, but there's also value in keeping the mainline esp-link project
focused on a clear mission.
## Esp-link uses
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 that through the initial
callback registration.
## Eye Candy
These screen shots show the Home page, the Wifi configuration page, the console for the
attached microcontroller, and the pin assignments card:
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/19334029/f8128c92-90ad-11e6-804e-9a4796035e9a.png">
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/8261427/6caf7326-167f-11e5-8085-bc8b20159b2b.png">
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/8261426/6ca7f75e-167f-11e5-827d-9a1c582ad05d.png">
<img width="30%" src="https://cloud.githubusercontent.com/assets/39480/8261658/11e6c64a-1681-11e5-82d0-ea5ec90a6ddb.png">
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/19334011/e0c3fe40-90ad-11e6-9893-847e805e7b89.png">
<img width="45%" src="https://cloud.githubusercontent.com/assets/39480/19333988/c1858cec-90ad-11e6-8b1c-ffed516e1b7f.png">
Getting Started
---------------
To get started you need to:
1. prepare your esp8266 module for serial flashing
2. download the latest esp-link release image (you can build your own later)
3. flash the firmware
4. configure the Wifi in esp-link for your network
You can then attach a uC and upload a sketch:
1. attach a uC (e.g. arduino) to your esp8266 module
2. connect via the serial port to see a pre-loaded sketch running
3. upload a fresh version of the sketch
From there, more advanced steps are:
- write a sketch that uses MQTT to communicate, or that makes outbound REST requests
- create some web pages and write a sketch that populates data in them or reacts to buttons
and forms
- make changes or enhancements to esp-link and build your own firmware
### Serial bridge
In order to connect through the esp-link to a microcontroller use port 23. For example,
on linux you can use `nc esp-hostname 23` or `telnet esp-hostname 23`.
The connections on port 23 and 2323 have a 5 minute inactivity timeout. This is standard with
Espressif's SDK and esp-link does not change it. The reason is that due to memory limitations only a
few connections can be open (4 per port) and it's easy for connections to get "lost" staying open
forever, for example, due to wifi disconnects. That could easily make it impossible to connect to
esp-link due to connection exhaustion. Something smarter is most likely possible...
Note that multiple connections to port 23 and 2323 can be made simultaneously. Esp-link will
intermix characters received on all these connections onto the serial TX and it will
broadcast incoming characters from the serial RX to all connections. Use with caution!
If you are using esp-link to connect to the console of a linux system, such as an rPi, you
will most likely see what you typed being echoed twice. If you are on a linux system use
telnet and issue a `mode char` command (in telnet, hit the escape char `^]` and type `mode
char` at the prompt). If you are using putty on Windows, open the connection settings and
in the terminal settings set both `local echo` and `local line editing` to `off`.
Contact
-------
If you find problems with esp-link, please create a github issue. If you have a question, please
use the gitter chat link at the top of this page.

@ -0,0 +1,14 @@
Esp-link: Outbound HTTP REST requests and MQTT client
-------------------------------------------
The V2 versions of esp-link use the SLIP protocol over the serial link to support simple outbound
HTTP REST requests as well as an MQTT client. The SLIP protocol consists of commands with
binary arguments sent from the
attached microcontroller to the esp8266, which then performs the command and responds back.
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.
You can find REST and MQTT libraries as well as demo sketches in the
[el-client](https://github.com/jeelabs/el-client) repository.

@ -0,0 +1,36 @@
Esp-Link troubleshooting
========================
### Troubleshooting
- verify that you have sufficient power, borderline power can cause the esp module to seemingly
function until it tries to transmit and the power rail collapses
- if you just cannot flash your esp8266 module (some people call it the zombie mode) make sure you
have gpio0 and gpio15 pulled to gnd with a 1K resistor, gpio2 tied to 3.3V with 1K resistor, and
RX/TX connected without anything in series. If you need to level shift the signal going into the
esp8266's RX use a 1K resistor. Use 115200 baud in the flasher.
(For a permanent set-up I would use higher resistor values but
when nothing seems to work these are the ones I try.)
- if the flashing succeeded, check the "conn" LED to see which mode esp-link is in (see LED info above)
- reset or power-cycle the esp-link to force it to become an access-point if it can't
connect to your network within 15-20 seconds
- if the LED says that esp-link is on your network but you can't get to it, make sure your
laptop is on the same network (and no longer on the esp's network)
- if you do not know the esp-link's IP address on your network, try `esp-link.local`, try to find
the lease in your DHCP server; if all fails, you may have to turn off your access point (or walk
far enough away) and reset/power-cycle esp-link, it will then fail to connect and start its
own AP after 15-20 seconds
### LED indicators
Assuming appropriate hardware attached to GPIO pins, the green "conn" LED will show the wifi
status as follows:
- Very short flash once a second: not connected to a network and running as AP+STA, i.e.
trying to connect to the configured network
- Very short flash once every two seconds: not connected to a network and running as AP-only
- Even on/off at 1HZ: connected to the configured network but no IP address (waiting on DHCP)
- Steady on with very short off every 3 seconds: connected to the configured network with an
IP address (esp-link shuts down its AP after 60 seconds)
The yellow "ser" LED will blink briefly every time serial data is sent or received by the esp-link.

@ -0,0 +1,183 @@
Flashing an attached Microcontroller
====================================
In order to connect through the esp-link to a microcontroller use port 23. For example,
on linux you can use `nc esp-hostname 23` or `telnet esp-hostname 23`.
Note that multiple connections to port 23 and 2323 can be made simultaneously. Esp-link will
intermix characters received on all these connections onto the serial TX and it will
broadcast incoming characters from the serial RX to all connections. Use with caution!
### Flashing an attached AVR/Arduino
There are multiple options for reprogramming an attached AVR/Arduino microcontroller:
- Use avrdude and point it at port 23 of esp-link. Esp-link automatically detects the programming
sequence and issues a reset to the AVR.
- Use avrdude and point it at port 2323 of esp-link. This is the same as port 23 except that the
autodectection is not used and the reset happens because port 2323 is used
- Use curl or a similar tool to HTTP POST the firmware to esp-link. This uses the built-in
programmer, which only works for AVRs/Arduinos with the optiboot bootloader (which is std).
- Use some serial port forwarding software, such as com2com, or hwvsp (you have to uncheck
nvt in the settings when using the latter).
To reprogram an Arduino / AVR microcontroller by pointing avrdude at port 23 or 2323 you
specify a serial port of the form `net:esp-link:23` in avrdude's -P option, where
`esp-link` is either the hostname of your esp-link or its IP address).
This is instead of specifying a serial port of the form /dev/ttyUSB0.
Esp-link detects that avrdude starts its connection with a flash synchronization sequence
and sends a reset to the AVR microcontroller so it can switch into flash programming mode.
Note for Windows users: very recent avrdude versions on Windows support the -P option, while older
ones don't. See the second-to-last bullet in the
[avrdude 6.3 release notes]http://savannah.nongnu.org/forum/forum.php?forum_id=8461).
To reprogram using the HTTP POST method you need to first issue a POST to put optiboot into
programming mode: POST to `http://esp-link/pgm/sync`, this starts the process. Then check that
synchronization with optiboot has been achieved by issuing a GET to the same URL
(`http://esp-link/pgm/sync`). Repeat until you have sync (takes <500ms normally). Finally
issue a POST request to `http://esp-link/pgm/upload` with your hex file as POST data (raw,
not url-encoded or multipart-mime. Please look into the avrflash script for the curl command-line
details or use that script directly (`./avrflash esp-link.local my_sketch.hex`).
_Important_: after the initial sync request that resets the AVR you have 10 seconds to get to the
upload post or esp-link will time-out. So if you're manually entering curl commands have them
prepared so you can copy&paste!
Beware of the baud rate, which you can set on the uC Console page. Sometimes you may be using
115200 baud in sketches but the bootloader may use 57600 baud. When you use port 23 or 2323 you
need to set the baud rate correctly. If you use the built-in programmer (HTTP POST method) then
esp-link will try the configured baud rate and also 9600, 57600, and 115200 baud, so it should
work even if you have the wrong baud rate configured...
When to use which method? If port 23 works then go with that. If you have trouble getting sync
or it craps out in the middle too often then try the built-in programmer with the HTTP POST.
If your AVR doesn't use optiboot then use port 2323 since esp-link may not recognize the programming
sequence and not issue a reset if you use port 23.
If you are having trouble with the built-in programmer and see something like this:
```
# ./avrflash 192.168.3.104 blink.hex
Error checking sync: FAILED to SYNC: abandoned after timeout, got:
:\xF/\x00\xCj\xCz\xCJ\xCZ\xC\xAÜ\xC\xAä\xC\xAÜ\xC\xAä\xC\xBì\xC\xBô\xC\xBì\xC\xBô\xC\xAÜ\xC\xAä\xC
```
the most likely cause is a baud rate mismatch and/or a bad connection from the esp8266 to the
AVRs reset line.
The baud rate used by esp-link is set on the uC Console web page and, as mentioned above, it will
automatically try 9600, 57600, and 115200 as well.
The above garbage characters are most likely due to optiboot timing out and starting the sketch
and then the sketch sending data at a different baud rate than configured into esp-link.
Note that sketches don't necessarily use the same baud rate as optiboot, so you may have the
correct baud rate configured but reset isn't functioning, or reset may be functioning but the
baud rate may be incorrect.
The output of a successful flash using the built-in programmer looks like this:
```
Success. 3098 bytes at 57600 baud in 0.8s, 3674B/s 63% efficient
```
This says that the sketch comprises 3098 bytes of flash, was written in 0.8 seconds
(excludes the initial sync time) at 57600 baud,
and the 3098 bytes were flashed at a rate of 3674 bytes per second.
The efficiency measure is the ratio of the actual rate to the serial baud rate,
thus 3674/5760 = 0.63 (there are 10 baud per character).
The efficiency is not 100% because there is protocol overhead (such as sync, record type, and
length characters)
and there is dead time waiting for an ack or preparing the next record to be sent.
### Details of built-in AVR flash algorithm
The built-in flashing algorithm differs a bit from what avrdude does. The programming protocol
states that STK_GET_SYNC+CRC_EOP (0x30 0x20) should be sent to synchronize, but that works poorly
because the AVR's UART only buffers one character. This means that if STK_GET_SYNC+CRC_EOP is
sent twice there is a high chance that only the last character (CRC_EOP) is actually
received. If that is followed by another STK_GET_SYNC+CRC_EOP sequence then optiboot receives
CRC_EOP+STK_GET_SYNC+CRC_EOP which causes it to abort and run the old sketch. Ending up in that
situation is quite likely because optiboot initializes the UART as one of the first things, but
then goes off an flashes an LED for ~300ms during which it doesn't empty the UART.
Looking at the optiboot code, the good news is that CRC_EOP+CRC_EOP can be used to get an initial
response without the overrun danger of the normal sync sequence and this is what esp-link does.
The programming sequence runs as follows:
- esp-link sends a brief reset pulse (1ms)
- esp-link sends CRC_EOP+CRC_EOP ~50ms later
- esp-link sends CRC_EOP+CRC_EOP every ~70-80ms
- eventually optiboot responds with STK_INSYNC+STK_OK (0x14;0x10)
- esp-link sends one CRC_EOP to sort out the even/odd issue
- either optiboot responds with STK_INSYNC+STK_OK or nothing happens for 70-80ms, in which case
esp-link sends another CRC_EOP
- esp-link sends STK_GET_SYNC+CRC_EOP and optiboot responds with STK_INSYNC+STK_OK and we're in
sync now
- esp-link sends the next command (starts with 'u') and programming starts...
If no sync is achieved, esp-link changes baud rate and the whole thing starts over with a reset
pulse about 600ms, esp-link gives up after about 5 seconds and reports an error.
### Flashing an attached ARM processor
You can reprogram NXP's LPC800-series and many other ARM processors as well by pointing your
programmer similarly at the esp-link's port 23. For example, if you are using
https://github.com/jeelabs/embello/tree/master/tools/uploader a command line like
`uploader -t -s -w esp-link:23 build/firmware.bin` does the trick.
The way it works is that the uploader uses telnet protocol escape sequences in order to
make esp-link issue the appropriate "ISP" and reset sequence to the microcontroller to start the
flash programming. If you use a different ARM programming tool it will work as well as long as
it starts the connection with the `?\r\n` synchronization sequence.
### Flashing an attached esp8266
__Flashing another esp8266 module is possible in theory but real-world attempts have so far been
rather unsuccessful due to Wifi interference. This section is left here in case someone else
wants to dig in and find a solution.__
You can use esp-link running on one esp8266 module to flash another esp8266 module,
however it is rather tricky! The problem is not electric, it is wifi interference.
The basic idea is to use some method to direct the esp8266 flash program to port 2323 of
esp-link. Using port 2323 with the appropriate wiring will cause the esp8266's reset and
gpio0 pins to be toggled such that the chip enters the flash programming mode.
One option for connecting the programmer with esp-link is to use my version of esptool.py
at http://github.com/tve/esptool, which supports specifying a URL instead of a port. Thus
instead of specifying something like `--port /dev/ttyUSB0` or `--port COM1` you specify
`--port socket://esp-link.local:2323`. Important: the baud rate specified on the esptool.py
command-line is irrelevant as the baud rate used by esp-link will be the one set in the
uC console page. Fortunately the esp8266 bootloader does auto-baud detection. (Setting the
baud rate to 115200 is recommended.)
Another option is to use a serial-to-tcp port forwarding driver and point that to port 2323
of esp-link. On windows users have reported success with
http://www.hw-group.com/products/hw_vsp/hw_vsp2_en.html[HW Virtual Serial Port]
Now to the interference problem: once the attached esp8266 is reset it
starts outputting its 26Mhz clock on gpio0, which needs to be attached to
the esp8266 running esp-link (since it needs to drive gpio0 low during
the reset to enter flash mode). This 26Mhz signal on gpio0 causes a
significant amount of radio interference with the result that the esp8266
running esp-link has trouble receiving Wifi packets. You can observe this
by running a ping to esp-link in another window: as soon as the target
esp8266 is reset, the pings become very slow or stop altogetehr. As soon
as you remove power to the attached esp8266 the pings resume beautifully.
To try and get the interference under control, try some of the following:
add a series 100ohm resistor and 100pf capacitor to ground as close to
the gpio0 pin as possible (basically a low pass filter); and/or pass
the cable connecting the two esp8266's through a ferrite bead.
### Debug log
The esp-link web UI can display the esp-link debug log (os_printf statements in the code). This
is handy but sometimes not sufficient. Esp-link also prints the debug info to the UART where
it is sometimes more convenient and sometimes less... For this reason three UART debug log
modes are supported that can be set in the web UI (and the mode is saved in flash):
- auto: the UART log starts enabled at boot using uart0 and disables itself when esp-link
associates with an AP. It re-enables itself if the association is lost.
- off: the UART log is always off
- on0: the UART log is always on using uart0
- on1: the UART log is always on using uart1 (gpio2 pin)
Note that even if the UART log is always off the ROM prints to uart0 whenever the
esp8266 comes out of reset. This cannot be disabled.

@ -0,0 +1,69 @@
ESP-LINK web-server tutorial
============================
Video
--------------------
https://www.youtube.com/watch?v=vBESCO0UhYI
Installing el-client Arduino library
--------------------
Download and install ELClient library.
https://github.com/jeelabs/el-client
LED flashing sample
--------------------
Circuit:
- 1: connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO:
(RX - levelshifter - TX, TX - levelshifter - RX)
- 2: optionally connect RESET-s with a level shifter
Installation steps:
- 1: open webserver_led ELClient sample file in Arduino
- 2: upload the code onto an Arduino Nano/Uno
- 3: open the Web Server page on esp-link UI
- 4: upload LED.html from webserver_led ( ELCient/examples/webserver_led/LED.html )
- 5: choose LED page on esp-link UI
- 6: turn on/off the LED
HTML controls sample
--------------------------
Circuit:
- 1: connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO:
(RX - levelshifter - TX, TX - levelshifter - RX)
- 2: optionally connect RESET-s with a level shifter
- 3: add a trimmer to A0 for voltage measurement
Installation steps:
- 1: open webserver_controls ELClient sample file in Arduino
- 2: upload the code onto an Arduino Nano/Uno
- 3: open the Web Server page on esp-link UI
- 4: upload the 3 HTML files from webserver_controls ( select multiple htmls from ELCient/examples/webserver_controls/ )
- 5: jump to LED/User/Voltage pages
- 6: try out different settings
Supported HTML controls
--------------------
HTML&nbsp;control&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | Value Type | Description | Form Submission |
------------------------ | ----- | ------------ | ------- |
&lt;p id="id"/&gt; <br/> &lt;div id="id"/&gt; <br/> &lt;tr id="id"/&gt; <br/> &lt;th id="id"/&gt; <br/> &lt;td id="id"/&gt; <br/> &lt;textarea id="id"/&gt; | String&nbsp;(HTML) | MCU can replace the inner HTML part of the control at LOAD/REFRESH queries. The string (sent by MCU) is handled as HTML, so &lt;img...&gt; will be displayed as an image on the page | NO |
&lt;button id="id"/&gt; | String | When button is pressed, a message is transmitted to MCU containing the id (BUTTON_PRESS) | NO |
&lt;input name="id"/&gt; | String <br/> Integer <br/> Float <br/> Boolean | MCU can replace the value or checked properties of the HTML control in the form (LOAD/REFRESH). At form submission, the content of value will be transmitted to MCU (SET_FIELD). | YES |
&lt;select name="id"/&gt; | String | MCU can choose a value from the drop down (LOAD/REFRESH). At form submission the currently selected value will be transmitted to MCU (SET_FIELD) | YES |
&lt;ul id="id"/&gt; <br/> &lt;ol id="id"/&gt; | JSON list <br/> ["1","2","3"] | MCU can send a JSON list which is transformed to an HTML list ( &lt;li/&gt; ) (LOAD/REFRESH) | NO |
&lt;table id="id"/&gt; | JSON table <br/> [["1","2"], <br/> ["3","4"]] | MCU sends a JSON table which is transformed to an HTML table (LOAD/REFRESH) | NO |

@ -0,0 +1,87 @@
Esp-link Wifi configuration
===========================
For proper operation the end state that esp-link needs to arrive at is to have it
join your pre-existing wifi network as a pure station.
However, in order to get there esp-link will start out as an access point and you'll have
to join its network to configure it. The short version is:
1. esp-link creates a wifi access point with an SSID of the form `ESP_012ABC` (some modules
use a different SSID form, such as `ai-thinker-012ABC`)
2. you join your laptop or phone to esp-link's network as a station and you configure
esp-link wifi with your network info by pointing your browser at `http://192.168.4.1/`
3. you set a hostname for esp-link on the "home" page, or leave the default ("esp-link")
4. esp-link starts to connect to your network while continuing to also be an access point
("AP+STA"), the esp-link may show up with a `${hostname}.local` hostname
(depends on your DHCP/DNS config)
4. esp-link succeeds in connecting and shuts down its own access point after 15 seconds,
you reconnect your laptop/phone to your normal network and access esp-link via its hostname
or IP address
### Notes on using AP (access point) mode
Esp-link does not support STA+AP mode, however it does support STA mode and AP mode. What happens
is that STA+AP mode is used at boot and when making STA changes to allow for recovery: the AP
mode stays on for a while so you can connect to it and fix the STA mode. Once STA has connected,
esp-link switches to STA-only mode. There is no setting to stay in STA+AP mode. So... if you want
to use AP ensure you set esp-link to AP-only mode. If you want STA+AP mode you're gonna have to
modify the source for yourself. (This stuff is painful to test and rather tricky, so don't expect
the way it works to change.)
Configuration details
---------------------
### Wifi
After you have serially flashed the module it will create a wifi access point (AP) with an
SSID of the form `ESP_012ABC` where 012ABC is a piece of the module's MAC address.
Using a laptop, phone, or tablet connect to this SSID and then open a browser pointed at
http://192.168.4.1/, you should then see the esp-link web site.
Now configure the wifi. The desired configuration is for the esp-link to be a
station on your local wifi network so you can communicate with it from all your computers.
To make this happen, navigate to the wifi page and you should see the esp-link scan
for available networks. You should then see a list of detected networks on the web page and you
can select yours.
Enter a password if your network is secure (highly recommended...) and hit the connect button.
You should now see that the esp-link has connected to your network and it should show you
its IP address. _Write it down_. You will then have to switch your laptop, phone, or tablet
back to your network and then you can connect to the esp-link's IP address or, depending on your
network's DHCP/DNS config you may be able to go to http://esp-link.local
At this point the esp-link will have switched to STA mode and be just a station on your
wifi network. These settings are stored in flash and thereby remembered through resets and
power cycles. They are also remembered when you flash new firmware. Only flashing `blank.bin`
via the serial port as indicated above will reset the wifi settings.
There is a fail-safe, which is that after a reset or a configuration change, if the esp-link
cannot connect to your network it will revert back to AP+STA mode after 15 seconds and thus
both present its `ESP_012ABC`-style network and continue trying to reconnect to the requested network.
You can then connect to the esp-link's AP and reconfigure the station part.
One open issue (#28) is that esp-link cannot always display the IP address it is getting to the browser
used to configure the ssid/password info. The problem is that the initial STA+AP mode may use
channel 1 and you configure it to connect to an AP on channel 6. This requires the ESP8266's AP
to also switch to channel 6 disconnecting you in the meantime.
### Hostname, description, DHCP, mDNS
You can set a hostname on the "home" page, this should be just the hostname and not a domain
name, i.e., something like "test-module-1" and not "test-module-1.mydomain.com".
This has a number of effects:
- you will see the first 12 chars of the hostname in the menu bar (top left of the page) so
if you have multiple modules you can distinguish them visually
- esp-link will use the hostname in its DHCP request, which allows you to identify the module's
MAC and IP addresses in your DHCP server (typ. your wifi router). In addition, some DHCP
servers will inject these names into the local DNS cache so you can use URLs like
`hostname.local`.
- someday, esp-link will inject the hostname into mDNS (multicast DNS, bonjour, etc...) so
URLs of the form `hostname.local` work for everyone (as of v2.1.beta5 mDNS is disabled due
to reliability issues with it)
You can also enter a description of up to 128 characters on the home page (bottom right). This
allows you to leave a memo for yourself, such as "installed in basement to control the heating
system". This descritpion is not used anywhere else.

@ -13,8 +13,6 @@
#define DBG(format, ...) do { } while(0) #define DBG(format, ...) do { } while(0)
#endif #endif
extern const CmdList commands[];
//===== ESP -> Serial responses //===== ESP -> Serial responses
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
@ -128,7 +126,11 @@ cmdParsePacket(uint8_t *buf, short len) {
} }
#endif #endif
if (data_ptr <= data_limit) { if (!cmdInSync && packet->cmd != CMD_SYNC) {
// we have not received a sync, perhaps we reset? Tell MCU to do a sync
cmdResponseStart(CMD_SYNC, 0, 0);
cmdResponseEnd();
} else if (data_ptr <= data_limit) {
cmdExec(commands, packet); cmdExec(commands, packet);
} else { } else {
DBG("cmdParsePacket: packet length overrun, parsing arg %d\n", packet->argc); DBG("cmdParsePacket: packet length overrun, parsing arg %d\n", packet->argc);

@ -6,6 +6,9 @@
#define CMD_H #define CMD_H
#include <esp8266.h> #include <esp8266.h>
// keep track of whether we received a sync command from uC
extern bool cmdInSync;
// Standard SLIP escape chars from RFC // Standard SLIP escape chars from RFC
#define SLIP_END 0300 // indicates end of packet #define SLIP_END 0300 // indicates end of packet
#define SLIP_ESC 0333 // indicates byte stuffing #define SLIP_ESC 0333 // indicates byte stuffing
@ -45,9 +48,16 @@ typedef enum {
CMD_MQTT_SUBSCRIBE, // subscribe to a topic CMD_MQTT_SUBSCRIBE, // subscribe to a topic
CMD_MQTT_LWT, // set the last-will-topic and messge CMD_MQTT_LWT, // set the last-will-topic and messge
CMD_REST_SETUP = 20, CMD_REST_SETUP = 20, // set-up callbacks
CMD_REST_REQUEST, CMD_REST_REQUEST, // do REST request
CMD_REST_SETHEADER, CMD_REST_SETHEADER, // define header
CMD_WEB_SETUP = 30, // set-up WEB callback
CMD_WEB_DATA, // WEB data from MCU
CMD_SOCKET_SETUP = 40, // set-up callbacks
CMD_SOCKET_SEND, // send data over UDP socket
} CmdName; } CmdName;
typedef void (*cmdfunc_t)(CmdPacket *cmd); typedef void (*cmdfunc_t)(CmdPacket *cmd);
@ -58,6 +68,9 @@ typedef struct {
cmdfunc_t sc_function; // pointer to function cmdfunc_t sc_function; // pointer to function
} CmdList; } CmdList;
// command dispatch table
extern const CmdList commands[];
#define CMD_CBNLEN 16 #define CMD_CBNLEN 16
typedef struct { typedef struct {
char name[CMD_CBNLEN]; char name[CMD_CBNLEN];

@ -3,7 +3,9 @@
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh // Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
#include "esp8266.h" #include "esp8266.h"
#include "sntp.h"
#include "cmd.h" #include "cmd.h"
#include "uart.h"
#include <cgiwifi.h> #include <cgiwifi.h>
#ifdef MQTT #ifdef MQTT
#include <mqtt_cmd.h> #include <mqtt_cmd.h>
@ -11,6 +13,10 @@
#ifdef REST #ifdef REST
#include <rest.h> #include <rest.h>
#endif #endif
#include <web-server.h>
#ifdef SOCKET
#include <socket.h>
#endif
#ifdef CMD_DBG #ifdef CMD_DBG
#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
@ -21,11 +27,15 @@
static void cmdNull(CmdPacket *cmd); static void cmdNull(CmdPacket *cmd);
static void cmdSync(CmdPacket *cmd); static void cmdSync(CmdPacket *cmd);
static void cmdWifiStatus(CmdPacket *cmd); static void cmdWifiStatus(CmdPacket *cmd);
static void cmdGetTime(CmdPacket *cmd);
static void cmdAddCallback(CmdPacket *cmd); static void cmdAddCallback(CmdPacket *cmd);
// keep track of last status sent to uC so we can notify it when it changes // keep track of last status sent to uC so we can notify it when it changes
static uint8_t lastWifiStatus = wifiIsDisconnected; static uint8_t lastWifiStatus = wifiIsDisconnected;
// keep track of whether we have registered our cb handler with the wifi subsystem
static bool wifiCbAdded = false; static bool wifiCbAdded = false;
// keep track of whether we received a sync command from uC
bool cmdInSync = false;
// Command dispatch table for serial -> ESP commands // Command dispatch table for serial -> ESP commands
const CmdList commands[] = { const CmdList commands[] = {
@ -33,6 +43,7 @@ const CmdList commands[] = {
{CMD_SYNC, "SYNC", cmdSync}, // synchronize {CMD_SYNC, "SYNC", cmdSync}, // synchronize
{CMD_WIFI_STATUS, "WIFI_STATUS", cmdWifiStatus}, {CMD_WIFI_STATUS, "WIFI_STATUS", cmdWifiStatus},
{CMD_CB_ADD, "ADD_CB", cmdAddCallback}, {CMD_CB_ADD, "ADD_CB", cmdAddCallback},
{CMD_GET_TIME, "GET_TIME", cmdGetTime},
#ifdef MQTT #ifdef MQTT
{CMD_MQTT_SETUP, "MQTT_SETUP", MQTTCMD_Setup}, {CMD_MQTT_SETUP, "MQTT_SETUP", MQTTCMD_Setup},
{CMD_MQTT_PUBLISH, "MQTT_PUB", MQTTCMD_Publish}, {CMD_MQTT_PUBLISH, "MQTT_PUB", MQTTCMD_Publish},
@ -43,12 +54,18 @@ const CmdList commands[] = {
{CMD_REST_SETUP, "REST_SETUP", REST_Setup}, {CMD_REST_SETUP, "REST_SETUP", REST_Setup},
{CMD_REST_REQUEST, "REST_REQ", REST_Request}, {CMD_REST_REQUEST, "REST_REQ", REST_Request},
{CMD_REST_SETHEADER, "REST_SETHDR", REST_SetHeader}, {CMD_REST_SETHEADER, "REST_SETHDR", REST_SetHeader},
#endif
{CMD_WEB_SETUP, "WEB_SETUP", WEB_Setup},
{CMD_WEB_DATA, "WEB_DATA", WEB_Data},
#ifdef SOCKET
{CMD_SOCKET_SETUP, "SOCKET_SETUP", SOCKET_Setup},
{CMD_SOCKET_SEND, "SOCKET_SEND", SOCKET_Send},
#endif #endif
}; };
//===== List of registered callbacks (to uC) //===== List of registered callbacks (to uC)
// WifiCb plus 10 for sensors // WifiCb plus 10 for other stuff
#define MAX_CALLBACKS 12 #define MAX_CALLBACKS 12
CmdCallback callbacks[MAX_CALLBACKS]; // cleared in cmdSync CmdCallback callbacks[MAX_CALLBACKS]; // cleared in cmdSync
@ -113,6 +130,7 @@ cmdNull(CmdPacket *cmd) {
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
cmdSync(CmdPacket *cmd) { cmdSync(CmdPacket *cmd) {
CmdRequest req; CmdRequest req;
uart0_write_char(SLIP_END); // prefix with a SLIP END to ensure we get a clean start
cmdRequest(&req, cmd); cmdRequest(&req, cmd);
if(cmd->argc != 0 || cmd->value == 0) { if(cmd->argc != 0 || cmd->value == 0) {
cmdResponseStart(CMD_RESP_V, 0, 0); cmdResponseStart(CMD_RESP_V, 0, 0);
@ -123,6 +141,8 @@ cmdSync(CmdPacket *cmd) {
// clear callbacks table // clear callbacks table
os_memset(callbacks, 0, sizeof(callbacks)); os_memset(callbacks, 0, sizeof(callbacks));
// TODO: call other protocols back to tell them to reset
// register our callback with wifi subsystem // register our callback with wifi subsystem
if (!wifiCbAdded) { if (!wifiCbAdded) {
wifiAddStateChangeCb(cmdWifiCb); wifiAddStateChangeCb(cmdWifiCb);
@ -132,6 +152,7 @@ cmdSync(CmdPacket *cmd) {
// send OK response // send OK response
cmdResponseStart(CMD_RESP_V, cmd->value, 0); cmdResponseStart(CMD_RESP_V, cmd->value, 0);
cmdResponseEnd(); cmdResponseEnd();
cmdInSync = true;
// save the MCU's callback and trigger an initial callback // save the MCU's callback and trigger an initial callback
cmdAddCb("wifiCb", cmd->value); cmdAddCb("wifiCb", cmd->value);
@ -149,6 +170,13 @@ cmdWifiStatus(CmdPacket *cmd) {
return; return;
} }
// Command handler for time
static void ICACHE_FLASH_ATTR
cmdGetTime(CmdPacket *cmd) {
cmdResponseStart(CMD_RESP_V, sntp_get_current_timestamp(), 0);
cmdResponseEnd();
return;
}
// Command handler to add a callback to the named-callbacks list, this is for a callback to the uC // Command handler to add a callback to the named-callbacks list, this is for a callback to the uC
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR

@ -16,6 +16,7 @@ Some random cgi routines.
#include <esp8266.h> #include <esp8266.h>
#include "cgi.h" #include "cgi.h"
#include "config.h" #include "config.h"
#include "web-server.h"
#ifdef CGI_DBG #ifdef CGI_DBG
#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
@ -193,8 +194,7 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[1024]; char buff[1024];
// don't use jsonHeader so the response does get cached // don't use jsonHeader so the response does get cached
httpdStartResponse(connData, 200); noCacheHeaders(connData, 200);
httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate");
httpdHeader(connData, "Content-Type", "application/json"); httpdHeader(connData, "Content-Type", "application/json");
httpdEndHeaders(connData); httpdEndHeaders(connData);
// limit hostname to 12 chars // limit hostname to 12 chars
@ -213,12 +213,15 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) {
#ifdef MQTT #ifdef MQTT
"\"REST/MQTT\", \"/mqtt.html\", " "\"REST/MQTT\", \"/mqtt.html\", "
#endif #endif
"\"Debug log\", \"/log.html\"" "\"Debug log\", \"/log.html\","
"\"Upgrade Firmware\", \"/flash.html\","
"\"Web Server\", \"/web-server.html\""
"%s"
" ], " " ], "
"\"version\": \"%s\", " "\"version\": \"%s\", "
"\"name\": \"%s\"" "\"name\": \"%s\""
" }", " }",
esp_link_version, name); WEB_UserPages(), esp_link_version, name);
httpdSend(connData, buff, -1); httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;

@ -95,11 +95,11 @@ int ICACHE_FLASH_ATTR cgiMqttSet(HttpdConnData *connData) {
if (mqtt_server < 0) return HTTPD_CGI_DONE; if (mqtt_server < 0) return HTTPD_CGI_DONE;
mqtt_server |= getBoolArg(connData, "mqtt-clean-session", mqtt_server |= getBoolArg(connData, "mqtt-clean-session",
(bool *)&flashConfig.mqtt_clean_session); &flashConfig.mqtt_clean_session);
if (mqtt_server < 0) return HTTPD_CGI_DONE; if (mqtt_server < 0) return HTTPD_CGI_DONE;
int8_t mqtt_en_chg = getBoolArg(connData, "mqtt-enable", int8_t mqtt_en_chg = getBoolArg(connData, "mqtt-enable",
(bool *)&flashConfig.mqtt_enable); &flashConfig.mqtt_enable);
char buff[16]; char buff[16];
@ -134,25 +134,28 @@ int ICACHE_FLASH_ATTR cgiMqttSet(HttpdConnData *connData) {
mqtt_client_init(); mqtt_client_init();
// if just enable changed we just need to bounce the client // if just enable changed we just need to bounce the client
} } else if (mqtt_en_chg > 0) {
else if (mqtt_en_chg > 0) {
DBG("MQTT server enable=%d changed\n", flashConfig.mqtt_enable); DBG("MQTT server enable=%d changed\n", flashConfig.mqtt_enable);
if (flashConfig.mqtt_enable && strlen(flashConfig.mqtt_host) > 0) if (flashConfig.mqtt_enable && strlen(flashConfig.mqtt_host) > 0) {
MQTT_Free(&mqttClient); // safe even if not connected
mqtt_client_init();
MQTT_Reconnect(&mqttClient); MQTT_Reconnect(&mqttClient);
else } else {
MQTT_Disconnect(&mqttClient); MQTT_Disconnect(&mqttClient);
MQTT_Free(&mqttClient); // safe even if not connected
}
} }
// no action required if mqtt status settings change, they just get picked up at the // no action required if mqtt status settings change, they just get picked up at the
// next status tick // next status tick
if (getBoolArg(connData, "mqtt-status-enable", (bool *)&flashConfig.mqtt_status_enable) < 0) if (getBoolArg(connData, "mqtt-status-enable", &flashConfig.mqtt_status_enable) < 0)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
if (getStringArg(connData, "mqtt-status-topic", if (getStringArg(connData, "mqtt-status-topic",
flashConfig.mqtt_status_topic, sizeof(flashConfig.mqtt_status_topic)) < 0) flashConfig.mqtt_status_topic, sizeof(flashConfig.mqtt_status_topic)) < 0)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
// if SLIP-enable is toggled it gets picked-up immediately by the parser // if SLIP-enable is toggled it gets picked-up immediately by the parser
int slip_update = getBoolArg(connData, "slip-enable", (bool *)&flashConfig.slip_enable); int slip_update = getBoolArg(connData, "slip-enable", &flashConfig.slip_enable);
if (slip_update < 0) return HTTPD_CGI_DONE; if (slip_update < 0) return HTTPD_CGI_DONE;
if (slip_update > 0) if (slip_update > 0)
DBG("SLIP-enable changed: %d\n", flashConfig.slip_enable); DBG("SLIP-enable changed: %d\n", flashConfig.slip_enable);

@ -7,6 +7,7 @@
#include "uart.h" #include "uart.h"
#include "stk500.h" #include "stk500.h"
#include "serbridge.h" #include "serbridge.h"
#include "mqtt_cmd.h"
#include "serled.h" #include "serled.h"
#define INIT_DELAY 150 // wait this many millisecs before sending anything #define INIT_DELAY 150 // wait this many millisecs before sending anything
@ -75,6 +76,7 @@ static void ICACHE_FLASH_ATTR optibootInit() {
progState = stateInit; progState = stateInit;
baudCnt = 0; baudCnt = 0;
uart0_baud(flashConfig.baud_rate); uart0_baud(flashConfig.baud_rate);
mqtt_unblock();
ackWait = 0; ackWait = 0;
errMessage[0] = 0; errMessage[0] = 0;
responseLen = 0; responseLen = 0;
@ -127,6 +129,7 @@ int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) {
} else if (connData->requestType == HTTPD_METHOD_POST) { } else if (connData->requestType == HTTPD_METHOD_POST) {
// issue reset // issue reset
optibootInit(); optibootInit();
mqtt_block(); // prevent MQTT from interfering
baudRate = flashConfig.baud_rate; baudRate = flashConfig.baud_rate;
programmingCB = optibootUartRecv; programmingCB = optibootUartRecv;
initBaud(); initBaud();
@ -644,6 +647,7 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) {
responseLen -= 2; responseLen -= 2;
} }
armTimer(PGM_INTERVAL); // reset timer armTimer(PGM_INTERVAL); // reset timer
break;
default: default:
break; break;
} }

@ -69,6 +69,7 @@ int ICACHE_FLASH_ATTR cgiSystemInfo(HttpdConnData *connData) {
"\"name\": \"%s\", " "\"name\": \"%s\", "
"\"reset cause\": \"%d=%s\", " "\"reset cause\": \"%d=%s\", "
"\"size\": \"%s\", " "\"size\": \"%s\", "
"\"upload-size\": \"%d\", "
"\"id\": \"0x%02X 0x%04X\", " "\"id\": \"0x%02X 0x%04X\", "
"\"partition\": \"%s\", " "\"partition\": \"%s\", "
"\"slip\": \"%s\", " "\"slip\": \"%s\", "
@ -80,6 +81,7 @@ int ICACHE_FLASH_ATTR cgiSystemInfo(HttpdConnData *connData) {
rst_info->reason, rst_info->reason,
rst_codes[rst_info->reason], rst_codes[rst_info->reason],
flash_maps[system_get_flash_size_map()], flash_maps[system_get_flash_size_map()],
getUserPageSectionEnd()-getUserPageSectionStart(),
fid & 0xff, (fid & 0xff00) | ((fid >> 16) & 0xff), fid & 0xff, (fid & 0xff00) | ((fid >> 16) & 0xff),
part_id ? "user2.bin" : "user1.bin", part_id ? "user2.bin" : "user1.bin",
flashConfig.slip_enable ? "enabled" : "disabled", flashConfig.slip_enable ? "enabled" : "disabled",
@ -153,9 +155,9 @@ int ICACHE_FLASH_ATTR cgiServicesSet(HttpdConnData *connData) {
if (syslog < 0) return HTTPD_CGI_DONE; if (syslog < 0) return HTTPD_CGI_DONE;
syslog |= getUInt8Arg(connData, "syslog_filter", &flashConfig.syslog_filter); syslog |= getUInt8Arg(connData, "syslog_filter", &flashConfig.syslog_filter);
if (syslog < 0) return HTTPD_CGI_DONE; if (syslog < 0) return HTTPD_CGI_DONE;
syslog |= getBoolArg(connData, "syslog_showtick", (bool *)&flashConfig.syslog_showtick); syslog |= getBoolArg(connData, "syslog_showtick", &flashConfig.syslog_showtick);
if (syslog < 0) return HTTPD_CGI_DONE; if (syslog < 0) return HTTPD_CGI_DONE;
syslog |= getBoolArg(connData, "syslog_showdate", (bool *)&flashConfig.syslog_showdate); syslog |= getBoolArg(connData, "syslog_showdate", &flashConfig.syslog_showdate);
if (syslog < 0) return HTTPD_CGI_DONE; if (syslog < 0) return HTTPD_CGI_DONE;
#ifdef SYSLOG #ifdef SYSLOG
@ -175,7 +177,7 @@ int ICACHE_FLASH_ATTR cgiServicesSet(HttpdConnData *connData) {
} }
int8_t mdns = 0; int8_t mdns = 0;
mdns |= getBoolArg(connData, "mdns_enable", (bool *)&flashConfig.mdns_enable); mdns |= getBoolArg(connData, "mdns_enable", &flashConfig.mdns_enable);
if (mdns < 0) return HTTPD_CGI_DONE; if (mdns < 0) return HTTPD_CGI_DONE;
if (mdns > 0) { if (mdns > 0) {

@ -0,0 +1,189 @@
// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo
#include <esp8266.h>
#include <osapi.h>
#include "cgi.h"
#include "cgioptiboot.h"
#include "multipart.h"
#include "espfsformat.h"
#include "config.h"
#include "web-server.h"
int header_position = 0; // flash offset of the file header
int upload_position = 0; // flash offset where to store page upload
int html_header_len = 0; // length of the HTML header added to the file
// this is the header to add if user uploads HTML file
const char * HTML_HEADER = "<!doctype html><html><head><title>esp-link</title>"
"<link rel=stylesheet href=\"/pure.css\"><link rel=stylesheet href=\"/style.css\">"
"<meta name=viewport content=\"width=device-width, initial-scale=1\"><script src=\"/ui.js\">"
"</script><script src=\"/userpage.js\"></script></head><body><div id=layout> ";
// this method is for flash writing and erasing the page
// write is incremental, so whenever a page border is reached, the next page will be erased
int ICACHE_FLASH_ATTR webServerSetupWriteFlash( int addr, void * data, int length )
{
int end_addr = addr + length;
if( end_addr >= getUserPageSectionEnd() )
{
os_printf("No more space in the flash!\n");
return 1;
}
void * free_ptr = 0;
if(( length & 3 ) != 0 ) // ESP8266 always writes 4 bytes, so the remaining ones should be oxFF-ed out
{
free_ptr = os_malloc(length + 4);
os_memset(free_ptr, 0xFF, length + 4);
os_memcpy(free_ptr, data, length);
data = free_ptr;
}
int ptr = 0;
while( addr < end_addr )
{
if (addr % SPI_FLASH_SEC_SIZE == 0){
spi_flash_erase_sector(addr/SPI_FLASH_SEC_SIZE);
}
int max = (addr | (SPI_FLASH_SEC_SIZE - 1)) + 1;
int len = end_addr - addr;
if( end_addr > max )
len = max - addr;
spi_flash_write( addr, (uint32_t *)((char *)data + ptr), len );
ptr += len;
addr += len;
}
if( free_ptr != 0 )
os_free(free_ptr);
return 0;
}
// debug code
void ICACHE_FLASH_ATTR dumpFlash( int end )
{
int dump = getUserPageSectionStart();
while( dump < end )
{
char buffer[16];
spi_flash_read(dump, (uint32_t *)buffer, sizeof(buffer));
char dmpstr[sizeof(buffer)*3];
os_sprintf(dmpstr, "%06X: ", dump);
for(int i=0; i < sizeof(buffer); i++ )
os_sprintf(dmpstr + os_strlen(dmpstr), "%02X ", buffer[i]);
os_printf("%s\n", dmpstr);
dump += sizeof(buffer);
}
}
// multipart callback for uploading user defined pages
int ICACHE_FLASH_ATTR webServerSetupMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position)
{
switch(cmd)
{
case FILE_UPLOAD_START:
upload_position = getUserPageSectionStart();
header_position = upload_position;
break;
case FILE_START:
{
html_header_len = 0;
// write the starting block on esp-fs
EspFsHeader hdr;
hdr.magic = 0xFFFFFFFF; // espfs magic is invalid during upload
hdr.flags = 0;
hdr.compression = 0;
int len = dataLen + 1;
while(( len & 3 ) != 0 )
len++;
hdr.nameLen = len;
hdr.fileLenComp = hdr.fileLenDecomp = 0xFFFFFFFF;
header_position = upload_position;
if( webServerSetupWriteFlash( upload_position, (uint32_t *)(&hdr), sizeof(EspFsHeader) ) )
return 1;
upload_position += sizeof(EspFsHeader);
char nameBuf[len];
os_memset(nameBuf, 0, len);
os_memcpy(nameBuf, data, dataLen);
if( webServerSetupWriteFlash( upload_position, (uint32_t *)(nameBuf), len ) )
return 1;
upload_position += len;
// add header to HTML files
if( ( dataLen > 5 ) && ( os_strcmp(data + dataLen - 5, ".html") == 0 ) ) // if the file ends with .html, wrap into an espfs image
{
html_header_len = os_strlen(HTML_HEADER) & ~3; // upload only 4 byte aligned part
char buf[html_header_len];
os_memcpy(buf, HTML_HEADER, html_header_len);
if( webServerSetupWriteFlash( upload_position, (uint32_t *)(buf), html_header_len ) )
return 1;
upload_position += html_header_len;
}
}
break;
case FILE_DATA:
if( webServerSetupWriteFlash( upload_position, data, dataLen ) )
return 1;
upload_position += dataLen;
break;
case FILE_DONE:
{
// write padding after the file
uint8_t pad_cnt = (4 - position) & 3;
if( pad_cnt ) {
uint32_t pad = 0;
if( webServerSetupWriteFlash( upload_position, &pad, pad_cnt ) )
return 1;
upload_position += pad_cnt;
}
EspFsHeader hdr;
hdr.magic = ESPFS_MAGIC;
hdr.fileLenComp = hdr.fileLenDecomp = position + html_header_len;
// restore ESPFS magic
spi_flash_write( header_position + ((char *)&hdr.magic - (char*)&hdr), (uint32_t *)&hdr.magic, sizeof(uint32_t) );
// set file size
spi_flash_write( header_position + ((char *)&hdr.fileLenComp - (char*)&hdr), (uint32_t *)&hdr.fileLenComp, sizeof(uint32_t) );
spi_flash_write( header_position + ((char *)&hdr.fileLenDecomp - (char*)&hdr), (uint32_t *)&hdr.fileLenDecomp, sizeof(uint32_t) );
}
break;
case FILE_UPLOAD_DONE:
{
// write the termination block
EspFsHeader hdr;
hdr.magic = ESPFS_MAGIC;
hdr.flags = 1;
hdr.compression = 0;
hdr.nameLen = 0;
hdr.fileLenComp = hdr.fileLenDecomp = 0;
if( webServerSetupWriteFlash( upload_position, (uint32_t *)(&hdr), sizeof(EspFsHeader) ) )
return 1;
upload_position += sizeof(EspFsHeader);
WEB_Init(); // reload the content
}
break;
}
return 0;
}
MultipartCtx * webServerContext = NULL; // multipart upload context for web server
// this callback is called when user uploads the web-page
int ICACHE_FLASH_ATTR cgiWebServerSetupUpload(HttpdConnData *connData)
{
if( webServerContext == NULL )
webServerContext = multipartCreateContext( webServerSetupMultipartCallback );
return multipartProcess(webServerContext, connData);
}

@ -0,0 +1,8 @@
#ifndef CGIWEBSERVER_H
#define CGIWEBSERVER_H
#include <httpd.h>
int ICACHE_FLASH_ATTR cgiWebServerSetupUpload(HttpdConnData *connData);
#endif /* CGIWEBSERVER_H */

@ -121,18 +121,29 @@ void ICACHE_FLASH_ATTR wifiAddStateChangeCb(WifiStateChangeCb cb) {
DBG("WIFI: max state change cb count exceeded\n"); DBG("WIFI: max state change cb count exceeded\n");
} }
static struct mdns_info *mdns_info;
// See https://github.com/arduino/Arduino/blob/master/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java#L155-L168
static char* mdns_txt = "ssh_upload=no";
void ICACHE_FLASH_ATTR wifiStartMDNS(struct ip_addr ip) { void ICACHE_FLASH_ATTR wifiStartMDNS(struct ip_addr ip) {
if (flashConfig.mdns_enable) { if (flashConfig.mdns_enable) {
struct mdns_info *mdns_info = (struct mdns_info *)os_zalloc(sizeof(struct mdns_info)); if (mdns_info == NULL)
mdns_info = (struct mdns_info *)os_zalloc(sizeof(struct mdns_info));
mdns_info->host_name = flashConfig.hostname; mdns_info->host_name = flashConfig.hostname;
mdns_info->server_name = flashConfig.mdns_servername; mdns_info->server_name = flashConfig.mdns_servername;
mdns_info->server_port = 80; mdns_info->server_port = 80;
mdns_info->ipAddr = ip.addr; mdns_info->ipAddr = ip.addr;
mdns_info->txt_data[0] = mdns_txt;
espconn_mdns_init(mdns_info); espconn_mdns_init(mdns_info);
} }
else { else {
espconn_mdns_server_unregister(); espconn_mdns_server_unregister();
espconn_mdns_close(); espconn_mdns_close();
if (mdns_info != NULL) {
os_free(mdns_info);
mdns_info = NULL;
}
} }
mdns_started = true; mdns_started = true;
} }
@ -551,30 +562,34 @@ int ICACHE_FLASH_ATTR cgiApSettingsChange(HttpdConnData *connData) {
if (checkString(buff) && len>7 && len<=64) { if (checkString(buff) && len>7 && len<=64) {
// String preprocessing done in client side, wifiap.js line 31 // String preprocessing done in client side, wifiap.js line 31
os_memcpy(apconf.password, buff, len); os_memcpy(apconf.password, buff, len);
os_printf("Setting AP password len=%d\n", len);
} else if (len != 0) { } else if (len != 0) {
jsonHeader(connData, 400); jsonHeader(connData, 400);
httpdSend(connData, "PASSWORD not valid or out of range", -1); httpdSend(connData, "PASSWORD not valid or out of range", -1);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
// Set auth mode // Set auth mode
if(len != 0){ if (len != 0) {
// Set authentication mode, before password to check open settings // Set authentication mode, before password to check open settings
len=httpdFindArg(connData->getArgs, "ap_authmode", buff, sizeof(buff)); len=httpdFindArg(connData->getArgs, "ap_authmode", buff, sizeof(buff));
if(len>0){ if (len > 0) {
int value = atoi(buff); int value = atoi(buff);
if(value >= 0 && value <= 4){ if (value > 0 && value <= 4) {
apconf.authmode = value; apconf.authmode = value;
}else{ } else {
// If out of range set by default // If out of range set by default
os_printf("Forcing AP authmode to WPA_WPA2_PSK\n");
apconf.authmode = 4; apconf.authmode = 4;
} }
}else{ } else {
// Valid password but wrong auth mode, default 4 // Valid password but wrong auth mode, default 4
os_printf("Forcing AP authmode to WPA_WPA2_PSK\n");
apconf.authmode = 4; apconf.authmode = 4;
} }
}else{ } else {
apconf.authmode = 0; apconf.authmode = 0;
} }
os_printf("Setting AP authmode=%d\n", apconf.authmode);
// Set max connection number // Set max connection number
len=httpdFindArg(connData->getArgs, "ap_maxconn", buff, sizeof(buff)); len=httpdFindArg(connData->getArgs, "ap_maxconn", buff, sizeof(buff));
if(len>0){ if(len>0){

@ -23,14 +23,18 @@ FlashConfig flashDefault = {
.slip_enable = 0, .mqtt_enable = 0, .mqtt_status_enable = 0, .slip_enable = 0, .mqtt_enable = 0, .mqtt_status_enable = 0,
.mqtt_timeout = 2, .mqtt_clean_session = 1, .mqtt_timeout = 2, .mqtt_clean_session = 1,
.mqtt_port = 1883, .mqtt_keepalive = 60, .mqtt_port = 1883, .mqtt_keepalive = 60,
.mqtt_host = "\0", .mqtt_clientid = "\0", .mqtt_old_host = "\0", .mqtt_clientid = "\0",
.mqtt_username= "\0", .mqtt_password = "\0", .mqtt_status_topic = "\0", .mqtt_username = "\0", .mqtt_password = "\0", .mqtt_status_topic = "\0",
.mqtt_host = "\0",
.sys_descr = "\0", .sys_descr = "\0",
.rx_pullup = 1, .rx_pullup = 1,
.sntp_server = "us.pool.ntp.org\0", .sntp_server = "us.pool.ntp.org\0",
.syslog_host = "\0", .syslog_minheap = 8192, .syslog_filter = 7, .syslog_showtick = 1, .syslog_showdate = 0, .syslog_host = "\0", .syslog_minheap = 8192, .syslog_filter = 7, .syslog_showtick = 1, .syslog_showdate = 0,
.mdns_enable = 1, .mdns_servername = "http\0", .timezone_offset = 0, .mdns_enable = 1, .mdns_servername = "http\0", .timezone_offset = 0,
.uart0_tx_enable_pin = -1 .uart0_tx_enable_pin = -1
.data_bits = EIGHT_BITS,
.parity = NONE_BITS,
.stop_bits = ONE_STOP_BIT,
}; };
typedef union { typedef union {
@ -141,8 +145,22 @@ bool ICACHE_FLASH_ATTR configRestore(void) {
flash_pri = 0; flash_pri = 0;
return false; return false;
} }
// copy good one into global var and return // copy good one into global var
os_memcpy(&flashConfig, flash_pri == 0 ? &ff0.fc : &ff1.fc, sizeof(FlashConfig)); os_memcpy(&flashConfig, flash_pri == 0 ? &ff0.fc : &ff1.fc, sizeof(FlashConfig));
// convert old config
if (flashConfig.mqtt_host[0] == 0 && flashConfig.mqtt_old_host[0] != 0) {
// the mqtt_host got changed from 32 chars to 64 in a new location
os_printf("Converting old mqtt_host\n");
os_memcpy(flashConfig.mqtt_host, flashConfig.mqtt_old_host, 32);
os_memset(flashConfig.mqtt_old_host, 0, 32);
} else os_printf("mqtt_host is '%s'\n", flashConfig.mqtt_host);
if (flashConfig.data_bits == 0) {
// restore to default 8N1
flashConfig.data_bits = flashDefault.data_bits;
flashConfig.parity = flashDefault.parity;
flashConfig.stop_bits = flashDefault.stop_bits;
}
return true; return true;
} }
@ -187,5 +205,42 @@ getFlashSize() {
return 1 << size_id; return 1 << size_id;
} }
const uint32_t getUserPageSectionStart()
{
enum flash_size_map map = system_get_flash_size_map();
switch(map)
{
case FLASH_SIZE_4M_MAP_256_256:
return FLASH_SECT + FIRMWARE_SIZE - 3*FLASH_SECT;// bootloader + firmware - 12KB (highly risky...)
case FLASH_SIZE_8M_MAP_512_512:
return FLASH_SECT + FIRMWARE_SIZE;
case FLASH_SIZE_16M_MAP_512_512:
case FLASH_SIZE_16M_MAP_1024_1024:
case FLASH_SIZE_32M_MAP_512_512:
case FLASH_SIZE_32M_MAP_1024_1024:
return 0x100000;
default:
return 0xFFFFFFFF;
}
}
const uint32_t getUserPageSectionEnd()
{
enum flash_size_map map = system_get_flash_size_map();
switch(map)
{
case FLASH_SIZE_4M_MAP_256_256:
return FLASH_SECT + FIRMWARE_SIZE - 2*FLASH_SECT;
case FLASH_SIZE_8M_MAP_512_512:
return FLASH_SECT + FIRMWARE_SIZE + 2*FLASH_SECT;
case FLASH_SIZE_16M_MAP_512_512:
case FLASH_SIZE_16M_MAP_1024_1024:
return 0x1FC000;
case FLASH_SIZE_32M_MAP_512_512:
case FLASH_SIZE_32M_MAP_1024_1024:
return 0x3FC000;
default:
return 0xFFFFFFFF;
}
}

@ -21,7 +21,7 @@ typedef struct {
mqtt_timeout, // MQTT send timeout mqtt_timeout, // MQTT send timeout
mqtt_clean_session; // MQTT clean session mqtt_clean_session; // MQTT clean session
uint16_t mqtt_port, mqtt_keepalive; // MQTT Host port, MQTT Keepalive timer uint16_t mqtt_port, mqtt_keepalive; // MQTT Host port, MQTT Keepalive timer
char mqtt_host[32], char mqtt_old_host[32], // replaced by 64-char mqtt_host below
mqtt_clientid[48], mqtt_clientid[48],
mqtt_username[32], mqtt_username[32],
mqtt_password[32], mqtt_password[32],
@ -38,6 +38,10 @@ typedef struct {
char mdns_servername[32]; char mdns_servername[32];
int8_t timezone_offset; int8_t timezone_offset;
int8_t uart0_tx_enable_pin; int8_t uart0_tx_enable_pin;
char mqtt_host[64]; // MQTT host we connect to, was 32-char mqtt_old_host
int8_t data_bits;
int8_t parity;
int8_t stop_bits;
} FlashConfig; } FlashConfig;
extern FlashConfig flashConfig; extern FlashConfig flashConfig;
@ -46,4 +50,7 @@ bool configRestore(void);
void configWipe(void); void configWipe(void);
const size_t getFlashSize(); const size_t getFlashSize();
const uint32_t getUserPageSectionStart();
const uint32_t getUserPageSectionEnd();
#endif #endif

@ -19,6 +19,7 @@
#include "cgimqtt.h" #include "cgimqtt.h"
#include "cgiflash.h" #include "cgiflash.h"
#include "cgioptiboot.h" #include "cgioptiboot.h"
#include "cgiwebserversetup.h"
#include "auth.h" #include "auth.h"
#include "espfs.h" #include "espfs.h"
#include "uart.h" #include "uart.h"
@ -30,6 +31,7 @@
#include "log.h" #include "log.h"
#include "gpio.h" #include "gpio.h"
#include "cgiservices.h" #include "cgiservices.h"
#include "web-server.h"
#ifdef SYSLOG #ifdef SYSLOG
#include "syslog.h" #include "syslog.h"
@ -43,6 +45,14 @@
} while ( 0 ) } while ( 0 )
#endif #endif
#ifdef MEMLEAK_DEBUG
#include "mem.h"
bool ICACHE_FLASH_ATTR check_memleak_debug_enable(void)
{
return MEMLEAK_DEBUG_ENABLE;
}
#endif
/* /*
This is the main url->function dispatching data struct. This is the main url->function dispatching data struct.
In short, it's a struct with various URLs plus their handlers. The handlers can In short, it's a struct with various URLs plus their handlers. The handlers can
@ -66,6 +76,7 @@ HttpdBuiltInUrl builtInUrls[] = {
{ "/log/reset", cgiReset, NULL }, { "/log/reset", cgiReset, NULL },
{ "/console/reset", ajaxConsoleReset, NULL }, { "/console/reset", ajaxConsoleReset, NULL },
{ "/console/baud", ajaxConsoleBaud, NULL }, { "/console/baud", ajaxConsoleBaud, NULL },
{ "/console/fmt", ajaxConsoleFormat, NULL },
{ "/console/text", ajaxConsole, NULL }, { "/console/text", ajaxConsole, NULL },
{ "/console/send", ajaxConsoleSend, NULL }, { "/console/send", ajaxConsoleSend, NULL },
//Enable the line below to protect the WiFi configuration with an username/password combo. //Enable the line below to protect the WiFi configuration with an username/password combo.
@ -88,6 +99,8 @@ HttpdBuiltInUrl builtInUrls[] = {
#ifdef MQTT #ifdef MQTT
{ "/mqtt", cgiMqtt, NULL }, { "/mqtt", cgiMqtt, NULL },
#endif #endif
{ "/web-server/upload", cgiWebServerSetupUpload, NULL },
{ "*.json", WEB_CgiJsonHook, NULL }, //Catch-all cgi JSON queries
{ "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem
{ NULL, NULL, NULL } { NULL, NULL, NULL }
}; };
@ -109,14 +122,37 @@ extern uint32_t _binary_espfs_img_start;
extern void app_init(void); extern void app_init(void);
extern void mqtt_client_init(void); extern void mqtt_client_init(void);
void user_rf_pre_init(void) { void ICACHE_FLASH_ATTR
user_rf_pre_init(void) {
//default is enabled //default is enabled
system_set_os_print(DEBUG_SDK); system_set_os_print(DEBUG_SDK);
} }
/* user_rf_cal_sector_set is a required function that is called by the SDK to get a flash
* sector number where it can store RF calibration data. This was introduced with SDK 1.5.4.1
* and is necessary because Espressif ran out of pre-reserved flash sectors. Ooops... */
uint32 ICACHE_FLASH_ATTR
user_rf_cal_sector_set(void) {
uint32_t sect = 0;
switch (system_get_flash_size_map()) {
case FLASH_SIZE_4M_MAP_256_256: // 512KB
sect = 128 - 10; // 0x76000
break;
default:
sect = 128; // 0x80000
}
return sect;
}
// Main routine to initialize esp-link. // Main routine to initialize esp-link.
void user_init(void) { void ICACHE_FLASH_ATTR
user_init(void) {
system_timer_reinit(); system_timer_reinit();
// uncomment the following three lines to see flash config messages for troubleshooting
//uart_init(115200, 115200);
//logInit();
//os_delay_us(100000L);
// get the flash config so we know how to init things // get the flash config so we know how to init things
//configWipe(); // uncomment to reset the config for testing purposes //configWipe(); // uncomment to reset the config for testing purposes
bool restoreOk = configRestore(); bool restoreOk = configRestore();
@ -124,7 +160,8 @@ void user_init(void) {
gpio_init(); gpio_init();
gpio_output_set(0, 0, 0, (1<<15)); // some people tie it to GND, gotta ensure it's disabled gpio_output_set(0, 0, 0, (1<<15)); // some people tie it to GND, gotta ensure it's disabled
// init UART // init UART
uart_init(flashConfig.baud_rate, flashConfig.uart0_tx_enable_pin, 115200); uart_init(CALC_UARTMODE(flashConfig.data_bits, flashConfig.parity, flashConfig.stop_bits),
flashConfig.baud_rate, flashConfig.uart0_tx_enable_pin, 115200);
logInit(); // must come after init of uart logInit(); // must come after init of uart
// Say hello (leave some time to cause break in TX after boot loader's msg // Say hello (leave some time to cause break in TX after boot loader's msg
os_delay_us(10000L); os_delay_us(10000L);
@ -136,11 +173,14 @@ void user_init(void) {
// Wifi // Wifi
wifiInit(); wifiInit();
// init the flash filesystem with the html stuff // init the flash filesystem with the html stuff
espFsInit(&_binary_espfs_img_start); espFsInit(espLinkCtx, &_binary_espfs_img_start, ESPFS_MEMORY);
//EspFsInitResult res = espFsInit(&_binary_espfs_img_start); //EspFsInitResult res = espFsInit(&_binary_espfs_img_start);
//os_printf("espFsInit %s\n", res?"ERR":"ok"); //os_printf("espFsInit %s\n", res?"ERR":"ok");
// mount the http handlers // mount the http handlers
httpdInit(builtInUrls, 80); httpdInit(builtInUrls, 80);
WEB_Init();
// init the wifi-serial transparent bridge (port 23) // init the wifi-serial transparent bridge (port 23)
serbridgeInit(23, 2323); serbridgeInit(23, 2323);
uart_add_recv_cb(&serbridgeUartCb); uart_add_recv_cb(&serbridgeUartCb);
@ -163,10 +203,15 @@ void user_init(void) {
// Init SNTP service // Init SNTP service
cgiServicesSNTPInit(); cgiServicesSNTPInit();
#ifdef MQTT #ifdef MQTT
if (flashConfig.mqtt_enable) {
NOTICE("initializing MQTT"); NOTICE("initializing MQTT");
mqtt_client_init(); mqtt_client_init();
}
#endif #endif
NOTICE("initializing user application"); NOTICE("initializing user application");
app_init(); app_init();
NOTICE("Waiting for work to do..."); NOTICE("Waiting for work to do...");
#ifdef MEMLEAK_DEBUG
system_show_malloc();
#endif
} }

@ -5,7 +5,7 @@
#include "mqtt.h" #include "mqtt.h"
#ifdef MQTTCLIENT_DBG #ifdef MQTTCLIENT_DBG
#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__) } while(0) #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
#else #else
#define DBG(format, ...) do { } while(0) #define DBG(format, ...) do { } while(0)
#endif #endif
@ -66,7 +66,7 @@ void ICACHE_FLASH_ATTR
wifiStateChangeCb(uint8_t status) wifiStateChangeCb(uint8_t status)
{ {
if (flashConfig.mqtt_enable) { if (flashConfig.mqtt_enable) {
if (status == wifiGotIP && mqttClient.connState != TCP_CONNECTING) { if (status == wifiGotIP && mqttClient.connState < TCP_CONNECTING) {
MQTT_Connect(&mqttClient); MQTT_Connect(&mqttClient);
} }
else if (status == wifiIsDisconnected && mqttClient.connState == TCP_CONNECTING) { else if (status == wifiIsDisconnected && mqttClient.connState == TCP_CONNECTING) {
@ -87,8 +87,9 @@ mqtt_client_init()
MQTT_OnPublished(&mqttClient, mqttPublishedCb); MQTT_OnPublished(&mqttClient, mqttPublishedCb);
MQTT_OnData(&mqttClient, mqttDataCb); MQTT_OnData(&mqttClient, mqttDataCb);
if (flashConfig.mqtt_enable && strlen(flashConfig.mqtt_host) > 0) // Don't connect now, wait for a wifi status change callback
MQTT_Connect(&mqttClient); //if (flashConfig.mqtt_enable && strlen(flashConfig.mqtt_host) > 0)
// MQTT_Connect(&mqttClient);
wifiAddStateChangeCb(wifiStateChangeCb); wifiAddStateChangeCb(wifiStateChangeCb);
} }

@ -12,7 +12,7 @@
#define USRTASK_H #define USRTASK_H
#define _taskPrio 1 #define _taskPrio 1
#define _task_queueLen 64 #define _task_queueLen 8
uint8_t register_usr_task (os_task_t event); uint8_t register_usr_task (os_task_t event);
bool post_usr_task(uint8_t task, os_param_t par); bool post_usr_task(uint8_t task, os_param_t par);

@ -30,6 +30,7 @@ It's written for use with httpd, but doesn't need to be used as such.
#define os_malloc malloc #define os_malloc malloc
#define os_free free #define os_free free
#define os_memcpy memcpy #define os_memcpy memcpy
#define os_memset memset
#define os_strncmp strncmp #define os_strncmp strncmp
#define os_strcmp strcmp #define os_strcmp strcmp
#define os_strcpy strcpy #define os_strcpy strcpy
@ -40,9 +41,21 @@ It's written for use with httpd, but doesn't need to be used as such.
#include "espfsformat.h" #include "espfsformat.h"
#include "espfs.h" #include "espfs.h"
static char* espFsData = NULL; EspFsContext espLinkCtxDef;
EspFsContext userPageCtxDef;
EspFsContext * espLinkCtx = &espLinkCtxDef;
EspFsContext * userPageCtx = &userPageCtxDef;
struct EspFsContext
{
char* data;
EspFsSource source;
uint8_t valid;
};
struct EspFsFile { struct EspFsFile {
EspFsContext *ctx;
EspFsHeader *header; EspFsHeader *header;
char decompressor; char decompressor;
int32_t posDecomp; int32_t posDecomp;
@ -67,29 +80,12 @@ Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All
a memory exception, crashing the program. a memory exception, crashing the program.
*/ */
EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) {
// base address must be aligned to 4 bytes
if (((int)flashAddress & 3) != 0) {
return ESPFS_INIT_RESULT_BAD_ALIGN;
}
// check if there is valid header at address
EspFsHeader testHeader;
os_memcpy(&testHeader, flashAddress, sizeof(EspFsHeader));
if (testHeader.magic != ESPFS_MAGIC) {
return ESPFS_INIT_RESULT_NO_IMAGE;
}
espFsData = (char *)flashAddress;
return ESPFS_INIT_RESULT_OK;
}
//Copies len bytes over from dst to src, but does it using *only* //Copies len bytes over from dst to src, but does it using *only*
//aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works. //aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works.
//ToDo: perhaps os_memcpy also does unaligned accesses? //ToDo: perhaps os_memcpy also does unaligned accesses?
#ifdef __ets__ #ifdef __ets__
void ICACHE_FLASH_ATTR memcpyAligned(char *dst, char *src, int len) { void ICACHE_FLASH_ATTR memcpyAligned(char *dst, const char *src, int len) {
int x; int x;
int w, b; int w, b;
for (x=0; x<len; x++) { for (x=0; x<len; x++) {
@ -106,6 +102,51 @@ void ICACHE_FLASH_ATTR memcpyAligned(char *dst, char *src, int len) {
#define memcpyAligned memcpy #define memcpyAligned memcpy
#endif #endif
void ICACHE_FLASH_ATTR memcpyFromFlash(char *dst, const char *src, int len)
{
if( spi_flash_read( (int)src, (void *)dst, len ) != SPI_FLASH_RESULT_OK )
os_memset( dst, 0, len ); // if read was not successful, reply with zeroes
}
// memcpy on MEMORY/FLASH file systems
void espfs_memcpy( EspFsContext * ctx, void * dest, const void * src, int count )
{
if( ctx->source == ESPFS_MEMORY )
os_memcpy( dest, src, count );
else
memcpyFromFlash(dest, src, count);
}
// aligned memcpy on MEMORY/FLASH file systems
void espfs_memcpyAligned( EspFsContext * ctx, void * dest, const void * src, int count )
{
if( ctx->source == ESPFS_MEMORY )
memcpyAligned(dest, src, count);
else
memcpyFromFlash(dest, src, count);
}
// initializes an EspFs context
EspFsInitResult ICACHE_FLASH_ATTR espFsInit(EspFsContext *ctx, void *flashAddress, EspFsSource source) {
ctx->valid = 0;
ctx->source = source;
// base address must be aligned to 4 bytes
if (((int)flashAddress & 3) != 0) {
return ESPFS_INIT_RESULT_BAD_ALIGN;
}
// check if there is valid header at address
EspFsHeader testHeader;
espfs_memcpy(ctx, &testHeader, flashAddress, sizeof(EspFsHeader));
if (testHeader.magic != ESPFS_MAGIC) {
return ESPFS_INIT_RESULT_NO_IMAGE;
}
ctx->data = (char *)flashAddress;
ctx->valid = 1;
return ESPFS_INIT_RESULT_OK;
}
// Returns flags of opened file. // Returns flags of opened file.
int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) {
if (fh == NULL) { if (fh == NULL) {
@ -116,57 +157,93 @@ int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) {
} }
int8_t flags; int8_t flags;
memcpyAligned((char*)&flags, (char*)&fh->header->flags, 1); espfs_memcpyAligned(fh->ctx, (char*)&flags, (char*)&fh->header->flags, 1);
return (int)flags; return (int)flags;
} }
//Open a file and return a pointer to the file desc struct. // creates and initializes an iterator over the espfs file system
EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { void ICACHE_FLASH_ATTR espFsIteratorInit(EspFsContext *ctx, EspFsIterator *iterator)
if (espFsData == NULL) { {
#ifdef ESPFS_DBG if( ctx->data == NULL )
os_printf("Call espFsInit first!\n"); {
#endif iterator->ctx = NULL;
return NULL; return;
} }
char *p=espFsData; iterator->ctx = ctx;
char *hpos; iterator->position = NULL;
char namebuf[256]; }
EspFsHeader h;
EspFsFile *r; // moves iterator to the next file on espfs
//Strip initial slashes // returns 1 if iterator move was successful, otherwise 0 (last file)
while(fileName[0]=='/') fileName++; // iterator->header and iterator->name will contain file information
//Go find that file! int ICACHE_FLASH_ATTR espFsIteratorNext(EspFsIterator *iterator)
while(1) { {
hpos=p; if( iterator->ctx == NULL )
//Grab the next file header. return 0;
os_memcpy(&h, p, sizeof(EspFsHeader));
if (h.magic!=ESPFS_MAGIC) { char * position = iterator->position;
if( position == NULL )
position = iterator->ctx->data; // first node
else
{
// jump the iterator to the next file
position+=sizeof(EspFsHeader) + iterator->header.nameLen+iterator->header.fileLenComp;
if ((int)position&3) position+=4-((int)position&3); //align to next 32bit val
}
iterator->position = position;
EspFsHeader * hdr = &iterator->header;
espfs_memcpy(iterator->ctx, hdr, position, sizeof(EspFsHeader));
if (hdr->magic!=ESPFS_MAGIC) {
#ifdef ESPFS_DBG #ifdef ESPFS_DBG
os_printf("Magic mismatch. EspFS image broken.\n"); os_printf("Magic mismatch. EspFS image broken.\n");
#endif #endif
return NULL; return 0;
} }
if (h.flags&FLAG_LASTFILE) { if (hdr->flags&FLAG_LASTFILE) {
//os_printf("End of image.\n"); //os_printf("End of image.\n");
return NULL; iterator->ctx = NULL; // invalidate the iterator
return 0;
} }
position += sizeof(EspFsHeader);
//Grab the name of the file. //Grab the name of the file.
p+=sizeof(EspFsHeader); espfs_memcpy(iterator->ctx, iterator->name, position, sizeof(iterator->name));
os_memcpy(namebuf, p, sizeof(namebuf));
// os_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n", return 1;
// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags); }
if (os_strcmp(namebuf, fileName)==0) {
//Open a file and return a pointer to the file desc struct.
EspFsFile ICACHE_FLASH_ATTR *espFsOpen(EspFsContext *ctx, char *fileName) {
EspFsIterator it;
espFsIteratorInit(ctx, &it);
if (it.ctx == NULL) {
#ifdef ESPFS_DBG
os_printf("Call espFsInit first!\n");
#endif
return NULL;
}
//Strip initial slashes
while(fileName[0]=='/') fileName++;
//Search the file
while( espFsIteratorNext(&it) )
{
if (os_strcmp(it.name, fileName)==0) {
//Yay, this is the file we need! //Yay, this is the file we need!
p+=h.nameLen; //Skip to content. EspFsFile * r=(EspFsFile *)os_malloc(sizeof(EspFsFile)); //Alloc file desc mem
r=(EspFsFile *)os_malloc(sizeof(EspFsFile)); //Alloc file desc mem
//os_printf("Alloc %p[%d]\n", r, sizeof(EspFsFile)); //os_printf("Alloc %p[%d]\n", r, sizeof(EspFsFile));
if (r==NULL) return NULL; if (r==NULL) return NULL;
r->header=(EspFsHeader *)hpos; r->ctx = ctx;
r->decompressor=h.compression; r->header=(EspFsHeader *)it.position;
r->posComp=p; r->decompressor=it.header.compression;
r->posStart=p; r->posComp=it.position + it.header.nameLen + sizeof(EspFsHeader);
r->posStart=it.position + it.header.nameLen + sizeof(EspFsHeader);
r->posDecomp=0; r->posDecomp=0;
if (h.compression==COMPRESS_NONE) { if (it.header.compression==COMPRESS_NONE) {
r->decompData=NULL; r->decompData=NULL;
} else { } else {
#ifdef ESPFS_DBG #ifdef ESPFS_DBG
@ -176,10 +253,8 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) {
} }
return r; return r;
} }
//We don't need this file. Skip name and file
p+=h.nameLen+h.fileLenComp;
if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val
} }
return NULL;
} }
//Read len bytes from the given file into buff. Returns the actual amount of bytes read. //Read len bytes from the given file into buff. Returns the actual amount of bytes read.
@ -187,15 +262,15 @@ int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) {
int flen, fdlen; int flen, fdlen;
if (fh==NULL) return 0; if (fh==NULL) return 0;
//Cache file length. //Cache file length.
memcpyAligned((char*)&flen, (char*)&fh->header->fileLenComp, 4); espfs_memcpyAligned(fh->ctx, (char*)&flen, (char*)&fh->header->fileLenComp, 4);
memcpyAligned((char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4); espfs_memcpyAligned(fh->ctx, (char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4);
//Do stuff depending on the way the file is compressed. //Do stuff depending on the way the file is compressed.
if (fh->decompressor==COMPRESS_NONE) { if (fh->decompressor==COMPRESS_NONE) {
int toRead; int toRead;
toRead=flen-(fh->posComp-fh->posStart); toRead=flen-(fh->posComp-fh->posStart);
if (len>toRead) len=toRead; if (len>toRead) len=toRead;
// os_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp); // os_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp);
memcpyAligned(buff, fh->posComp, len); espfs_memcpyAligned(fh->ctx, buff, fh->posComp, len);
fh->posDecomp+=len; fh->posDecomp+=len;
fh->posComp+=len; fh->posComp+=len;
// os_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp); // os_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp);
@ -211,5 +286,8 @@ void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) {
os_free(fh); os_free(fh);
} }
// checks if the file system is valid (detect if the content is an espfs image or random data)
int ICACHE_FLASH_ATTR espFsIsValid(EspFsContext *ctx) {
return ctx->valid;
}

@ -1,19 +1,42 @@
#ifndef ESPFS_H #ifndef ESPFS_H
#define ESPFS_H #define ESPFS_H
#include "espfsformat.h"
typedef enum { typedef enum {
ESPFS_INIT_RESULT_OK, ESPFS_INIT_RESULT_OK,
ESPFS_INIT_RESULT_NO_IMAGE, ESPFS_INIT_RESULT_NO_IMAGE,
ESPFS_INIT_RESULT_BAD_ALIGN, ESPFS_INIT_RESULT_BAD_ALIGN,
} EspFsInitResult; } EspFsInitResult;
// Only 1 MByte of the flash can be directly accessed with ESP8266
// If flash size is >1 Mbyte, SDK API is required to retrieve flash content
typedef enum {
ESPFS_MEMORY, // read data directly from memory (fast, max 1 MByte)
ESPFS_FLASH, // read data from flash using SDK API (no limit for the size)
} EspFsSource;
typedef struct EspFsFile EspFsFile; typedef struct EspFsFile EspFsFile;
typedef struct EspFsContext EspFsContext;
typedef struct {
EspFsHeader header; // the header of the current file
EspFsContext *ctx; // pointer to espfs context
char name[256]; // the name of the current file
char *position; // position of the iterator (pointer on the file system)
} EspFsIterator;
extern EspFsContext * espLinkCtx;
extern EspFsContext * userPageCtx;
EspFsInitResult espFsInit(void *flashAddress); EspFsInitResult espFsInit(EspFsContext *ctx, void *flashAddress, EspFsSource source);
EspFsFile *espFsOpen(char *fileName); EspFsFile *espFsOpen(EspFsContext *ctx, char *fileName);
int espFsIsValid(EspFsContext *ctx);
int espFsFlags(EspFsFile *fh); int espFsFlags(EspFsFile *fh);
int espFsRead(EspFsFile *fh, char *buff, int len); int espFsRead(EspFsFile *fh, char *buff, int len);
void espFsClose(EspFsFile *fh); void espFsClose(EspFsFile *fh);
void espFsIteratorInit(EspFsContext *ctx, EspFsIterator *iterator);
int espFsIteratorNext(EspFsIterator *iterator);
#endif #endif

@ -34,9 +34,10 @@ clean:
else else
CC=gcc
CFLAGS=-I.. -std=gnu99 CFLAGS=-I.. -std=gnu99
ifeq ("$(GZIP_COMPRESSION)","yes") ifeq ("$(GZIP_COMPRESSION)","yes")
CFLAGS += -DESPFS_GZIP CFLAGS+= -DESPFS_GZIP
endif endif
OBJS=main.o OBJS=main.o

@ -17,8 +17,23 @@
<option value="38400">38400</option> <option value="38400">38400</option>
<option value="19200">19200</option> <option value="19200">19200</option>
<option value="9600">9600</option> <option value="9600">9600</option>
<option value="4800">4800</option>
<option value="2400">2400</option>
<option value="1200">1200</option>
<option value="600">600</option>
<option value="300">300</option>
</select>
&nbsp; Fmt:
<select id="fmt-sel" class="pure-button" href="#">
<option value="8N1">8N1</option>
<option value="8E1">8E1</option>
<option value="8N2">8N2</option>
<option value="8E2">8E2</option>
<option value="7N1">7N1</option>
<option value="7E1">7E1</option>
<option value="7N2">7N2</option>
<option value="7E2">7E2</option>
</select> </select>
&nbsp; Fmt: 8N1
</p> </p>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1-4"><legend><b>Console</b></legend></div> <div class="pure-u-1-4"><legend><b>Console</b></legend></div>
@ -93,6 +108,20 @@
); );
}); });
ajaxJson('GET', "/console/fmt",
function(data) { $("#fmt-sel").value = data.fmt; },
function(s, st) { showNotification(st); }
);
bnd($("#fmt-sel"), "change", function(ev) {
ev.preventDefault();
var fmt = $("#fmt-sel").value;
ajaxSpin('POST', "/console/fmt?fmt="+fmt,
function(resp) { showNotification("" + fmt + " format set"); },
function(s, st) { showWarning("Error setting format: " + st); }
);
});
consoleSendInit(); consoleSendInit();
addClass($('html')[0], "height100"); addClass($('html')[0], "height100");

@ -0,0 +1,43 @@
<div id="main">
<div class="header">
<h1>Upgrade Firmware</h1>
</div>
<div class="content">
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<div class="card">
<h1>Upgrade Firmware
<div id="fw-spinner" class="spinner spinner-small"></div>
</h1>
<form action="#" id="fw-form" class="pure-form" hidden>
<legend>Firmware Info</legend>
<p>
Current firmware: <span style="font-weight: bold;" id="current-fw"></span>
</p>
<div class="pure-form-stacked">
<p>
Make sure you upload the file called: <span style="font-weight: bold;" id="fw-slot"></span>
</p>
<label>Firmware File</label>
<input type="file" name="fw-file" id="fw-file"/>
</div>
<button id="fw-button" type="submit" class="pure-button button-primary">
Update the firmware
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="flash.js"></script>
<script type="text/javascript">
onLoad(function() {
fetchFlash();
bnd($("#fw-form"), "submit", flashFirmware);
});
</script>
</body></html>

@ -0,0 +1,33 @@
//===== FLASH cards
function flashFirmware(e) {
e.preventDefault();
var fw_data = document.getElementById('fw-file').files[0];
$("#fw-form").setAttribute("hidden", "");
$("#fw-spinner").removeAttribute("hidden");
showNotification("Firmware is being updated ...");
ajaxReq("POST", "/flash/upload", function (resp) {
ajaxReq("GET", "/flash/reboot", function (resp) {
showNotification("Firmware has been successfully updated!");
setTimeout(function(){ window.location.reload()}, 4000);
$("#fw-spinner").setAttribute("hidden", "");
$("#fw-form").removeAttribute("hidden");
});
}, null, fw_data)
}
function fetchFlash() {
ajaxReq("GET", "/flash/next", function (resp) {
$("#fw-slot").innerHTML = resp;
$("#fw-spinner").setAttribute("hidden", "");
$("#fw-form").removeAttribute("hidden");
});
ajaxJson("GET", "/menu", function(data) {
var v = $("#current-fw");
if (v != null) { v.innerHTML = data.version; }
}
);
}

@ -40,9 +40,9 @@
/home/arduino/hardware/tools/avrdude&nbsp;\<br> /home/arduino/hardware/tools/avrdude&nbsp;\<br>
&nbsp;&nbsp;-DV -patmega328p \<br> &nbsp;&nbsp;-DV -patmega328p \<br>
&nbsp;&nbsp;-Pnet:esp-link.local:23 \<br> &nbsp;&nbsp;-Pnet:esp-link.local:23 \<br>
&nbsp;&nbsp;-carduino -b115200 -U -C \<br> &nbsp;&nbsp;-carduino -b115200 \<br>
&nbsp;&nbsp;/home/arduino/hardware/tools/avrdude.conf&nbsp;\<br> &nbsp;&nbsp;-U flash:w:my_sketch.hex:i\<br>
&nbsp;&nbsp;flash:w:my_sketch.hex:i &nbsp;&nbsp;-C /home/arduino/hardware/tools/avrdude.conf
</div> </div>
<p>where <tt>-Pnet:esp-link.local:23</tt> tells avrdude to connect to port 23 of esp-link. <p>where <tt>-Pnet:esp-link.local:23</tt> tells avrdude to connect to port 23 of esp-link.
You can substitute the IP address of your esp-link for esp-link.local if necessary. You can substitute the IP address of your esp-link for esp-link.local if necessary.
@ -128,6 +128,12 @@
<div class="popup pop-left">Size configured into bootloader, must match chip size</div> <div class="popup pop-left">Size configured into bootloader, must match chip size</div>
</div> </div>
</td></tr> </td></tr>
<tr><td>Webpage size</td><td>
<div>
<span class="system-upload-size"></span>
<div class="popup pop-left">The maximal size of the custom web page a user can upload.</div>
</div>
</td></tr>
<tr><td>Current partition</td><td class="system-partition"></td></tr> <tr><td>Current partition</td><td class="system-partition"></td></tr>
<tr><td colspan=2 class="popup-target">Description:<br> <tr><td colspan=2 class="popup-target">Description:<br>
<div class="click-to-edit system-description"> <div class="click-to-edit system-description">

@ -54,7 +54,7 @@ function displayServices(data) {
if (data.syslog_host !== undefined) { if (data.syslog_host !== undefined) {
$("#Syslog-form").removeAttribute("hidden"); $("#Syslog-form").removeAttribute("hidden");
} else { } else {
# syslog disabled... // syslog disabled...
$("#Syslog-form").parentNode.setAttribute("hidden", ""); $("#Syslog-form").parentNode.setAttribute("hidden", "");
} }
$("#SNTP-form").removeAttribute("hidden"); $("#SNTP-form").removeAttribute("hidden");

@ -151,7 +151,7 @@ function toggleClass(el, cl) {
//===== AJAX //===== AJAX
function ajaxReq(method, url, ok_cb, err_cb) { function ajaxReq(method, url, ok_cb, err_cb, data) {
var xhr = j(); var xhr = j();
xhr.open(method, url, true); xhr.open(method, url, true);
var timeout = setTimeout(function() { var timeout = setTimeout(function() {
@ -173,7 +173,7 @@ function ajaxReq(method, url, ok_cb, err_cb) {
} }
// console.log("XHR send:", method, url); // console.log("XHR send:", method, url);
try { try {
xhr.send(); xhr.send(data);
} catch(err) { } catch(err) {
console.log("XHR EXC :", method, url, "->", err); console.log("XHR EXC :", method, url, "->", err);
err_cb(599, err); err_cb(599, err);

@ -0,0 +1,242 @@
//===== Java script for user pages
var loadCounter = 0;
var refreshRate = 0;
var refreshTimer;
var hiddenInputs = [];
function notifyResponse( data )
{
Object.keys(data).forEach(function(v) {
var elems = document.getElementsByName(v);
var ndx;
for(ndx = 0; ndx < elems.length; ndx++ )
{
var el = elems[ndx];
if(el.tagName == "INPUT")
{
if( el.type == "radio" )
{
el.checked = data[v] == el.value;
}
else if( el.type == "checkbox" )
{
if( data[v] == "on" )
el.checked = true;
else if( data[v] == "off" )
el.checked = false;
else if( data[v] == true )
el.checked = true;
else
el.checked = false;
}
else
{
el.value = data[v];
}
}
if(el.tagName == "SELECT")
{
el.value = data[v];
}
}
var elem = document.getElementById(v);
if( elem != null )
{
if(elem.tagName == "P" || elem.tagName == "DIV" || elem.tagName == "SPAN" || elem.tagName == "TR" || elem.tagName == "TH" || elem.tagName == "TD" ||
elem.tagName == "TEXTAREA" )
{
elem.innerHTML = data[v];
}
if(elem.tagName == "UL" || elem.tagName == "OL")
{
var list = data[v];
var html = "";
for (var i=0; i<list.length; i++) {
html = html.concat("<li>" + list[i] + "</li>");
}
elem.innerHTML = html;
}
if(elem.tagName == "TABLE")
{
var list = data[v];
var html = "";
if( list.length > 0 )
{
var ths = list[0];
html = html.concat("<tr>");
for (var i=0; i<ths.length; i++) {
html = html.concat("<th>" + ths[i] + "</th>");
}
html = html.concat("</tr>");
}
for (var i=1; i<list.length; i++) {
var tds = list[i];
html = html.concat("<tr>");
for (var j=0; j<tds.length; j++) {
html = html.concat("<td>" + tds[j] + "</td>");
}
html = html.concat("</tr>");
}
elem.innerHTML = html;
}
}
});
if( refreshRate != 0 )
{
clearTimeout(refreshTimer);
refreshTimer = setTimeout( function () {
ajaxJson("GET", window.location.pathname + ".json?reason=refresh", notifyResponse );
}, refreshRate );
}
}
function notifyButtonPressed( btnId )
{
ajaxJson("POST", window.location.pathname + ".json?reason=button\&id=" + btnId, notifyResponse);
}
function refreshFormData()
{
setTimeout( function () {
ajaxJson("GET", window.location.pathname + ".json?reason=refresh", function (resp) {
notifyResponse(resp);
if( loadCounter > 0 )
{
loadCounter--;
refreshFormData();
}
} );
} , 250);
}
function recalculateHiddenInputs()
{
for(var i=0; i < hiddenInputs.length; i++)
{
var hinput = hiddenInputs[i];
var name = hinput.name;
var elems = document.getElementsByName(name);
for(var j=0; j < elems.length; j++ )
{
var chk = elems[j];
var inptp = chk.type;
if( inptp == "checkbox" ) {
if( chk.checked )
{
hinput.disabled = true;
hinput.value = "on";
}
else
{
hinput.disabled = false;
hinput.value = "off";
}
}
}
}
}
document.addEventListener("DOMContentLoaded", function(){
// collect buttons
var btns = document.getElementsByTagName("button");
var ndx;
for (ndx = 0; ndx < btns.length; ndx++) {
var btn = btns[ndx];
var id = btn.getAttribute("id");
var onclk = btn.getAttribute("onclick");
var type = btn.getAttribute("type");
if( id != null && onclk == null && type == "button" )
{
var fn;
eval( "fn = function() { notifyButtonPressed(\"" + id + "\") }" );
btn.onclick = fn;
}
}
// collect forms
var frms = document.getElementsByTagName("form");
for (ndx = 0; ndx < frms.length; ndx++) {
var frm = frms[ndx];
var method = frm.method;
var action = frm.action;
frm.method = "POST";
frm.action = window.location.pathname + ".json?reason=submit";
loadCounter = 4;
frm.onsubmit = function () {
recalculateHiddenInputs();
refreshFormData();
return true;
};
}
// collect metas
var metas = document.getElementsByTagName("meta");
for (ndx = 0; ndx < metas.length; ndx++) {
var meta = metas[ndx];
if( meta.getAttribute("name") == "refresh-rate" )
{
refreshRate = meta.getAttribute("content");
}
}
// collect checkboxes
var inputs = document.getElementsByTagName("input");
for (ndx = 0; ndx < inputs.length; ndx++) {
var inp = inputs[ndx];
if( inp.getAttribute("type") == "checkbox" )
{
var name = inp.getAttribute("name");
var hasHidden = false;
if( name != null )
{
var inpelems = document.getElementsByName(name);
for(var i=0; i < inpelems.length; i++ )
{
var inptp = inpelems[i].type;
if( inptp == "hidden" )
hasHidden = true;
}
}
if( !hasHidden )
{
var parent = inp.parentElement;
var input = document.createElement("input");
input.type = "hidden";
input.name = inp.name;
parent.appendChild(input);
hiddenInputs.push(input);
}
}
}
// load variables at first time
var loadVariables = function() {
ajaxJson("GET", window.location.pathname + ".json?reason=load", notifyResponse,
function () { setTimeout(loadVariables, 1000); }
);
};
loadVariables();
});

@ -0,0 +1,29 @@
<div id="main">
<div class="header">
<h1>Web Server</h1>
</div>
<div class="content">
<p>User defined web pages can be uploaded to esp-link. This is useful if esp-link acts as a web server while MCU provides
the measurement data.</p>
<form method="post" action="web-server/upload" name="submit" enctype="multipart/form-data" onSubmit="return onSubmit()">
The custom web page to upload: <input type="file" name="webpage" multiple>
<input type="submit" name="submit" value="Submit">
</form>
</div>
</div>
<script>
var allowSubmit = true;
function onSubmit() {
setTimeout(function() {
window.location.reload();
}, 1000);
return true;
}
</script>
</body></html>

@ -31,7 +31,6 @@
<div id="AP_Settings-spinner" class="spinner spinner-small"></div> <div id="AP_Settings-spinner" class="spinner spinner-small"></div>
<form action="#" id="AP_Settings-form" class="pure-form" hidden> <form action="#" id="AP_Settings-form" class="pure-form" hidden>
<!-- <input type="text" id="conn_check" name="ap_connex" value="0" hidden>-->
<legend>Soft-AP main settings, use with care!</legend> <legend>Soft-AP main settings, use with care!</legend>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
@ -47,21 +46,17 @@
</div> </div>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
<legend>Soft-AP Advanced Settings</legend> <label>Soft-AP Auth Mode</label>
</div> <select name="ap_authmode" href="#">
<option value="0">OPEN</option>
<div class="form-horizontal"> <option value="1">WEP</option>
<label for="AP_Settings-ron" style="margin-right:1em"> <option value="2">WPA_PSK</option>
<input type="radio" name="ap" value="on" id="AP_Settings-ron"/> <option value="3">WPA2_PSK</option>
Show </label> <option value="4">WPA_WPA2_PSK</option>
<label for="AP_Settings-roff" style="margin-right:1em"> </select>
<input type="radio" name="ap" value="off" id="AP_Settings-roff"/> <div class="popup">Default WPA_WPA2_PSK</div>
Hide </label>
</div> </div>
<div id="AP_Settings-off" class="pure-form-stacked"></div>
<div id="AP_Settings-on" class="pure-form-stacked">
<div class="pure-form-stacked"> <div class="pure-form-stacked">
<label>Soft-AP Max Connections</label> <label>Soft-AP Max Connections</label>
<input type="text" name="ap_maxconn" /> <input type="text" name="ap_maxconn" />
@ -74,25 +69,11 @@
<div class="popup">Between 100 - 60000 ms ( default 100ms )</div> <div class="popup">Between 100 - 60000 ms ( default 100ms )</div>
</div> </div>
<div class="pure-form-stacked">
<label>Soft-AP Auth Mode</label>
<select name="ap_authmode" href="#">
<option value="0">OPEN</option>
<option value="1">WEP</option>
<option value="2">WPA_PSK</option>
<option value="3">WPA2_PSK</option>
<option value="4">WPA_WPA2_PSK</option>
</select>
<div class="popup">Default WPA_WPA2_PSK</div>
</div>
<div class="form-horizontal"> <div class="form-horizontal">
<label><input type="checkbox" name="ap_hidden" />Soft-AP SSID hidden</label> <label><input type="checkbox" name="ap_hidden" />Soft-AP SSID hidden</label>
<div class="popup">Check this box to hide you Soft-AP SSID ( default Not Hidden )</div> <div class="popup">Check this box to hide you Soft-AP SSID ( default Not Hidden )</div>
</div> </div>
</div>
<button id="AP_Settings-button" type="submit" class="pure-button button-primary"> <button id="AP_Settings-button" type="submit" class="pure-button button-primary">
Change Soft-AP settings! Change Soft-AP settings!
</button> </button>
@ -114,10 +95,7 @@ onLoad(function() {
getWifiInfo(); getWifiInfo();
// Fetch actual settings // Fetch actual settings
fetchApSettings(); fetchApSettings();
// Hide advanced settings // Wire-up form
undoApAdvanced();
bnd($("#AP_Settings-ron"), "click", doApAdvanced);
bnd($("#AP_Settings-roff"), "click", undoApAdvanced);
bnd($("#AP_Settings-form"), "submit", changeApSettings); bnd($("#AP_Settings-form"), "submit", changeApSettings);
}); });
</script> </script>

@ -83,15 +83,3 @@ function fetchApSettings() {
window.setTimeout(fetchApSettings, 1000); window.setTimeout(fetchApSettings, 1000);
}); });
} }
function doApAdvanced() {
$('#AP_Settings-on').removeAttribute('hidden');
$("#AP_Settings-off").setAttribute("hidden", "");
$("#AP_Settings-roff").removeAttribute("checked");
}
function undoApAdvanced(){
$("#AP_Settings-on").setAttribute("hidden", "");
$("#AP_Settings-off").removeAttribute("hidden");
$("#AP_Settings-roff").setAttribute("checked", "");
}

@ -90,11 +90,15 @@ function scanAPs() {
scanTimeout = null; scanTimeout = null;
scanReqCnt = 0; scanReqCnt = 0;
ajaxReq('POST', "scan", function(data) { ajaxReq('POST', "scan", function(data) {
//showNotification("Wifi scan started"); showNotification("Wifi scan started");
window.setTimeout(scanResult, 1000); window.setTimeout(scanResult, 1000);
}, function(s, st) { }, function(s, st) {
//showNotification("Wifi scan may have started?"); if (s == 400) {
window.setTimeout(scanResult, 1000); showWarning("Cannot scan in AP mode");
$("#aps").innerHTML =
"Switch to <a href=\"#\" onclick=\"changeWifiMode(3)\">STA+AP mode</a> to scan.";
} else showWarning("Failed to scan: " + st);
//window.setTimeout(scanResult, 1000);
}); });
} }
@ -109,12 +113,10 @@ function getStatus() {
showNotification(txt); showNotification(txt);
showWifiInfo(data); showWifiInfo(data);
blockScan = 0; blockScan = 0;
if (data.modechange == "yes") { if (data.modechange == "yes") {
var txt2 = "esp-link will switch to STA-only mode in a few seconds"; var txt2 = "esp-link will switch to STA-only mode in a few seconds";
window.setTimeout(function() { showNotification(txt2); }, 4000); window.setTimeout(function() { showNotification(txt2); }, 4000);
} }
$("#reconnect").removeAttribute("hidden"); $("#reconnect").removeAttribute("hidden");
$("#reconnect").innerHTML = $("#reconnect").innerHTML =
"If you are in the same network, go to <a href=\"http://"+data.ip+ "If you are in the same network, go to <a href=\"http://"+data.ip+
@ -138,6 +140,8 @@ function changeWifiMode(m) {
showNotification("Mode changed"); showNotification("Mode changed");
window.setTimeout(getWifiInfo, 100); window.setTimeout(getWifiInfo, 100);
blockScan = 0; blockScan = 0;
window.setTimeout(scanAPs, 500);
$("#aps").innerHTML = 'Scanning... <div class="spinner spinner-small"></div>';
}, function(s, st) { }, function(s, st) {
showWarning("Error changing mode: " + st); showWarning("Error changing mode: " + st);
window.setTimeout(getWifiInfo, 100); window.setTimeout(getWifiInfo, 100);

@ -17,6 +17,7 @@ Esp8266 http server - core routines
#include <esp8266.h> #include <esp8266.h>
#include "httpd.h" #include "httpd.h"
//#define HTTPD_DBG
#ifdef HTTPD_DBG #ifdef HTTPD_DBG
#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
#else #else
@ -132,6 +133,7 @@ static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) {
if (conn->post->buff != NULL) os_free(conn->post->buff); if (conn->post->buff != NULL) os_free(conn->post->buff);
conn->cgi = NULL; conn->cgi = NULL;
conn->post->buff = NULL; conn->post->buff = NULL;
conn->post->multipartBoundary = NULL;
} }
//Stupid li'l helper function that returns the value of a hex char. //Stupid li'l helper function that returns the value of a hex char.
@ -354,14 +356,18 @@ static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) {
if (conn->cgi == NULL) { if (conn->cgi == NULL) {
while (builtInUrls[i].url != NULL) { while (builtInUrls[i].url != NULL) {
int match = 0; int match = 0;
int urlLen = os_strlen(builtInUrls[i].url);
//See if there's a literal match //See if there's a literal match
if (os_strcmp(builtInUrls[i].url, conn->url) == 0) match = 1; if (os_strcmp(builtInUrls[i].url, conn->url) == 0) match = 1;
//See if there's a wildcard match //See if there's a wildcard match
if (builtInUrls[i].url[os_strlen(builtInUrls[i].url) - 1] == '*' && if (builtInUrls[i].url[urlLen - 1] == '*' &&
os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url) - 1) == 0) match = 1; os_strncmp(builtInUrls[i].url, conn->url, urlLen - 1) == 0) match = 1;
else if (builtInUrls[i].url[0] == '*' && ( strlen(conn->url) >= urlLen -1 ) &&
os_strncmp(builtInUrls[i].url + 1, conn->url + strlen(conn->url) - urlLen + 1, urlLen - 1) == 0) match = 1;
if (match) { if (match) {
//os_printf("Is url index %d\n", i); //os_printf("Is url index %d\n", i);
conn->cgiData = NULL; conn->cgiData = NULL;
conn->cgiResponse = NULL;
conn->cgi = builtInUrls[i].cgiCb; conn->cgi = builtInUrls[i].cgiCb;
conn->cgiArg = builtInUrls[i].cgiArg; conn->cgiArg = builtInUrls[i].cgiArg;
break; break;
@ -509,6 +515,7 @@ static void ICACHE_FLASH_ATTR httpdRecvCb(void *arg, char *data, unsigned short
if (data[x] == '\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n") != NULL) { if (data[x] == '\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n") != NULL) {
//Indicate we're done with the headers. //Indicate we're done with the headers.
conn->post->len = 0; conn->post->len = 0;
conn->post->multipartBoundary = NULL;
//Reset url data //Reset url data
conn->url = NULL; conn->url = NULL;
//Iterate over all received headers and parse them. //Iterate over all received headers and parse them.
@ -621,3 +628,43 @@ void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) {
espconn_accept(&httpdConn); espconn_accept(&httpdConn);
espconn_tcp_set_max_con_allow(&httpdConn, MAX_CONN); espconn_tcp_set_max_con_allow(&httpdConn, MAX_CONN);
} }
// looks up connection handle based on ip / port
HttpdConnData * ICACHE_FLASH_ATTR httpdLookUpConn(uint8_t * ip, int port) {
int i;
for (i = 0; i<MAX_CONN; i++)
{
HttpdConnData *conn = connData+i;
if (conn->conn == NULL)
continue;
if (conn->cgi == NULL)
continue;
if (conn->conn->proto.tcp->remote_port != port )
continue;
if (os_memcmp(conn->conn->proto.tcp->remote_ip, ip, 4) != 0)
continue;
return conn;
}
return NULL;
}
// this method is used for setting the response of a CGI handler outside of the HTTP callback
// this method useful at the following scenario:
// Browser -> CGI handler -> MCU request
// MCU response -> CGI handler -> browser
// when MCU response arrives, the handler looks up connection based on ip/port and call httpdSetCGIResponse with the data to transmit
int ICACHE_FLASH_ATTR httpdSetCGIResponse(HttpdConnData * conn, void * response) {
char sendBuff[MAX_SENDBUFF_LEN];
conn->priv->sendBuff = sendBuff;
conn->priv->sendBuffLen = 0;
conn->cgiResponse = response;
httpdProcessRequest(conn);
conn->cgiResponse = NULL;
return HTTPD_CGI_DONE;
}

@ -30,6 +30,7 @@ struct HttpdConnData {
const void *cgiArg; const void *cgiArg;
void *cgiData; void *cgiData;
void *cgiPrivData; // Used for streaming handlers storing state between requests void *cgiPrivData; // Used for streaming handlers storing state between requests
void *cgiResponse; // used for forwarding response to the CGI handler
HttpdPriv *priv; HttpdPriv *priv;
cgiSendCallback cgi; cgiSendCallback cgi;
HttpdPostData *post; HttpdPostData *post;
@ -66,5 +67,7 @@ void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn);
int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen); int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen);
int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len); int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len);
void ICACHE_FLASH_ATTR httpdFlush(HttpdConnData *conn); void ICACHE_FLASH_ATTR httpdFlush(HttpdConnData *conn);
HttpdConnData * ICACHE_FLASH_ATTR httpdLookUpConn(uint8_t * ip, int port);
int ICACHE_FLASH_ATTR httpdSetCGIResponse(HttpdConnData * conn, void *response);
#endif #endif

@ -14,11 +14,12 @@ Connector to let httpd use the espfs filesystem to serve the files in it.
*/ */
#include "httpdespfs.h" #include "httpdespfs.h"
#define MAX_URL_LEN 255
// The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression. // 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.) // If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.)
static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n"; static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n";
//This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding //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 //path in the filesystem and if it exists, passes the file through. This simulates what a normal
//webserver would do with static files. //webserver would do with static files.
@ -40,8 +41,20 @@ cgiEspFsHook(HttpdConnData *connData) {
if (file==NULL) { if (file==NULL) {
//First call to this cgi. Open the file so we can read it. //First call to this cgi. Open the file so we can read it.
file=espFsOpen(connData->url); file=espFsOpen(espLinkCtx, connData->url);
if (file==NULL) { if (file==NULL) {
if( espFsIsValid(userPageCtx) )
{
int maxLen = strlen(connData->url) * 2 + 1;
if( maxLen > MAX_URL_LEN )
maxLen = MAX_URL_LEN;
char decodedURL[maxLen];
httpdUrlDecode(connData->url, strlen(connData->url), decodedURL, maxLen);
file = espFsOpen(userPageCtx, decodedURL );
if( file == NULL )
return HTTPD_CGI_NOTFOUND;
}
else
return HTTPD_CGI_NOTFOUND; return HTTPD_CGI_NOTFOUND;
} }

@ -0,0 +1,307 @@
#include <esp8266.h>
#include <osapi.h>
#include "multipart.h"
#include "cgi.h"
#define BOUNDARY_SIZE 128
typedef enum {
STATE_SEARCH_BOUNDARY = 0, // state: searching multipart boundary
STATE_SEARCH_HEADER, // state: search multipart file header
STATE_SEARCH_HEADER_END, // state: search the end of the file header
STATE_UPLOAD_FILE, // state: read file content
STATE_ERROR, // state: error (stop processing)
} MultipartState;
struct _MultipartCtx {
MultipartCallback callBack; // callback for multipart events
int position; // current file position
int startTime; // timestamp when connection was initiated
int recvPosition; // receive position (how many bytes was processed from the HTTP post)
char * boundaryBuffer; // buffer used for boundary detection
int boundaryBufferPtr; // pointer in the boundary buffer
MultipartState state; // multipart processing state
};
// this method is responsible for creating the multipart context
MultipartCtx * ICACHE_FLASH_ATTR multipartCreateContext(MultipartCallback callback)
{
MultipartCtx * ctx = (MultipartCtx *)os_malloc(sizeof(MultipartCtx));
ctx->callBack = callback;
ctx->position = ctx->startTime = ctx->recvPosition = ctx->boundaryBufferPtr = 0;
ctx->boundaryBuffer = NULL;
ctx->state = STATE_SEARCH_BOUNDARY;
return ctx;
}
// for allocating buffer for multipart upload
void ICACHE_FLASH_ATTR multipartAllocBoundaryBuffer(MultipartCtx * context)
{
if( context->boundaryBuffer == NULL )
context->boundaryBuffer = (char *)os_malloc(3*BOUNDARY_SIZE + 1);
context->boundaryBufferPtr = 0;
}
// for freeing multipart buffer
void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context)
{
if( context->boundaryBuffer != NULL )
{
os_free(context->boundaryBuffer);
context->boundaryBuffer = NULL;
}
}
// for destroying the context
void ICACHE_FLASH_ATTR multipartDestroyContext(MultipartCtx * context)
{
multipartFreeBoundaryBuffer(context);
os_free(context);
}
// this is because of os_memmem is missing
void * mp_memmem(const void *l, size_t l_len, const void *s, size_t s_len)
{
register char *cur, *last;
const char *cl = (const char *)l;
const char *cs = (const char *)s;
/* we need something to compare */
if (l_len == 0 || s_len == 0)
return NULL;
/* "s" must be smaller or equal to "l" */
if (l_len < s_len)
return NULL;
/* special case where s_len == 1 */
if (s_len == 1)
return memchr(l, (int)*cs, l_len);
/* the last position where its possible to find "s" in "l" */
last = (char *)cl + l_len - s_len;
for (cur = (char *)cl; cur <= last; cur++)
if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
return cur;
return NULL;
}
// this method is for processing data coming from the HTTP post request
// context: the multipart context
// boundary: a string which indicates boundary
// data: the received data
// len: the received data length (can't be bigger than BOUNDARY_SIZE)
// last: last packet indicator
//
// Detecting a boundary is not easy. One has to take care of boundaries which are splitted in 2 packets
// [Packet 1, 5 bytes of the boundary][Packet 2, remaining 10 bytes of the boundary];
//
// Algorythm:
// - create a buffer which size is 3*BOUNDARY_SIZE
// - put data into the buffer as long as the buffer size is smaller than 2*BOUNDARY_SIZE
// - search boundary in the received buffer, if found: boundary reached -> process data before boundary -> process boundary
// - if not found -> process the first BOUNDARY_SIZE amount of bytes from the buffer
// - remove processed data from the buffer
// this algorythm guarantees that no boundary loss will happen
int ICACHE_FLASH_ATTR multipartProcessData(MultipartCtx * context, char * boundary, char * data, int len, int last)
{
if( len != 0 ) // add data to the boundary buffer
{
os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, data, len);
context->boundaryBufferPtr += len;
context->boundaryBuffer[context->boundaryBufferPtr] = 0;
}
while( context->boundaryBufferPtr > 0 )
{
if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) // return if buffer is too small and not the last packet is processed
return 0;
int dataSize = BOUNDARY_SIZE;
char * boundaryLoc = mp_memmem( context->boundaryBuffer, context->boundaryBufferPtr, boundary, os_strlen(boundary) );
if( boundaryLoc != NULL )
{
int pos = boundaryLoc - context->boundaryBuffer;
if( pos > BOUNDARY_SIZE ) // process in the next call
boundaryLoc = NULL;
else
dataSize = pos;
}
if( dataSize != 0 ) // data to process
{
switch( context->state )
{
case STATE_SEARCH_HEADER:
case STATE_SEARCH_HEADER_END:
{
char * chr = os_strchr( context->boundaryBuffer, '\n' );
if( chr != NULL )
{
// chop datasize to contain only one line
int pos = chr - context->boundaryBuffer + 1;
if( pos < dataSize ) // if chop smaller than the dataSize, delete the boundary
{
dataSize = pos;
boundaryLoc = NULL; // process boundary next time
}
if( context->state == STATE_SEARCH_HEADER_END )
{
if( pos == 1 || ( ( pos == 2 ) && ( context->boundaryBuffer[0] == '\r' ) ) ) // empty line?
{
context->state = STATE_UPLOAD_FILE;
context->position = 0;
}
}
else if( os_strncmp( context->boundaryBuffer, "Content-Disposition:", 20 ) == 0 )
{
char * fnam = os_strstr( context->boundaryBuffer, "filename=" );
if( fnam != NULL )
{
int pos = fnam - context->boundaryBuffer + 9;
if( pos < dataSize )
{
while(context->boundaryBuffer[pos] == ' ') pos++; // skip spaces
if( context->boundaryBuffer[pos] == '"' ) // quote start
{
pos++;
int start = pos;
while( pos < context->boundaryBufferPtr )
{
if( context->boundaryBuffer[pos] == '"' ) // quote end
break;
pos++;
}
if( pos < context->boundaryBufferPtr )
{
context->boundaryBuffer[pos] = 0; // terminating zero for the file name
os_printf("Uploading file: %s\n", context->boundaryBuffer + start);
if( context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ) ) // FILE_START callback
return 1; // if an error happened
context->boundaryBuffer[pos] = '"'; // restore the original quote
context->state = STATE_SEARCH_HEADER_END;
}
}
}
}
}
}
}
break;
case STATE_UPLOAD_FILE:
{
char c = context->boundaryBuffer[dataSize];
context->boundaryBuffer[dataSize] = 0; // add terminating zero (for easier handling)
if( context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ) ) // FILE_DATA callback
return 1;
context->boundaryBuffer[dataSize] = c;
context->position += dataSize;
}
break;
default:
break;
}
}
if( boundaryLoc != NULL ) // boundary found?
{
dataSize += os_strlen(boundary); // jump over the boundary
if( context->state == STATE_UPLOAD_FILE )
{
if( context->callBack( FILE_DONE, NULL, 0, context->position ) ) // file done callback
return 1; // if an error happened
os_printf("File upload done\n");
}
context->state = STATE_SEARCH_HEADER; // search the next header
}
// move the buffer back with dataSize
context->boundaryBufferPtr -= dataSize;
os_memcpy(context->boundaryBuffer, context->boundaryBuffer + dataSize, context->boundaryBufferPtr);
}
return 0;
}
// for processing multipart requests
int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * connData )
{
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
if (connData->requestType == HTTPD_METHOD_POST) {
HttpdPostData *post = connData->post;
if( post->multipartBoundary == NULL )
{
errorResponse(connData, 404, "Only multipart POST is supported");
return HTTPD_CGI_DONE;
}
if( connData->startTime != context->startTime )
{
// reinitialize, as this is a new request
context->position = 0;
context->recvPosition = 0;
context->startTime = connData->startTime;
context->state = STATE_SEARCH_BOUNDARY;
multipartAllocBoundaryBuffer(context);
if( context->callBack( FILE_UPLOAD_START, NULL, 0, context->position ) ) // start uploading files
context->state = STATE_ERROR;
}
if( context->state != STATE_ERROR )
{
int feed = 0;
while( feed < post->buffLen )
{
int len = post->buffLen - feed;
if( len > BOUNDARY_SIZE )
len = BOUNDARY_SIZE;
if( multipartProcessData(context, post->multipartBoundary, post->buff + feed, len, 0) )
{
context->state = STATE_ERROR;
break;
}
feed += len;
}
}
context->recvPosition += post->buffLen;
if( context->recvPosition < post->len )
return HTTPD_CGI_MORE;
if( context->state != STATE_ERROR )
{
// this is the last package, process the remaining data
if( multipartProcessData(context, post->multipartBoundary, NULL, 0, 1) )
context->state = STATE_ERROR;
else if( context->callBack( FILE_UPLOAD_DONE, NULL, 0, context->position ) ) // done with files
context->state = STATE_ERROR;
}
multipartFreeBoundaryBuffer( context );
if( context->state == STATE_ERROR )
errorResponse(connData, 400, "Invalid file upload!");
else
{
httpdStartResponse(connData, 204);
httpdEndHeaders(connData);
}
return HTTPD_CGI_DONE;
}
else {
errorResponse(connData, 404, "Only multipart POST is supported");
return HTTPD_CGI_DONE;
}
}

@ -0,0 +1,34 @@
#ifndef MULTIPART_H
#define MULTIPART_H
#include <httpd.h>
typedef enum {
FILE_UPLOAD_START, // multipart: uploading files started
FILE_START, // multipart: the start of a new file (can be more)
FILE_DATA, // multipart: file data
FILE_DONE, // multipart: file end
FILE_UPLOAD_DONE, // multipart: finished for all files
} MultipartCmd;
// multipart callback
// -> FILE_START : data+dataLen contains the filename, position is 0
// -> FILE_DATA : data+dataLen contains file data, position is the file position
// -> FILE_DONE : data+dataLen is 0, position is the complete file size
typedef int (* MultipartCallback)(MultipartCmd cmd, char *data, int dataLen, int position);
struct _MultipartCtx; // the context for multipart listening
typedef struct _MultipartCtx MultipartCtx;
// use this for creating a multipart context
MultipartCtx * ICACHE_FLASH_ATTR multipartCreateContext(MultipartCallback callback);
// for destroying multipart context
void ICACHE_FLASH_ATTR multipartDestroyContext(MultipartCtx * context);
// use this function for processing HTML multipart updates
int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * post );
#endif /* MULTIPART_H */

@ -2,6 +2,9 @@
#ifndef _ESP8266_H_ #ifndef _ESP8266_H_
#define _ESP8266_H_ #define _ESP8266_H_
#undef MEMLEAK_DEBUG
#define USE_OPTIMIZE_PRINTF
#include <user_config.h> #include <user_config.h>
#include <ctype.h> #include <ctype.h>
#include <stdio.h> #include <stdio.h>

@ -62,9 +62,9 @@ int os_printf_plus(const char *format, ...) __attribute__((format(printf, 1, 2)
// memory allocation functions are "different" due to memory debugging functionality // memory allocation functions are "different" due to memory debugging functionality
// added in SDK 1.4.0 // added in SDK 1.4.0
void vPortFree(void *ptr, char * file, int line); void vPortFree(void *ptr, const char * file, int line);
void *pvPortMalloc(size_t xWantedSize, char * file, int line); void *pvPortMalloc(size_t xWantedSize, const char * file, int line);
void *pvPortZalloc(size_t, char * file, int line); void *pvPortZalloc(size_t, const char * file, int line);
void *vPortMalloc(size_t xWantedSize); void *vPortMalloc(size_t xWantedSize);
void pvPortFree(void *ptr); void pvPortFree(void *ptr);

@ -163,6 +163,11 @@ typedef enum {
} UartExistParity; } UartExistParity;
typedef enum { typedef enum {
BIT_RATE_300 = 300,
BIT_RATE_600 = 600,
BIT_RATE_1200 = 1200,
BIT_RATE_2400 = 2400,
BIT_RATE_4800 = 4800,
BIT_RATE_9600 = 9600, BIT_RATE_9600 = 9600,
BIT_RATE_19200 = 19200, BIT_RATE_19200 = 19200,
BIT_RATE_38400 = 38400, BIT_RATE_38400 = 38400,

@ -22,6 +22,7 @@
#define HTTPD_DBG #define HTTPD_DBG
#define MQTT_DBG #define MQTT_DBG
#define MQTTCMD_DBG #define MQTTCMD_DBG
#define MQTTCLIENT_DBG
#undef PKTBUF_DBG #undef PKTBUF_DBG
#define REST_DBG #define REST_DBG
#define RESTCMD_DBG #define RESTCMD_DBG
@ -32,7 +33,7 @@
#define MDNS_DBG #define MDNS_DBG
#define OPTIBOOT_DBG #define OPTIBOOT_DBG
#undef SYSLOG_DBG #undef SYSLOG_DBG
#undef CGISERVICES_DBG #define CGISERVICES_DBG
// If defined, the default hostname for DHCP will include the chip ID to make it unique // If defined, the default hostname for DHCP will include the chip ID to make it unique
#undef CHIP_IN_HOSTNAME #undef CHIP_IN_HOSTNAME

@ -447,7 +447,6 @@ mqtt_send_message(MQTT_Client* client) {
// get some details about the message // get some details about the message
uint16_t msg_type = mqtt_get_type(buf->data); uint16_t msg_type = mqtt_get_type(buf->data);
uint8_t msg_id = mqtt_get_id(buf->data, buf->filled); uint8_t msg_id = mqtt_get_id(buf->data, buf->filled);
msg_id = msg_id;
#ifdef MQTT_DBG #ifdef MQTT_DBG
os_printf("MQTT: Send type=%s id=%04X len=%d\n", mqtt_msg_type[msg_type], msg_id, buf->filled); os_printf("MQTT: Send type=%s id=%04X len=%d\n", mqtt_msg_type[msg_type], msg_id, buf->filled);
#if 0 #if 0
@ -629,7 +628,7 @@ void ICACHE_FLASH_ATTR
MQTT_Init(MQTT_Client* client, char* host, uint32 port, uint8_t security, uint8_t sendTimeout, MQTT_Init(MQTT_Client* client, char* host, uint32 port, uint8_t security, uint8_t sendTimeout,
char* client_id, char* client_user, char* client_pass, char* client_id, char* client_user, char* client_pass,
uint8_t keepAliveTime) { uint8_t keepAliveTime) {
DBG_MQTT("MQTT_Init\n"); DBG_MQTT("MQTT_Init, host=%s\n", host);
os_memset(client, 0, sizeof(MQTT_Client)); os_memset(client, 0, sizeof(MQTT_Client));

@ -13,8 +13,16 @@
#define DBG(format, ...) do { } while(0) #define DBG(format, ...) do { } while(0)
#endif #endif
static bool blocked; // flag to prevent MQTT from sending on serial while trying to PGM uC
void ICACHE_FLASH_ATTR
mqtt_block() { blocked = true; }
void ICACHE_FLASH_ATTR
mqtt_unblock() { blocked = false; }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
cmdMqttConnectedCb(MQTT_Client* client) { cmdMqttConnectedCb(MQTT_Client* client) {
if (blocked) return;
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Connected Cb=%p\n", (void*)cb->connectedCb); DBG("MQTT: Connected Cb=%p\n", (void*)cb->connectedCb);
cmdResponseStart(CMD_RESP_CB, cb->connectedCb, 0); cmdResponseStart(CMD_RESP_CB, cb->connectedCb, 0);
@ -23,6 +31,7 @@ cmdMqttConnectedCb(MQTT_Client* client) {
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
cmdMqttDisconnectedCb(MQTT_Client* client) { cmdMqttDisconnectedCb(MQTT_Client* client) {
if (blocked) return;
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Disconnected cb=%p\n", (void*)cb->disconnectedCb); DBG("MQTT: Disconnected cb=%p\n", (void*)cb->disconnectedCb);
cmdResponseStart(CMD_RESP_CB, cb->disconnectedCb, 0); cmdResponseStart(CMD_RESP_CB, cb->disconnectedCb, 0);
@ -31,6 +40,7 @@ cmdMqttDisconnectedCb(MQTT_Client* client) {
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
cmdMqttPublishedCb(MQTT_Client* client) { cmdMqttPublishedCb(MQTT_Client* client) {
if (blocked) return;
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Published cb=%p\n", (void*)cb->publishedCb); DBG("MQTT: Published cb=%p\n", (void*)cb->publishedCb);
cmdResponseStart(CMD_RESP_CB, cb->publishedCb, 0); cmdResponseStart(CMD_RESP_CB, cb->publishedCb, 0);
@ -41,6 +51,7 @@ void ICACHE_FLASH_ATTR
cmdMqttDataCb(MQTT_Client* client, const char* topic, uint32_t topic_len, cmdMqttDataCb(MQTT_Client* client, const char* topic, uint32_t topic_len,
const char* data, uint32_t data_len) const char* data, uint32_t data_len)
{ {
if (blocked) return;
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; MqttCmdCb* cb = (MqttCmdCb*)client->user_data;
DBG("MQTT: Data cb=%p topic=%s len=%u\n", (void*)cb->dataCb, topic, data_len); DBG("MQTT: Data cb=%p topic=%s len=%u\n", (void*)cb->dataCb, topic, data_len);
@ -184,59 +195,6 @@ MQTTCMD_Setup(CmdPacket *cmd) {
if (cmdGetArgc(&req) != 4) return; if (cmdGetArgc(&req) != 4) return;
#if 0
if (cmdGetArgc(&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 = cmdArgLen(&req);
if (len > 32) return 0; // safety check
client_id = (uint8_t*)os_zalloc(len + 1);
cmdPopArg(&req, client_id, len);
client_id[len] = 0;
// get username
len = cmdArgLen(&req);
if (len > 32) return 0; // safety check
user_data = (uint8_t*)os_zalloc(len + 1);
cmdPopArg(&req, user_data, len);
user_data[len] = 0;
// get password
len = cmdArgLen(&req);
if (len > 32) return 0; // safety check
pass_data = (uint8_t*)os_zalloc(len + 1);
cmdPopArg(&req, pass_data, len);
pass_data[len] = 0;
// get keepalive
cmdPopArg(&req, (uint8_t*)&keepalive, 4);
// get clean session
cmdPopArg(&req, (uint8_t*)&clean_session, 4);
#ifdef MQTTCMD_DBG
DBG("MQTT: MQTTCMD_Setup clientid=%s, user=%s, pw=%s, keepalive=%ld, clean_session=%ld\n", client_id, user_data, pass_data, keepalive, clean_session);
#endif
// 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 // create callback
MqttCmdCb* callback = (MqttCmdCb*)os_zalloc(sizeof(MqttCmdCb)); MqttCmdCb* callback = (MqttCmdCb*)os_zalloc(sizeof(MqttCmdCb));
cmdPopArg(&req, &callback->connectedCb, 4); cmdPopArg(&req, &callback->connectedCb, 4);
@ -259,81 +217,3 @@ MQTTCMD_Setup(CmdPacket *cmd) {
cmdMqttDisconnectedCb(client); cmdMqttDisconnectedCb(client);
} }
} }
#if 0
uint32_t ICACHE_FLASH_ATTR
MQTTCMD_Connect(CmdPacket *cmd) {
CmdRequest req;
cmdRequest(&req, cmd);
#ifdef MQTT_1_CLIENT
if (mqttClient.connState == MQTT_CONNECTED && mqttClient.cmdConnectedCb) {
mqttClient.cmdConnectedCb((uint32_t*)&mqttClient);
}
else if (mqttClient.connState == MQTT_DISCONNECTED && mqttClient.cmdDisconnectedCb) {
mqttClient.cmdDisconnectedCb((uint32_t*)&mqttClient);
}
return 1;
#else
if (cmdGetArgc(&req) != 4)
return 0;
// get mqtt client
uint32_t client_ptr;
cmdPopArg(&req, (uint8_t*)&client_ptr, 4);
MQTT_Client* client = (MQTT_Client*)client_ptr;
DBG("MQTT: MQTTCMD_Connect client ptr=%p\n", (void*)client_ptr);
uint16_t len;
// get host
if (client->host)
os_free(client->host);
len = cmdArgLen(&req);
if (len > 128) return 0; // safety check
client->host = (char*)os_zalloc(len + 1);
cmdPopArg(&req, client->host, len);
client->host[len] = 0;
// get port
cmdPopArg(&req, (uint8_t*)&client->port, 4);
// get security
cmdPopArg(&req, (uint8_t*)&client->security, 4);
DBG("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;
cmdRequest(&req, cmd);
#ifdef MQTT_1_CLIENT
return 1;
#else
if (cmdGetArgc(&req) != 1)
return 0;
// get mqtt client
uint32_t client_ptr;
cmdPopArg(&req, (uint8_t*)&client_ptr, 4);
MQTT_Client* client = (MQTT_Client*)client_ptr;
DBG("MQTT: MQTTCMD_Disconnect client ptr=%p\n", (void*)client_ptr);
// disconnect
MQTT_Disconnect(client);
return 1;
#endif
}
#endif

@ -17,4 +17,7 @@ void MQTTCMD_Publish(CmdPacket *cmd);
void MQTTCMD_Subscribe(CmdPacket *cmd); void MQTTCMD_Subscribe(CmdPacket *cmd);
void MQTTCMD_Lwt(CmdPacket *cmd); void MQTTCMD_Lwt(CmdPacket *cmd);
void mqtt_block();
void mqtt_unblock();
#endif /* MODULES_MQTT_CMD_H_ */ #endif /* MODULES_MQTT_CMD_H_ */

@ -5,6 +5,7 @@
#include "cgi.h" #include "cgi.h"
#include "uart.h" #include "uart.h"
#include "serbridge.h" #include "serbridge.h"
#include "serled.h"
#include "config.h" #include "config.h"
#include "console.h" #include "console.h"
@ -66,7 +67,7 @@ ajaxConsoleBaud(HttpdConnData *connData) {
len = httpdFindArg(connData->getArgs, "rate", buff, sizeof(buff)); len = httpdFindArg(connData->getArgs, "rate", buff, sizeof(buff));
if (len > 0) { if (len > 0) {
int rate = atoi(buff); int rate = atoi(buff);
if (rate >= 9600 && rate <= 1000000) { if (rate >= 300 && rate <= 1000000) {
uart0_baud(rate); uart0_baud(rate);
flashConfig.baud_rate = rate; flashConfig.baud_rate = rate;
status = configSave() ? 200 : 400; status = configSave() ? 200 : 400;
@ -81,6 +82,35 @@ ajaxConsoleBaud(HttpdConnData *connData) {
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
int ICACHE_FLASH_ATTR
ajaxConsoleFormat(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[16];
int len, status = 400;
len = httpdFindArg(connData->getArgs, "fmt", buff, sizeof(buff));
if (len >= 3) {
int c = buff[0];
if (c >= '5' && c <= '8') flashConfig.data_bits = c - '5' + FIVE_BITS;
if (buff[1] == 'N') flashConfig.parity = NONE_BITS;
if (buff[1] == 'E') flashConfig.parity = EVEN_BITS;
if (buff[1] == 'O') flashConfig.parity = ODD_BITS;
if (buff[2] == '1') flashConfig.stop_bits = ONE_STOP_BIT;
if (buff[2] == '2') flashConfig.stop_bits = TWO_STOP_BIT;
uart0_config(flashConfig.data_bits, flashConfig.parity, flashConfig.stop_bits);
status = configSave() ? 200 : 400;
} else if (connData->requestType == HTTPD_METHOD_GET) {
status = 200;
}
jsonHeader(connData, status);
os_sprintf(buff, "{\"fmt\": \"%c%c%c\"}", flashConfig.data_bits + '5',
flashConfig.parity ? 'E' : 'N', flashConfig.stop_bits ? '2': '1');
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
int ICACHE_FLASH_ATTR int ICACHE_FLASH_ATTR
ajaxConsoleSend(HttpdConnData *connData) { ajaxConsoleSend(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
@ -90,6 +120,7 @@ ajaxConsoleSend(HttpdConnData *connData) {
// figure out where to start in buffer based on URI param // figure out where to start in buffer based on URI param
len = httpdFindArg(connData->getArgs, "text", buff, sizeof(buff)); len = httpdFindArg(connData->getArgs, "text", buff, sizeof(buff));
if (len > 0) { if (len > 0) {
serledFlash(50); // short blink on serial LED
uart0_tx_buffer(buff, len); uart0_tx_buffer(buff, len);
status = 200; status = 200;
} }

@ -8,6 +8,7 @@ void ICACHE_FLASH_ATTR console_write_char(char c);
int ajaxConsole(HttpdConnData *connData); int ajaxConsole(HttpdConnData *connData);
int ajaxConsoleReset(HttpdConnData *connData); int ajaxConsoleReset(HttpdConnData *connData);
int ajaxConsoleBaud(HttpdConnData *connData); int ajaxConsoleBaud(HttpdConnData *connData);
int ajaxConsoleFormat(HttpdConnData *connData);
int ajaxConsoleSend(HttpdConnData *connData); int ajaxConsoleSend(HttpdConnData *connData);
int tplConsole(HttpdConnData *connData, char *token, void **arg); int tplConsole(HttpdConnData *connData, char *token, void **arg);

@ -16,17 +16,17 @@
#define syslog(X1...) #define syslog(X1...)
#endif #endif
#define SKIP_AT_RESET
static struct espconn serbridgeConn1; // plain bridging port static struct espconn serbridgeConn1; // plain bridging port
static struct espconn serbridgeConn2; // programming port static struct espconn serbridgeConn2; // programming port
static esp_tcp serbridgeTcp1, serbridgeTcp2; static esp_tcp serbridgeTcp1, serbridgeTcp2;
static int8_t mcu_reset_pin, mcu_isp_pin, uart0_tx_enable_pin; static int8_t mcu_reset_pin, mcu_isp_pin, uart0_tx_enable_pin;
extern uint8_t slip_disabled; // disable slip to allow flashing of attached MCU uint8_t in_mcu_flashing; // for disabling slip during MCU flashing
void (*programmingCB)(char *buffer, short length) = NULL; void (*programmingCB)(char *buffer, short length) = NULL;
static sint8 espbuffsend(serbridgeConnData *conn, const char *data, uint16 len);
// Connection pool // Connection pool
serbridgeConnData connData[MAX_CONN]; serbridgeConnData connData[MAX_CONN];
@ -34,10 +34,15 @@ serbridgeConnData connData[MAX_CONN];
// Telnet protocol characters // Telnet protocol characters
#define IAC 255 // escape #define IAC 255 // escape
#define DONT 254 // negotiation
#define DO 253 // negotiation
#define WILL 251 // negotiation #define WILL 251 // negotiation
#define SB 250 // subnegotiation begin #define SB 250 // subnegotiation begin
#define SE 240 // subnegotiation end #define SE 240 // subnegotiation end
#define ComPortOpt 44 // COM port options #define ComPortOpt 44 // COM port options
#define SetBaud 1 // Set baud rate
#define SetDataSize 2 // Set data size
#define SetParity 3 // Set parity
#define SetControl 5 // Set control lines #define SetControl 5 // Set control lines
#define DTR_ON 8 // used here to reset microcontroller #define DTR_ON 8 // used here to reset microcontroller
#define DTR_OFF 9 #define DTR_OFF 9
@ -45,12 +50,17 @@ serbridgeConnData connData[MAX_CONN];
#define RTS_OFF 12 #define RTS_OFF 12
// telnet state machine states // telnet state machine states
enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl }; enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl, TN_setBaud,
TN_setDataSize, TN_setParity };
static char tn_baudCnt;
static uint32_t tn_baud; // shared across all sockets, thus possible race condition
// process a buffer-full on a telnet connection and return the ending telnet state // process a buffer-full on a telnet connection
static uint8_t ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
telnetUnwrap(uint8_t *inBuf, int len, uint8_t state) telnetUnwrap(serbridgeConnData *conn, uint8_t *inBuf, int len)
{ {
uint8_t state = conn->telnet_state;
for (int i=0; i<len; i++) { for (int i=0; i<len; i++) {
uint8_t c = inBuf[i]; uint8_t c = inBuf[i];
switch (state) { switch (state) {
@ -60,6 +70,7 @@ telnetUnwrap(uint8_t *inBuf, int len, uint8_t state)
else uart0_write_char(c); // regular char else uart0_write_char(c); // regular char
break; break;
case TN_iac: case TN_iac:
//os_printf("Telnet: IAC + %d\n", c);
switch (c) { switch (c) {
case IAC: // second escape -> write one to outbuf and go normal again case IAC: // second escape -> write one to outbuf and go normal again
state = TN_normal; state = TN_normal;
@ -79,9 +90,13 @@ telnetUnwrap(uint8_t *inBuf, int len, uint8_t state)
uart0_write_char(c); uart0_write_char(c);
} }
break; break;
case TN_will: case TN_will: { // client announcing it will send telnet cmds, try to respond
state = TN_normal; // yes, we do COM port options, let's go back to normal char respBuf[3] = {IAC, DONT, c};
break; if (c == ComPortOpt) respBuf[1] = DO;
else os_printf("Telnet: rejecting WILL %d\n", c);
espbuffsend(conn, respBuf, 3);
state = TN_normal; // go back to normal
break; }
case TN_start: // in command seq, now comes the type of cmd case TN_start: // in command seq, now comes the type of cmd
if (c == ComPortOpt) state = TN_comPort; if (c == ComPortOpt) state = TN_comPort;
else state = TN_end; // an option we don't know, skip 'til the end seq else state = TN_end; // an option we don't know, skip 'til the end seq
@ -90,21 +105,26 @@ telnetUnwrap(uint8_t *inBuf, int len, uint8_t state)
if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq
break; break;
case TN_comPort: case TN_comPort:
if (c == SetControl) state = TN_setControl; switch (c) {
else state = TN_end; case SetControl: state = TN_setControl; break;
case SetDataSize: state = TN_setDataSize; break;
case SetParity: state = TN_setParity; break;
case SetBaud: state = TN_setBaud; tn_baudCnt = 0; tn_baud = 0; break;
default: state = TN_end; break;
}
break; break;
case TN_setControl: // switch control line and delay a tad case TN_setControl: // switch control line and delay a tad
switch (c) { switch (c) {
case DTR_ON: case DTR_ON:
if (mcu_reset_pin >= 0) { if (mcu_reset_pin >= 0) {
#ifdef SERBR_DBG #ifdef SERBR_DBG
os_printf("MCU reset gpio%d\n", mcu_reset_pin); os_printf("Telnet: reset gpio%d\n", mcu_reset_pin);
#endif #endif
GPIO_OUTPUT_SET(mcu_reset_pin, 0); GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L); os_delay_us(100L);
} }
#ifdef SERBR_DBG #ifdef SERBR_DBG
else { os_printf("MCU reset: no pin\n"); } else { os_printf("Telnet: reset: no pin\n"); }
#endif #endif
break; break;
case DTR_OFF: case DTR_OFF:
@ -116,29 +136,84 @@ telnetUnwrap(uint8_t *inBuf, int len, uint8_t state)
case RTS_ON: case RTS_ON:
if (mcu_isp_pin >= 0) { if (mcu_isp_pin >= 0) {
#ifdef SERBR_DBG #ifdef SERBR_DBG
os_printf("MCU ISP gpio%d\n", mcu_isp_pin); os_printf("Telnet: ISP gpio%d LOW\n", mcu_isp_pin);
#endif #endif
GPIO_OUTPUT_SET(mcu_isp_pin, 0); GPIO_OUTPUT_SET(mcu_isp_pin, 0);
os_delay_us(100L); os_delay_us(100L);
} }
#ifdef SERBR_DBG #ifdef SERBR_DBG
else { os_printf("MCU isp: no pin\n"); } else { os_printf("Telnet: isp: no pin\n"); }
#endif #endif
slip_disabled++; in_mcu_flashing++;
break; break;
case RTS_OFF: case RTS_OFF:
if (mcu_isp_pin >= 0) { if (mcu_isp_pin >= 0) {
#ifdef SERBR_DBG
os_printf("Telnet: ISP gpio%d HIGH\n", mcu_isp_pin);
#endif
GPIO_OUTPUT_SET(mcu_isp_pin, 1); GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(100L); os_delay_us(100L);
} }
if (slip_disabled > 0) slip_disabled--; if (in_mcu_flashing > 0) in_mcu_flashing--;
break;
}
state = TN_end;
break;
case TN_setDataSize:
if (c >= 5 && c <= 8) {
flashConfig.data_bits = c - 5 + FIVE_BITS;
uart0_config(flashConfig.data_bits, flashConfig.parity, flashConfig.stop_bits);
configSave();
os_printf("Telnet: %d bits/char\n", c);
} else if (c == 0) {
// data size of zero means we need to send the current data size
char respBuf[7] = { IAC, SB, ComPortOpt, SetDataSize,
flashConfig.data_bits-FIVE_BITS+5, IAC, SE };
espbuffsend(conn, respBuf, 7);
}
state = TN_end;
break;
case TN_setBaud:
tn_baud |= ((uint32_t)c) << (24-8*tn_baudCnt);
tn_baudCnt++;
if (tn_baudCnt == 4) {
// we got all four baud rate bytes (big endian)
if (tn_baud >= 300 && tn_baud <= 1000000) {
uart0_baud(tn_baud);
flashConfig.baud_rate = tn_baud;
configSave();
os_printf("Telnet: %d baud\n", tn_baud);
} else if (tn_baud == 0) {
// baud rate of zero means we need to send the baud rate
uint32_t b = flashConfig.baud_rate;
char respBuf[10] = { IAC, SB, ComPortOpt, SetDataSize, b>>24, b>>16, b>>8, b, IAC, SE };
espbuffsend(conn, respBuf, 10);
}
state = TN_end;
}
break;
case TN_setParity:
if (c == 0) {
// parity of zero means we need to send the parity info
char respBuf[7] = { IAC, SB, ComPortOpt, SetDataSize, 1/*none*/, IAC, SE };
if (flashConfig.parity == ODD_BITS) respBuf[4] = 2;
if (flashConfig.parity == EVEN_BITS) respBuf[4] = 3;
espbuffsend(conn, respBuf, 7);
state = TN_end;
break; break;
} }
uint8_t parity = NONE_BITS;
if (c == 2) parity = ODD_BITS;
if (c == 3) parity = EVEN_BITS;
flashConfig.parity = parity;
//uart0_config(flashConfig.data_bits, flashConfig.parity, flashConfig.stop_bits);
configSave();
os_printf("Telnet: parity %s\n", c==2?"odd":c==3?"even":"none");
state = TN_end; state = TN_end;
break; break;
} }
} }
return state; conn->telnet_state = state;
} }
// Generate a reset pulse for the attached microcontroller // Generate a reset pulse for the attached microcontroller
@ -168,9 +243,10 @@ serbridgeRecvCb(void *arg, char *data, unsigned short len)
bool startPGM = false; bool startPGM = false;
// at the start of a connection we're in cmInit mode and we wait for the first few characters // At the start of a connection on the primary port we're in cmInit mode and we wait
// to arrive in order to decide what type of connection this is.. The following if statements // for the first few characters to arrive in order to decide what type of connection this is..
// do this dispatch. An issue here is that we assume that the first few characters all arrive // 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 // 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 // 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... // to make it work too). If this becomes a problem we need to buffer the first few chars...
@ -184,8 +260,7 @@ serbridgeRecvCb(void *arg, char *data, unsigned short len)
conn->conn_mode = cmPGM; conn->conn_mode = cmPGM;
// If the connection starts with a telnet negotiation we will do telnet // If the connection starts with a telnet negotiation we will do telnet
} } else if (len >= 2 && data[0] == IAC && (data[1]==WILL||data[1]==DO)) {
else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) {
conn->conn_mode = cmTelnet; conn->conn_mode = cmTelnet;
conn->telnet_state = TN_normal; conn->telnet_state = TN_normal;
// note that the three negotiation chars will be gobbled-up by telnetUnwrap // note that the three negotiation chars will be gobbled-up by telnetUnwrap
@ -193,9 +268,8 @@ serbridgeRecvCb(void *arg, char *data, unsigned short len)
os_printf("telnet mode\n"); os_printf("telnet mode\n");
#endif #endif
// looks like a plain-vanilla connection! // Looks like a plain-vanilla connection!
} } else {
else {
conn->conn_mode = cmTransparent; conn->conn_mode = cmTransparent;
} }
@ -222,17 +296,15 @@ serbridgeRecvCb(void *arg, char *data, unsigned short len)
//if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); //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 os_delay_us(1000L); // wait a millisecond before writing to the UART below
conn->conn_mode = cmPGM; conn->conn_mode = cmPGM;
slip_disabled++; // disable SLIP so it doesn't interfere with flashing in_mcu_flashing++; // disable SLIP so it doesn't interfere with flashing
#ifdef SKIP_AT_RESET
serledFlash(50); // short blink on serial LED serledFlash(50); // short blink on serial LED
return; return;
#endif
} }
// write the buffer to the uart // write the buffer to the uart
if (conn->conn_mode == cmTelnet) { if (conn->conn_mode == cmTelnet) {
conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); telnetUnwrap(conn, (uint8_t *)data, len);
} else { } else {
uart0_tx_buffer(data, len); uart0_tx_buffer(data, len);
} }
@ -355,7 +427,7 @@ serbridgeUartCb(char *buf, short length)
{ {
if (programmingCB) { if (programmingCB) {
programmingCB(buf, length); programmingCB(buf, length);
} else if (!flashConfig.slip_enable || slip_disabled > 0) { } else if (!flashConfig.slip_enable || in_mcu_flashing > 0) {
//os_printf("SLIP: disabled got %d\n", length); //os_printf("SLIP: disabled got %d\n", length);
console_process(buf, length); console_process(buf, length);
} else { } else {
@ -409,7 +481,8 @@ serbridgeConnectCb(void *arg)
#ifdef SERBR_DBG #ifdef SERBR_DBG
os_printf("Accept port %d, conn=%p, pool slot %d\n", conn->proto.tcp->local_port, conn, i); os_printf("Accept port %d, conn=%p, pool slot %d\n", conn->proto.tcp->local_port, conn, i);
#endif #endif
syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "esp-link", "Accept port %d, conn=%p, pool slot %d\n", conn->proto.tcp->local_port, conn, i); syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "esp-link", "Accept port %d, conn=%p, pool slot %d\n",
conn->proto.tcp->local_port, conn, i);
if (i==MAX_CONN) { if (i==MAX_CONN) {
#ifdef SERBR_DBG #ifdef SERBR_DBG
os_printf("Aiee, conn pool overflow!\n"); os_printf("Aiee, conn pool overflow!\n");
@ -518,3 +591,8 @@ serbridgeInit(int port1, int port2)
espconn_tcp_set_max_con_allow(&serbridgeConn2, MAX_CONN); espconn_tcp_set_max_con_allow(&serbridgeConn2, MAX_CONN);
espconn_regist_time(&serbridgeConn2, SER_BRIDGE_TIMEOUT, 0); espconn_regist_time(&serbridgeConn2, SER_BRIDGE_TIMEOUT, 0);
} }
int ICACHE_FLASH_ATTR serbridgeInMCUFlashing()
{
return in_mcu_flashing;
}

@ -36,6 +36,8 @@ void ICACHE_FLASH_ATTR serbridgeInitPins(void);
void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len); void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len);
void ICACHE_FLASH_ATTR serbridgeReset(); void ICACHE_FLASH_ATTR serbridgeReset();
int ICACHE_FLASH_ATTR serbridgeInMCUFlashing();
// callback when receiving UART chars when in programming mode // callback when receiving UART chars when in programming mode
extern void (*programmingCB)(char *buffer, short length); extern void (*programmingCB)(char *buffer, short length);

@ -29,7 +29,6 @@ void ICACHE_FLASH_ATTR serledFlash(int duration) {
} }
void ICACHE_FLASH_ATTR serledInit(void) { void ICACHE_FLASH_ATTR serledInit(void) {
return;
int8_t pin = flashConfig.ser_led_pin; int8_t pin = flashConfig.ser_led_pin;
if (pin >= 0) { if (pin >= 0) {
makeGpio(pin); makeGpio(pin);

@ -13,8 +13,6 @@
#define DBG(format, ...) do { } while(0) #define DBG(format, ...) do { } while(0)
#endif #endif
uint8_t slip_disabled; // temporarily disable slip to allow flashing of attached MCU
extern void ICACHE_FLASH_ATTR console_process(char *buf, short len); extern void ICACHE_FLASH_ATTR console_process(char *buf, short len);
// This SLIP parser tries to conform to RFC 1055 https://tools.ietf.org/html/rfc1055. // This SLIP parser tries to conform to RFC 1055 https://tools.ietf.org/html/rfc1055.

@ -95,8 +95,8 @@ tx_completed_interrupt(void *unused)
* Parameters : uart_no, use UART0 or UART1 defined ahead * Parameters : uart_no, use UART0 or UART1 defined ahead
* Returns : NONE * Returns : NONE
*******************************************************************************/ *******************************************************************************/
static void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
uart_config(uint8 uart_no) uart_config(uint8 uart_no, UartBautRate baudrate, uint32 conf0)
{ {
if (uart_no == UART1) { if (uart_no == UART1) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK); PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
@ -110,14 +110,11 @@ uart_config(uint8 uart_no)
//PIN_PULLUP_DIS (PERIPHS_IO_MUX_U0RXD_U); //PIN_PULLUP_DIS (PERIPHS_IO_MUX_U0RXD_U);
} }
uart_div_modify(uart_no, UART_CLK_FREQ / UartDev.baut_rate); uart_div_modify(uart_no, UART_CLK_FREQ / baudrate);
if (uart_no == UART1) //UART 1 always 8 N 1 if (uart_no == UART1) //UART 1 always 8 N 1
WRITE_PERI_REG(UART_CONF0(uart_no), conf0 = CALC_UARTMODE(EIGHT_BITS, NONE_BITS, ONE_STOP_BIT);
CALC_UARTMODE(EIGHT_BITS, NONE_BITS, ONE_STOP_BIT)); WRITE_PERI_REG(UART_CONF0(uart_no), conf0);
else
WRITE_PERI_REG(UART_CONF0(uart_no),
CALC_UARTMODE(UartDev.data_bits, UartDev.parity, UartDev.stop_bits));
//clear rx and tx fifo,not ready //clear rx and tx fifo,not ready
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST); SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
@ -337,6 +334,12 @@ uart0_baud(int rate) {
uart_div_modify(UART0, UART_CLK_FREQ / rate); uart_div_modify(UART0, UART_CLK_FREQ / rate);
} }
void ICACHE_FLASH_ATTR
uart0_config(uint8_t data_bits, uint8_t parity, uint8_t stop_bits) {
uint32_t conf0 = CALC_UARTMODE(data_bits, parity, stop_bits);
WRITE_PERI_REG(UART_CONF0(0), conf0);
}
/****************************************************************************** /******************************************************************************
* FunctionName : uart_init * FunctionName : uart_init
* Description : user interface for init uart * Description : user interface for init uart
@ -346,7 +349,7 @@ uart0_baud(int rate) {
* Returns : NONE * Returns : NONE
*******************************************************************************/ *******************************************************************************/
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
uart_init(UartBautRate uart0_br, int8_t uart0TxEnablePin, UartBautRate uart1_br) uart_init(uint32 conf0, UartBautRate uart0_br, int8_t uart0TxEnablePin, UartBautRate uart1_br)
{ {
if (uart0TxEnablePin >= 0) { if (uart0TxEnablePin >= 0) {
uart0_set_tx_enable_pin(uart0TxEnablePin); uart0_set_tx_enable_pin(uart0TxEnablePin);
@ -356,11 +359,8 @@ uart_init(UartBautRate uart0_br, int8_t uart0TxEnablePin, UartBautRate uart1_br)
} }
// rom use 74880 baut_rate, here reinitialize // rom use 74880 baut_rate, here reinitialize
uart0_baud_rate = (int)uart0_br; uart_config(UART0, uart0_br, conf0);
UartDev.baut_rate = uart0_br; uart_config(UART1, uart1_br, conf0);
uart_config(UART0);
UartDev.baut_rate = uart1_br;
uart_config(UART1);
for (int i=0; i<4; i++) uart_tx_one_char(UART1, '\n'); for (int i=0; i<4; i++) uart_tx_one_char(UART1, '\n');
for (int i=0; i<4; i++) uart_tx_one_char(UART0, '\n'); for (int i=0; i<4; i++) uart_tx_one_char(UART0, '\n');
ETS_UART_INTR_ENABLE(); ETS_UART_INTR_ENABLE();
@ -381,11 +381,3 @@ uart_add_recv_cb(UartRecv_cb cb) {
} }
os_printf("UART: max cb count exceeded\n"); os_printf("UART: max cb count exceeded\n");
} }
void ICACHE_FLASH_ATTR
uart_reattach()
{
uart_init(BIT_RATE_74880, -1, BIT_RATE_74880);
// ETS_UART_INTR_ATTACH(uart_rx_intr_handler_ssc, &(UartDev.rcv_buff));
// ETS_UART_INTR_ENABLE();
}

@ -8,7 +8,7 @@ typedef void (*UartRecv_cb)(char *buf, short len);
// Initialize UARTs to the provided baud rates (115200 recommended). This also makes the os_printf // Initialize UARTs to the provided baud rates (115200 recommended). This also makes the os_printf
// calls use uart1 for output (for debugging purposes) // calls use uart1 for output (for debugging purposes)
void uart_init(UartBautRate uart0_br, int8_t uart0TxEnablePin, UartBautRate uart1_br); void uart_init(uint32 conf0, UartBautRate uart0_br, int8_t uart0TxEnablePin, UartBautRate uart1_br);
// Transmit a buffer of characters on UART0 // Transmit a buffer of characters on UART0
void uart0_tx_buffer(char *buf, uint16 len); void uart0_tx_buffer(char *buf, uint16 len);
@ -27,6 +27,8 @@ void uart_add_recv_cb(UartRecv_cb cb);
uint16_t uart0_rx_poll(char *buff, uint16_t nchars, uint32_t timeout_us); uint16_t uart0_rx_poll(char *buff, uint16_t nchars, uint32_t timeout_us);
void uart0_baud(int rate); void uart0_baud(int rate);
void uart0_config(uint8_t data_bits, uint8_t parity, uint8_t stop_bits);
void uart_config(uint8 uart_no, UartBautRate baudrate, uint32 conf0);
void uart0_set_tx_enable_pin(int8_t pin); void uart0_set_tx_enable_pin(int8_t pin);

@ -0,0 +1,432 @@
// Copyright 2016 by BeeGee, see LICENSE.txt
//
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh
// Adapted from: rest.c, Author: Thorsten von Eicken
#include "esp8266.h"
#include "c_types.h"
#include "ip_addr.h"
#include "socket.h"
#include "cmd.h"
#define SOCK_DBG
#ifdef SOCK_DBG
#define DBG_SOCK(format, ...) os_printf(format, ## __VA_ARGS__)
#else
#define DBG_SOCK(format, ...) do { } while(0)
#endif
typedef struct {
char *host;
uint32_t port;
ip_addr_t ip;
struct espconn *pCon;
char *data;
uint16_t data_len;
uint16_t data_sent;
uint32_t resp_cb;
uint8_t conn_num;
uint8_t sock_mode;
} SocketClient;
// Connection pool for TCP/UDP socket clients/servers. Attached MCU's just call SOCKET_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 SOCKET connections as there are slots in the pool.
#define MAX_SOCKET 4
static SocketClient socketClient[MAX_SOCKET];
static uint8_t socketNum = 0xff; // index into socketClient for next slot to allocate
// Any incoming data?
static void ICACHE_FLASH_ATTR
socketclient_recv_cb(void *arg, char *pusrdata, unsigned short length) {
struct espconn *pCon = (struct espconn *)arg;
SocketClient* client = (SocketClient *)pCon->reverse;
uint8_t clientNum = client->conn_num;
uint8_t cb_type = USERCB_RECV;
DBG_SOCK("SOCKET #%d: Received %d bytes: %s\n", client-socketClient, length, pusrdata);
cmdResponseStart(CMD_RESP_CB, client->resp_cb, 4);
cmdResponseBody(&cb_type, 1);
cmdResponseBody(&clientNum, 1);
cmdResponseBody(&length, 2);
cmdResponseBody(pusrdata, length);
cmdResponseEnd();
if (client->sock_mode != SOCKET_TCP_SERVER) { // We don't wait for a response
DBG_SOCK("SOCKET #%d: disconnect after receiving\n", client-socketClient);
espconn_disconnect(client->pCon); // disconnect from the server
}
}
// Data is sent
static void ICACHE_FLASH_ATTR
socketclient_sent_cb(void *arg) {
struct espconn *pCon = (struct espconn *)arg;
SocketClient* client = (SocketClient *)pCon->reverse;
uint8_t clientNum = client->conn_num;
uint8_t cb_type = USERCB_SENT;
DBG_SOCK("SOCKET #%d: Sent\n", client-socketClient);
sint16 sentDataLen = client->data_sent;
if (client->data_sent != client->data_len)
{
// we only sent part of the buffer, send the rest
uint16_t data_left = client->data_len - client->data_sent;
if (data_left > 1400) // we have more than 1400 bytes left
{
data_left = 1400;
espconn_sent(client->pCon, (uint8_t*)(client->data+client->data_sent), 1400 );
}
espconn_sent(client->pCon, (uint8_t*)(client->data+client->data_sent), data_left );
client->data_sent += data_left;
}
else
{
// we're done sending, free the memory
if (client->data) os_free(client->data);
client->data = 0;
if (client->sock_mode == SOCKET_TCP_CLIENT) { // We don't wait for a response
DBG_SOCK("SOCKET #%d: disconnect after sending\n", clientNum);
espconn_disconnect(client->pCon);
}
cmdResponseStart(CMD_RESP_CB, client->resp_cb, 3);
cmdResponseBody(&cb_type, 1);
cmdResponseBody(&clientNum, 1);
cmdResponseBody(&sentDataLen, 2);
cmdResponseEnd();
}
}
// Connection is disconnected
static void ICACHE_FLASH_ATTR
socketclient_discon_cb(void *arg) {
struct espconn *pespconn = (struct espconn *)arg;
SocketClient* client = (SocketClient *)pespconn->reverse;
uint8_t clientNum = client->conn_num;
uint8_t cb_type = USERCB_CONN;
sint16 _status = CONNSTAT_DIS;
DBG_SOCK("SOCKET #%d: Disconnect\n", clientNum);
// free the data buffer, if we have one
if (client->data) os_free(client->data);
client->data = 0;
cmdResponseStart(CMD_RESP_CB, client->resp_cb, 3);
cmdResponseBody(&cb_type, 1);
cmdResponseBody(&clientNum, 1);
cmdResponseBody(&_status, 2);
cmdResponseEnd();
}
// Connection was reset
static void ICACHE_FLASH_ATTR
socketclient_recon_cb(void *arg, sint8 errType) {
struct espconn *pCon = (struct espconn *)arg;
SocketClient* client = (SocketClient *)pCon->reverse;
uint8_t clientNum = client->conn_num;
uint8_t cb_type = USERCB_RECO;
sint16 _errType = errType;
os_printf("SOCKET #%d: conn reset, err=%d\n", clientNum, _errType);
cmdResponseStart(CMD_RESP_CB, client->resp_cb, 3);
cmdResponseBody(&cb_type, 1);
cmdResponseBody(&clientNum, 1);
cmdResponseBody(&_errType, 2);
cmdResponseEnd();
// free the data buffer, if we have one
if (client->data) os_free(client->data);
client->data = 0;
}
// Connection is done
static void ICACHE_FLASH_ATTR
socketclient_connect_cb(void *arg) {
struct espconn *pCon = (struct espconn *)arg;
SocketClient* client = (SocketClient *)pCon->reverse;
uint8_t clientNum = client->conn_num;
uint8_t cb_type = USERCB_CONN;
sint16 _status = CONNSTAT_CON;
DBG_SOCK("SOCKET #%d: connected socket mode = %d\n", clientNum, client->sock_mode);
espconn_regist_disconcb(client->pCon, socketclient_discon_cb);
espconn_regist_recvcb(client->pCon, socketclient_recv_cb);
espconn_regist_sentcb(client->pCon, socketclient_sent_cb);
DBG_SOCK("SOCKET #%d: sending %d\n", clientNum, client->data_sent);
if (client->sock_mode != SOCKET_TCP_SERVER) { // Send data after established connection only in client mode
client->data_sent = client->data_len <= 1400 ? client->data_len : 1400;
DBG_SOCK("SOCKET #%d: sending %d\n", clientNum, client->data_sent);
espconn_send(client->pCon, (uint8_t*)client->data, client->data_sent);
}
cmdResponseStart(CMD_RESP_CB, client->resp_cb, 3);
cmdResponseBody(&cb_type, 1);
cmdResponseBody(&clientNum, 1);
cmdResponseBody(&_status, 2);
cmdResponseEnd();
}
static void ICACHE_FLASH_ATTR
socket_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) {
struct espconn *pConn = (struct espconn *)arg;
SocketClient* client = (SocketClient *)pConn->reverse;
uint8_t clientNum = client->conn_num;
if(ipaddr == NULL) {
sint16 _errType = ESPCONN_RTE; //-4;
uint8_t cb_type = USERCB_RECO; // use Routing problem or define a new one
os_printf("SOCKET #%d DNS: Got no ip, report error\n", clientNum);
cmdResponseStart(CMD_RESP_CB, client->resp_cb, 3);
cmdResponseBody(&cb_type, 2); // Same as connection reset?? or define a new one
cmdResponseBody(&clientNum, 1);
cmdResponseBody(&_errType, 2);
cmdResponseEnd();
return;
}
DBG_SOCK("SOCKET #%d DNS: found ip %d.%d.%d.%d\n",
clientNum,
*((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);
espconn_connect(client->pCon);
DBG_SOCK("SOCKET #%d: connecting...\n", clientNum);
}
}
void ICACHE_FLASH_ATTR
SOCKET_Setup(CmdPacket *cmd) {
CmdRequest req;
uint16_t port;
uint8_t sock_mode;
int32_t err = -1; // error code in case of failure
// start parsing the command
cmdRequest(&req, cmd);
if(cmdGetArgc(&req) != 3) {
DBG_SOCK("SOCKET Setup parse command failure: (cmdGetArgc(&req) != 3)\n");
goto fail;
}
err--;
// get the hostname (IP address)
uint16_t len = cmdArgLen(&req);
if (len > 128) {
DBG_SOCK("SOCKET Setup parse command failure: hostname longer than 128 characters\n");
goto fail; // safety check
}
err--;
uint8_t *socket_host = (uint8_t*)os_zalloc(len + 1);
if (socket_host == NULL) {
DBG_SOCK("SOCKET Setup failed to alloc memory for socket_host\n");
goto fail;
}
if (cmdPopArg(&req, socket_host, len)) {
DBG_SOCK("SOCKET Setup parse command failure: (cmdPopArg(&req, socket_host, len))\n");
goto fail;
}
err--;
socket_host[len] = 0;
// get the port
if (cmdPopArg(&req, (uint8_t*)&port, 2)) {
DBG_SOCK("SOCKET Setup parse command failure: cannot get port\n");
os_free(socket_host);
goto fail;
}
err--;
// get the socket mode
if (cmdPopArg(&req, (uint8_t*)&sock_mode, 1)) {
DBG_SOCK("SOCKET Setup parse command failure: cannot get mode\n");
os_free(socket_host);
goto fail;
}
err--;
DBG_SOCK("SOCKET Setup listener flag\n");
// clear connection structures the first time
if (socketNum == 0xff) {
os_memset(socketClient, 0, MAX_SOCKET * sizeof(SocketClient));
socketNum = 0;
}
// allocate a connection structure
SocketClient *client = socketClient + socketNum;
uint8_t clientNum = socketNum;
socketNum = (socketNum+1)%MAX_SOCKET;
// free any data structure that may be left from a previous connection
if (client->data) os_free(client->data);
if (client->pCon) {
if (sock_mode != SOCKET_UDP) {
if (client->pCon->proto.tcp) os_free(client->pCon->proto.tcp);
} else {
if (client->pCon->proto.udp) os_free(client->pCon->proto.udp);
}
os_free(client->pCon);
}
os_memset(client, 0, sizeof(SocketClient));
DBG_SOCK("SOCKET #%d: Setup host=%s port=%d \n", clientNum, socket_host, port);
client->sock_mode = sock_mode;
client->resp_cb = cmd->value;
client->conn_num = clientNum;
client->host = (char *)socket_host;
client->port = port;
if (sock_mode == SOCKET_UDP) {
wifi_set_broadcast_if(STATIONAP_MODE);
}
client->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn));
if (client->pCon == NULL) {
DBG_SOCK("SOCKET #%d: Setup failed to alloc memory for client_pCon\n", clientNum);
goto fail;
}
if (sock_mode != SOCKET_UDP) {
client->pCon->type = ESPCONN_TCP;
client->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp));
if (client->pCon->proto.tcp == NULL) {
DBG_SOCK("SOCKET #%d: Setup failed to alloc memory for client->pCon->proto.tcp\n", clientNum);
goto fail;
}
} else {
client->pCon->type = ESPCONN_UDP;
client->pCon->proto.udp = (esp_udp *)os_zalloc(sizeof(esp_udp));
if (client->pCon->proto.udp == NULL) {
DBG_SOCK("SOCKET #%d: Setup failed to alloc memory for client->pCon->proto.udp\n", clientNum);
goto fail;
}
}
client->pCon->state = ESPCONN_NONE;
os_memcpy(client->host, socket_host, 4);
if (sock_mode != SOCKET_UDP) {
client->pCon->proto.tcp->remote_port = client->port;
client->pCon->proto.tcp->local_port = client->port; // espconn_port();
} else {
client->pCon->proto.udp->remote_port = client->port;
client->pCon->proto.udp->local_port = client->port;
}
client->pCon->reverse = client;
espconn_regist_sentcb(client->pCon, socketclient_sent_cb);
espconn_regist_recvcb(client->pCon, socketclient_recv_cb);
if (sock_mode == SOCKET_UDP) {
DBG_SOCK("SOCKET #%d: Create connection to ip %s:%d\n", clientNum, client->host, client->port);
if(UTILS_StrToIP((char *)client->host, &client->pCon->proto.udp->remote_ip)) {
espconn_create(client->pCon);
} else {
DBG_SOCK("SOCKET #%d: failed to copy remote_ip to &client->pCon->proto.udp->remote_ip\n", clientNum);
goto fail;
}
} else {
espconn_regist_reconcb(client->pCon, socketclient_recon_cb);
if (client->sock_mode == SOCKET_TCP_SERVER) { // Server mode?
DBG_SOCK("SOCKET #%d: Enable server mode on port%d\n", clientNum, client->port);
espconn_accept(client->pCon);
espconn_regist_connectcb(client->pCon, socketclient_connect_cb);
}
}
cmdResponseStart(CMD_RESP_V, clientNum, 0);
cmdResponseEnd();
DBG_SOCK("SOCKET #%d: setup finished\n", clientNum);
return;
fail:
cmdResponseStart(CMD_RESP_V, err, 0);
cmdResponseEnd();
return;
}
void ICACHE_FLASH_ATTR
SOCKET_Send(CmdPacket *cmd) {
CmdRequest req;
cmdRequest(&req, cmd);
// Get client
uint32_t clientNum = cmd->value;
SocketClient *client = socketClient + (clientNum % MAX_SOCKET);
DBG_SOCK("SOCKET #%d: send", clientNum);
if (cmd->argc != 1 && cmd->argc != 2) {
DBG_SOCK("\nSOCKET #%d: send - wrong number of arguments\n", clientNum);
return;
}
// Get data to sent
client->data_len = cmdArgLen(&req);
DBG_SOCK(" dataLen=%d", client->data_len);
if (client->data) os_free(client->data);
client->data = (char*)os_zalloc(client->data_len);
if (client->data == NULL) {
DBG_SOCK("\nSOCKET #%d failed to alloc memory for client->data\n", clientNum);
goto fail;
}
cmdPopArg(&req, client->data, client->data_len);
DBG_SOCK(" socketData=%s", client->data);
// client->data_len = os_sprintf((char*)client->data, socketDataSet, socketData);
DBG_SOCK("\n");
DBG_SOCK("SOCKET #%d: Create connection to ip %s:%d\n", clientNum, client->host, client->port);
if (client->sock_mode == SOCKET_TCP_SERVER) { // In TCP server mode we should be connected already and send the data immediately
remot_info *premot = NULL;
if (espconn_get_connection_info(client->pCon,&premot,0) == ESPCONN_OK){
for (uint8 count = 0; count < client->pCon->link_cnt; count ++){
client->pCon->proto.tcp->remote_port = premot[count].remote_port;
client->pCon->proto.tcp->remote_ip[0] = premot[count].remote_ip[0];
client->pCon->proto.tcp->remote_ip[1] = premot[count].remote_ip[1];
client->pCon->proto.tcp->remote_ip[2] = premot[count].remote_ip[2];
client->pCon->proto.tcp->remote_ip[3] = premot[count].remote_ip[3];
DBG_SOCK("SOCKET #%d: connected to %d.%d.%d.%d:%d\n",
clientNum,
client->pCon->proto.tcp->remote_ip[0],
client->pCon->proto.tcp->remote_ip[1],
client->pCon->proto.tcp->remote_ip[2],
client->pCon->proto.tcp->remote_ip[3],
client->pCon->proto.tcp->remote_port
);
}
client->data_sent = client->data_len <= 1400 ? client->data_len : 1400;
DBG_SOCK("SOCKET #%d: Server sending %d\n", clientNum, client->data_sent);
espconn_send(client->pCon, (uint8_t*)client->data, client->data_sent);
}
} else if (client->sock_mode != SOCKET_UDP) { // In TCP client mode we connect and send the data from the connected callback
espconn_regist_connectcb(client->pCon, socketclient_connect_cb);
if(UTILS_StrToIP((char *)client->host, &client->pCon->proto.tcp->remote_ip)) {
DBG_SOCK("SOCKET #%d: Connect to ip %s:%d\n", clientNum, client->host, client->port);
espconn_connect(client->pCon);
} else {
DBG_SOCK("SOCKET #%d: Connect to host %s:%d\n", clientNum, client->host, client->port);
espconn_gethostbyname(client->pCon, (char *)client->host, &client->ip, socket_dns_found);
}
} else { // in UDP socket mode we send the data immediately
client->data_sent = client->data_len <= 1400 ? client->data_len : 1400;
DBG_SOCK("SOCKET #%d: sending %d bytes: %s\n", clientNum, client->data_sent, client->data);
espconn_sent(client->pCon, (uint8_t*)client->data, client->data_sent);
}
return;
fail:
DBG_SOCK("\n");
}

@ -0,0 +1,38 @@
/*
* socket.h
*
* Created on: Sep 16th 2016
* Author: BeeGee
*/
#ifndef MODULES_SOCKET_H_
#define MODULES_SOCKET_H_
#include "cmd.h"
void SOCKET_Setup(CmdPacket *cmd);
void SOCKET_Send(CmdPacket *cmd);
// Socket mode
typedef enum {
SOCKET_TCP_CLIENT = 0, /**< TCP socket client for sending only, doesn't wait for response from server */
SOCKET_TCP_CLIENT_LISTEN, /**< TCP socket client, waits for response from server after sending */
SOCKET_TCP_SERVER, /**< TCP socket server */
SOCKET_UDP, /**< UDP socket for sending and receiving UDP packets */
} socketMode;
// Callback type
typedef enum {
USERCB_SENT = 0, /**< Data send finished */
USERCB_RECV, /**< Data received */
USERCB_RECO, /**< Connection error */
USERCB_CONN, /**< Connection event */
} cbType;
// Connection status
typedef enum {
CONNSTAT_DIS = 0, // Disconnected
CONNSTAT_CON, // Connected
} connStat;
#endif /* MODULES_SOCKET_H_ */

@ -13,6 +13,7 @@
#include "syslog.h" #include "syslog.h"
#include "time.h" #include "time.h"
#include "task.h" #include "task.h"
#include "sntp.h"
extern void * mem_trim(void *m, size_t s); // not well documented... extern void * mem_trim(void *m, size_t s); // not well documented...
@ -46,7 +47,6 @@ static void ICACHE_FLASH_ATTR syslog_udp_recv_cb(void *arg, char *pusrdata, unsi
#define syslog_send_udp() post_usr_task(syslog_task,0) #define syslog_send_udp() post_usr_task(syslog_task,0)
#ifdef SYSLOG_DBG
static char ICACHE_FLASH_ATTR *syslog_get_status(void) { static char ICACHE_FLASH_ATTR *syslog_get_status(void) {
switch (syslogState) switch (syslogState)
{ {
@ -77,11 +77,13 @@ static char ICACHE_FLASH_ATTR *syslog_get_status(void) {
} }
return "UNKNOWN "; return "UNKNOWN ";
} }
#endif
static void ICACHE_FLASH_ATTR syslog_set_status(enum syslog_state state) { static void ICACHE_FLASH_ATTR syslog_set_status(enum syslog_state state) {
syslogState = state; syslogState = state;
DBG("[%dµs] %s: %s (%d)\n", WDEV_NOW(), __FUNCTION__, syslog_get_status(), state); DBG("[%dµs] %s: %s (%d)\n", WDEV_NOW(), __FUNCTION__, syslog_get_status(), state);
#ifndef SYSLOG_DBG
os_printf("Syslog state: %s\n", syslog_get_status());
#endif
} }
static void ICACHE_FLASH_ATTR syslog_timer_arm(int delay) { static void ICACHE_FLASH_ATTR syslog_timer_arm(int delay) {
@ -261,6 +263,7 @@ void ICACHE_FLASH_ATTR syslog_init(char *syslog_host)
{ {
DBG("[%uµs] %s\n", WDEV_NOW(), __FUNCTION__); DBG("[%uµs] %s\n", WDEV_NOW(), __FUNCTION__);
os_printf("SYSLOG host=%s *host=0x%x\n", syslog_host, *syslog_host);
if (!*syslog_host) { if (!*syslog_host) {
syslog_set_status(SYSLOG_HALTED); syslog_set_status(SYSLOG_HALTED);
return; return;
@ -399,6 +402,7 @@ syslog_compose(uint8_t facility, uint8_t severity, const char *tag, const char *
// create timestamp: FULL-DATE "T" PARTIAL-TIME "Z": 'YYYY-mm-ddTHH:MM:SSZ ' // create timestamp: FULL-DATE "T" PARTIAL-TIME "Z": 'YYYY-mm-ddTHH:MM:SSZ '
// as long as realtime_stamp is 0 we use tick div 10⁶ as date // as long as realtime_stamp is 0 we use tick div 10⁶ as date
uint32_t realtime_stamp = sntp_get_current_timestamp();
now = (realtime_stamp == 0) ? (se->tick / 1000000) : realtime_stamp; now = (realtime_stamp == 0) ? (se->tick / 1000000) : realtime_stamp;
tp = gmtime(&now); tp = gmtime(&now);
@ -504,8 +508,7 @@ void ICACHE_FLASH_ATTR syslog(uint8_t facility, uint8_t severity, const char *ta
{ {
DBG("[%dµs] %s status: %s\n", WDEV_NOW(), __FUNCTION__, syslog_get_status()); DBG("[%dµs] %s status: %s\n", WDEV_NOW(), __FUNCTION__, syslog_get_status());
if (syslogState == SYSLOG_ERROR || if (flashConfig.syslog_host[0] == 0 || syslogState == SYSLOG_ERROR || syslogState == SYSLOG_HALTED)
syslogState == SYSLOG_HALTED)
return; return;
if (severity > flashConfig.syslog_filter) if (severity > flashConfig.syslog_filter)

@ -70,7 +70,8 @@ enum syslog_facility {
#define REG_READ(_r) (*(volatile uint32 *)(_r)) #define REG_READ(_r) (*(volatile uint32 *)(_r))
#define WDEV_NOW() REG_READ(0x3ff20c00) #define WDEV_NOW() REG_READ(0x3ff20c00)
extern uint32_t realtime_stamp; // 1sec NTP ticker // This variable disappeared from lwip in SDK 2.0...
// extern uint32_t realtime_stamp; // 1sec NTP ticker
typedef struct syslog_host_t syslog_host_t; typedef struct syslog_host_t syslog_host_t;
struct syslog_host_t { struct syslog_host_t {

Binary file not shown.

Binary file not shown.

@ -0,0 +1,489 @@
#include "web-server.h"
#include <espconn.h>
#include "espfs.h"
#include "config.h"
#include "cgi.h"
#include "cmd.h"
#include "serbridge.h"
// the file is responsible for handling user defined web-pages
// - collects HTML files from user image, shows them on the left frame
// - handles JSON data coming from the browser
// - handles SLIP messages coming from MCU
#define MAX_ARGUMENT_BUFFER_SIZE 128
#define HEADER_SIZE 32
uint32_t web_server_cb = 0;
struct ArgumentBuffer
{
char argBuffer[MAX_ARGUMENT_BUFFER_SIZE];
int argBufferPtr;
int numberOfArgs;
};
static char* web_server_reasons[] = {
"load", // readable name for RequestReason::LOAD
"refresh", // readable name for RequestReason::REFRESH
"button", // readable name for RequestReason::BUTTON
"submit" // readable name for RequestReason::SUBMIT
};
// this variable contains the names of the user defined pages
// this information appears at the left frame below of the built in URL-s
// format: ,"UserPage1", "/UserPage1.html", "UserPage2", "/UserPage2.html",
char * webServerPages = NULL;
char * ICACHE_FLASH_ATTR WEB_UserPages()
{
return webServerPages;
}
// generates the content of webServerPages variable (called at booting/web page uploading)
void ICACHE_FLASH_ATTR WEB_BrowseFiles()
{
char buffer[1024];
buffer[0] = 0;
if( espFsIsValid( userPageCtx ) )
{
EspFsIterator it;
espFsIteratorInit(userPageCtx, &it);
while( espFsIteratorNext(&it) )
{
int nameLen = os_strlen(it.name);
if( nameLen >= 6 )
{
// fetch HTML files
if( os_strcmp( it.name + nameLen-5, ".html" ) == 0 )
{
int slashPos = nameLen - 5;
// chop path and .html from the name
while( slashPos > 0 && it.name[slashPos-1] != '/' )
slashPos--;
// here we check buffer overrun
int maxLen = 10 + os_strlen( it.name ) + (nameLen - slashPos -5);
if( maxLen >= sizeof(buffer) )
break;
os_strcat(buffer, ", \"");
int writePos = os_strlen(buffer);
for( int i=slashPos; i < nameLen-5; i++ )
buffer[writePos++] = it.name[i];
buffer[writePos] = 0; // terminating zero
os_strcat(buffer, "\", \"/");
os_strcat(buffer, it.name);
os_strcat(buffer, "\"");
}
}
}
}
if( webServerPages != NULL )
os_free( webServerPages );
int len = os_strlen(buffer) + 1;
webServerPages = (char *)os_malloc( len );
os_memcpy( webServerPages, buffer, len );
}
// initializer
void ICACHE_FLASH_ATTR WEB_Init()
{
espFsInit(userPageCtx, (void *)getUserPageSectionStart(), ESPFS_FLASH);
if( espFsIsValid( userPageCtx ) )
os_printf("Valid user file system found!\n");
else
os_printf("No user file system found!\n");
WEB_BrowseFiles(); // collect user defined HTML files
}
// initializes the argument buffer
static void ICACHE_FLASH_ATTR WEB_argInit(struct ArgumentBuffer * argBuffer)
{
argBuffer->numberOfArgs = 0;
argBuffer->argBufferPtr = 0;
}
// adds an argument to the argument buffer (returns 0 if successful)
static int ICACHE_FLASH_ATTR WEB_addArg(struct ArgumentBuffer * argBuffer, char * arg, int argLen )
{
if( argBuffer->argBufferPtr + argLen + sizeof(int) >= MAX_ARGUMENT_BUFFER_SIZE )
return -1; // buffer overflow
os_memcpy(argBuffer->argBuffer + argBuffer->argBufferPtr, &argLen, sizeof(int));
if( argLen != 0 )
{
os_memcpy( argBuffer->argBuffer + argBuffer->argBufferPtr + sizeof(int), arg, argLen );
argBuffer->numberOfArgs++;
}
argBuffer->argBufferPtr += argLen + sizeof(int);
return 0;
}
// creates and sends a SLIP message from the argument buffer
static void ICACHE_FLASH_ATTR WEB_sendArgBuffer(struct ArgumentBuffer * argBuffer, HttpdConnData *connData, RequestReason reason)
{
cmdResponseStart(CMD_RESP_CB, web_server_cb, 4 + argBuffer->numberOfArgs);
uint16_t r = (uint16_t)reason;
cmdResponseBody(&r, sizeof(uint16_t)); // 1st argument: reason
cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); // 2nd argument: IP
cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); // 3rd argument: port
cmdResponseBody(connData->url, os_strlen(connData->url)); // 4th argument: URL
int p = 0;
for( int j=0; j < argBuffer->numberOfArgs; j++ )
{
int argLen;
os_memcpy( &argLen, argBuffer->argBuffer + p, sizeof(int) );
char * arg = argBuffer->argBuffer + p + sizeof(int);
cmdResponseBody(arg, argLen);
p += argLen + sizeof(int);
}
cmdResponseEnd();
}
// this method processes SLIP data from MCU and converts to JSON
// this method receives JSON from the browser, sends SLIP data to MCU
static int ICACHE_FLASH_ATTR WEB_handleJSONRequest(HttpdConnData *connData)
{
if( !flashConfig.slip_enable )
{
errorResponse(connData, 400, "Slip processing is disabled!");
return HTTPD_CGI_DONE;
}
if( web_server_cb == 0 )
{
errorResponse(connData, 500, "No MCU callback is registered!");
return HTTPD_CGI_DONE;
}
if( serbridgeInMCUFlashing() )
{
errorResponse(connData, 500, "Slip disabled at uploading program onto the MCU!");
return HTTPD_CGI_DONE;
}
char reasonBuf[16];
int i;
int len = httpdFindArg(connData->getArgs, "reason", reasonBuf, sizeof(reasonBuf));
if( len < 0 )
{
errorResponse(connData, 400, "No reason specified!");
return HTTPD_CGI_DONE;
}
RequestReason reason = INVALID;
for(i=0; i < sizeof(web_server_reasons)/sizeof(char *); i++)
{
if( os_strcmp( web_server_reasons[i], reasonBuf ) == 0 )
reason = (RequestReason)i;
}
if( reason == INVALID )
{
errorResponse(connData, 400, "Invalid reason!");
return HTTPD_CGI_DONE;
}
struct ArgumentBuffer argBuffer;
WEB_argInit( &argBuffer );
switch(reason)
{
case BUTTON:
{
char id_buf[40];
int id_len = httpdFindArg(connData->getArgs, "id", id_buf, sizeof(id_buf));
if( id_len <= 0 )
{
errorResponse(connData, 400, "No button ID specified!");
return HTTPD_CGI_DONE;
}
if( WEB_addArg(&argBuffer, id_buf, id_len) )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
}
break;
case SUBMIT:
{
if( connData->post->received < connData->post->len )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
int bptr = 0;
int sent_args = 0;
int max_buf_size = MAX_ARGUMENT_BUFFER_SIZE - HEADER_SIZE - os_strlen(connData->url);
while( bptr < connData->post->len )
{
char * line = connData->post->buff + bptr;
char * eo = os_strchr(line, '&' );
if( eo != NULL )
{
*eo = 0;
bptr = eo - connData->post->buff + 1;
}
else
{
eo = line + os_strlen( line );
bptr = connData->post->len;
}
int len = os_strlen(line);
while( len >= 1 && ( line[len-1] == '\r' || line[len-1] == '\n' ))
len--;
line[len] = 0;
char * val = os_strchr(line, '=');
if( val != NULL )
{
*val = 0;
char * name = line;
int vblen = os_strlen(val+1) * 2;
char value[vblen];
httpdUrlDecode(val+1, strlen(val+1), value, vblen);
int namLen = os_strlen(name);
int valLen = os_strlen(value);
char arg[namLen + valLen + 3];
int argPtr = 0;
arg[argPtr++] = (char)WEB_STRING;
os_strcpy( arg + argPtr, name );
argPtr += namLen;
arg[argPtr++] = 0;
os_strcpy( arg + argPtr, value );
argPtr += valLen;
if( sent_args != 0 )
{
if( argBuffer.argBufferPtr + argPtr >= max_buf_size )
{
WEB_addArg(&argBuffer, NULL, 0); // there's enough room in the buffer for termination block
WEB_sendArgBuffer(&argBuffer, connData, reason );
WEB_argInit( &argBuffer );
sent_args = 0;
}
}
if( WEB_addArg(&argBuffer, arg, argPtr) )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
sent_args++;
}
}
}
break;
case LOAD:
case REFRESH:
default:
break;
}
if( WEB_addArg(&argBuffer, NULL, 0) )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
os_printf("Web callback to MCU: %s\n", reasonBuf);
WEB_sendArgBuffer(&argBuffer, connData, reason );
if( reason == SUBMIT )
{
httpdStartResponse(connData, 204);
httpdEndHeaders(connData);
return HTTPD_CGI_DONE;
}
return HTTPD_CGI_MORE;
}
// this method receives SLIP data from MCU sends JSON to the browser
static int ICACHE_FLASH_ATTR WEB_handleMCUResponse(HttpdConnData *connData, CmdRequest * response)
{
char jsonBuf[1500];
int jsonPtr = 0;
jsonBuf[jsonPtr++] = '{';
int c = 2;
while( c++ < cmdGetArgc(response) )
{
int len = cmdArgLen(response);
char buf[len+1];
buf[len] = 0;
cmdPopArg(response, buf, len);
if(len == 0)
break; // last argument
if( c > 3 ) // skip the first argument
jsonBuf[jsonPtr++] = ',';
if( jsonPtr + 20 + len > sizeof(jsonBuf) )
{
errorResponse(connData, 500, "Response too large!");
return HTTPD_CGI_DONE;
}
WebValueType type = (WebValueType)buf[0];
int nameLen = os_strlen(buf+1);
jsonBuf[jsonPtr++] = '"';
os_memcpy(jsonBuf + jsonPtr, buf + 1, nameLen);
jsonPtr += nameLen;
jsonBuf[jsonPtr++] = '"';
jsonBuf[jsonPtr++] = ':';
char * value = buf + 2 + nameLen;
switch(type)
{
case WEB_NULL:
os_memcpy(jsonBuf + jsonPtr, "null", 4);
jsonPtr += 4;
break;
case WEB_INTEGER:
{
int v;
os_memcpy( &v, value, 4);
char intbuf[20];
os_sprintf(intbuf, "%d", v);
os_strcpy(jsonBuf + jsonPtr, intbuf);
jsonPtr += os_strlen(intbuf);
}
break;
case WEB_BOOLEAN:
if( *value ) {
os_memcpy(jsonBuf + jsonPtr, "true", 4);
jsonPtr += 4;
} else {
os_memcpy(jsonBuf + jsonPtr, "false", 5);
jsonPtr += 5;
}
break;
case WEB_FLOAT:
{
float f;
os_memcpy( &f, value, 4);
// os_sprintf doesn't support %f
int intPart = f;
int fracPart = (f - intPart) * 1000; // use 3 digit precision
if( fracPart < 0 ) // for negative numbers
fracPart = -fracPart;
char floatBuf[20];
os_sprintf(floatBuf, "%d.%03d", intPart, fracPart);
os_strcpy(jsonBuf + jsonPtr, floatBuf);
jsonPtr += os_strlen(floatBuf);
}
break;
case WEB_STRING:
jsonBuf[jsonPtr++] = '"';
while(*value)
{
if( *value == '\\' || *value == '"' )
jsonBuf[jsonPtr++] = '\\';
jsonBuf[jsonPtr++] = *(value++);
}
jsonBuf[jsonPtr++] = '"';
break;
case WEB_JSON:
os_memcpy(jsonBuf + jsonPtr, value, len - 2 - nameLen);
jsonPtr += len - 2 - nameLen;
break;
}
}
jsonBuf[jsonPtr++] = '}';
noCacheHeaders(connData, 200);
httpdHeader(connData, "Content-Type", "application/json");
char cl[16];
os_sprintf(cl, "%d", jsonPtr);
httpdHeader(connData, "Content-Length", cl);
httpdEndHeaders(connData);
httpdSend(connData, jsonBuf, jsonPtr);
return HTTPD_CGI_DONE;
}
// this method is responsible for the MCU <==JSON==> Browser communication
int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData)
{
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
void * cgiData = connData->cgiData;
if( cgiData == NULL )
{
connData->cgiData = (void *)1; // indicate, that request was processed
return WEB_handleJSONRequest(connData);
}
if( connData->cgiResponse != NULL ) // data from MCU
return WEB_handleMCUResponse(connData, (CmdRequest *)(connData->cgiResponse));
return HTTPD_CGI_MORE;
}
// configuring the callback
void ICACHE_FLASH_ATTR WEB_Setup(CmdPacket *cmd)
{
CmdRequest req;
cmdRequest(&req, cmd);
if (cmdGetArgc(&req) < 1) return;
cmdPopArg(&req, &web_server_cb, 4); // pop the callback
os_printf("Web-server connected, cb=0x%x\n", web_server_cb);
}
// this method is called when MCU transmits WEB_DATA command
void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd)
{
CmdRequest req;
cmdRequest(&req, cmd);
if (cmdGetArgc(&req) < 2) return;
uint8_t ip[4];
cmdPopArg(&req, ip, 4); // pop the IP address
uint16_t port;
cmdPopArg(&req, &port, 2); // pop the HTTP port
HttpdConnData * conn = httpdLookUpConn(ip, port); // look up connection based on IP/port
if( conn != NULL && conn->cgi == WEB_CgiJsonHook ) // make sure that the right CGI handler is configured
httpdSetCGIResponse( conn, &req );
else
os_printf("WEB_DATA ignored as no valid HTTP connection found!\n");
}

@ -0,0 +1,38 @@
#ifndef WEB_SERVER_H
#define WEB_SERVER_H
#include <esp8266.h>
#include "httpd.h"
#include "cmd.h"
typedef enum
{
LOAD=0, // loading web-page content at the first time
REFRESH, // loading web-page subsequently
BUTTON, // HTML button pressed
SUBMIT, // HTML form is submitted
INVALID=-1,
} RequestReason;
typedef enum
{
WEB_STRING=0, // the value is string
WEB_NULL, // the value is NULL
WEB_INTEGER, // the value is integer
WEB_BOOLEAN, // the value is boolean
WEB_FLOAT, // the value is float
WEB_JSON // the value is JSON data
} WebValueType;
void WEB_Init();
char * WEB_UserPages();
int WEB_CgiJsonHook(HttpdConnData *connData);
void WEB_Setup(CmdPacket *cmd);
void WEB_Data(CmdPacket *cmd);
#endif /* WEB_SERVER_H */
Loading…
Cancel
Save