mirror of https://github.com/jeelabs/esp-link.git
commit
acc7c90575
@ -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... |
||||||
|
``` |
@ -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 control | Value Type | Description | Form Submission | |
||||||
|
------------------------ | ----- | ------------ | ------- | |
||||||
|
<p id="id"/> <br/> <div id="id"/> <br/> <tr id="id"/> <br/> <th id="id"/> <br/> <td id="id"/> <br/> <textarea id="id"/> | String (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 <img...> will be displayed as an image on the page | NO | |
||||||
|
<button id="id"/> | String | When button is pressed, a message is transmitted to MCU containing the id (BUTTON_PRESS) | NO | |
||||||
|
<input name="id"/> | 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 | |
||||||
|
<select name="id"/> | 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 | |
||||||
|
<ul id="id"/> <br/> <ol id="id"/> | JSON list <br/> ["1","2","3"] | MCU can send a JSON list which is transformed to an HTML list ( <li/> ) (LOAD/REFRESH) | NO | |
||||||
|
<table id="id"/> | 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. |
@ -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 */ |
@ -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 |
@ -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; } |
||||||
|
} |
||||||
|
); |
||||||
|
} |
@ -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> |
@ -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 */ |
@ -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_ */ |
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…
Reference in new issue