diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e90d818 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +build/ +firmware/ +espfs/mkespfsimage/*.o +espfs/mkespfsimage/mkespfsimage +webpages.espfs +espfs/espfstest/*.o +espfs/espfstest/espfstest +*.DS_Store +html_compressed/ +esp-link.tgz +tve-patch/ +yui +espfs/mkespfsimage/mman-win32/mman.o +esp-link.opensdf +esp-link.sdf +espfs/mkespfsimage/mman-win32/libmman.a +.localhistory/ +tools/ +local.conf +*.tgz diff --git a/BOARDS.md b/BOARDS.md new file mode 100644 index 0000000..cdb25f5 --- /dev/null +++ b/BOARDS.md @@ -0,0 +1,213 @@ +Boards with esp-link +==================== + +This readme provides instructions for PCBs that I've made that are designed for esp-link. +Some of the instructions may be helpful to others as well. + +esp-bridge +---------- +![dsc_5127](https://cloud.githubusercontent.com/assets/39480/8509323/11037aa2-2252-11e5-9bd2-6c86c9a3b2ed.jpg) + +The esp-bridge has an esp-03 modulde, an FTDI connector (with wrong pinout!), a 3-pin power +and debug connector, and two buttons. +Next to the buttons it is marked "TvE2015 esp-ftdi". +It comes preloaded with the latest version of esp-link. + +Power: the on-board MCP1825S-33 regulator can provide 500mA and is good from about 3.6v to 6v. +Connect power either to the 3-pin connector (GND in center, 5v towards the esp module), or to +the FTDI connector (GND marked next to the buttons, 5V on 3rd pin). + +On power-up you should see the green LED on for ~1 second (the yellow should go on too, but +the firmware may not be configured correctly). After that the green should blink according to the +patterns described in the README's LED indicators section. Follow the Wifi configuration details +section thereafter. + +To connect a JeeNode to the esp-bridge to flash the AVR or debug it, plug it into the FTDI +port flipped-over, i.e. the component side of the JeeNode will be on the bottom and the +components of the esp-bridge will be on the top. (Yes, the FTDI port should have been reversed +on the esp-bridge...) + +To program the JeeNode, having set-up the Wifi through the web pages, run avrdude with an +option like "-Pnet:esp8266:23" (you can use an IP address instead of `esp8266`). My test command +line is as follows: +``` +/home/arduino/arduino-1.0.5/hardware/tools/avrdude \ + -C /home/arduino/arduino-1.0.5/hardware/tools/avrdude.conf -DV -patmega328p \ + -Pnet:esp8266:23 -carduino -b115200 -U flash:w:greenhouse.hex:i +``` +If you're using "edam's Arduino makefile" then you can simply set `SERIALDEV=net:bbb:2000` in your +sketch's Makefile. + +To program an LPC processor using the JeeLabs uploader. follow the instructions below for the jn-esp. + +Reflashing the esp-bridge itself (as opposed to the attached uController): +_you should not need to do this!_, in general use the over-the-air reflashing by downloading the latest release. +If you cannot reflash over-the-air and need to reflash serially, connect TX of a +USB BUB to RX of the esp-bridge and RX to TX (i.e. cross-over). Hold the flash button down +and briefly press the reset button. Then run esptool.py.as described below. + +jn-esp +------- +![dsc_5125](https://cloud.githubusercontent.com/assets/39480/8509322/08194674-2252-11e5-8539-4eacb0d79304.jpg) + +The jn-esp has an esp-03 module, an LPC824, a pseudo-FTDI connector (marked in tiny letters) +and a JeePort (also marked). On the bottom it is marked "JN-ESP-V2". +It comes preloaded with the latest version of esp-link. + +Power: the on-board MCP1825S-33 regulator can provide 500mA and is good from about 3.6v to 6v. +Connect power to the FTDI connector (GND and 5V marked on bottom). + +On power-up you should see the green LED on for ~1 second (the yellow should go on too, but +the firmware may not be configured correctly). After that the green should blink according to the +patterns described in the README's LED indicators section. Follow the Wifi configuration details +section thereafter. + +To program the LPC824 _ensure that you have a recent version of the Embello uploader_ +and point the Embello uploader at port 23. Something like: +``` +uploader -w -t -s 192.168.0.92:23 build/firmware.bin +``` +Remove the -s option if you don't want to stay connected. A simple sketch to try this out +with is the [hello sketch](https://github.com/jeelabs/embello/tree/master/projects/jnp/hello). +The result should look something like: +``` +$ uploader -w -t -s jn-esp:23 build/firmware.bin +found: 8242 - LPC824: 32 KB flash, 8 KB RAM, TSSOP20 +hwuid: 16500407679C61AE7189A053830200F5 +flash: 0640 done, 1540 bytes +entering terminal mode, press to quit: + + +[hello] +500 +1000 +1500 +2000 +2500 +... +``` + +The pseudo-ftdi connector has the following pin-out: + - 1: GND (marked on bottom) + - 2: LPC824 P17/A9 + - 3: 5V (marked on bottom) + - 4: LPC824 P11/SDA + - 5: LPC824 P10/SCL + - 6: LCP824 P23/A3/C4 + +The JeePort connector has the following pin-out: + - 1: LPC824 SWDIO/P2 (not 5v unlike JeeNodes!) + - 2: LPC824 P14/A2/C3 + - 3: GND + - 4: 3.3V (reg output) + - 5: LPC824 P13/A10 + - 6: LPC824 SWCLK/P2 + +Reflashing the jn-esp's esp8266 itself (as opposed to the attached uController): +_you should not need to do this!_, in general use the over-the-air reflashing by downloading the latest release. +If you cannot reflash over-the-air and need to reflash serially, there are SMD pads for an FTDI connector on the +bottom of the PCB below the esp-03 module. GND is marked. The best is to solder a right-angle +connector to it such that the pins point up (i.e. to the component side). You can then +hook-up a USB-BUB. I recommend jumpering the flash pin (next to GND) to GND and to +hook the reset pin (6) to the USB-BUB's DTR (should happen automatically). RX&TX also go +straight through). + +Wifi-link-12 +------------ + +The wifi-link has an esp-12 modulde, an FTDI connector, and a 2-pin power connector. +The underside is marked "Wifi-link-12-v2 Jeelabs TvE2015". +It comes preloaded with the latest version of esp-link V2. + +The ftdi connector has the following pin-out: + - 1: GND (marked top&bottom) + - 2: CTS (used as ISP to program ARM processors, also esp's TX when flashing the esp) + - 3: 5V (marked on bottom) + - 4: TX (from the esp) + - 5: RX (to the esp) + - 6: DTR (used ass reset to program AVR and ARM processors, also esp's RX when flashing the esp) + +Power: the on-board LM3671 switching regulator can provide 600mA while staying cool and is good from about 3.6v to 5.5v! +Connect power to the marked power connector or to the FTDI connector (GND marked, 5V on 3rd pin). Do not exceed 5.5v!! + +It is possible to bypass the on-board regulator and power the wifi-link directly with 3.3v: connect the 3.3v to the FTDI connector ("5v" pin) and switch the jumper on the bottom from 5v to 3v3 (you need to cut the tiny trace on the 5v side and jumper the two pads on the 3v3 side). + +On power-up you should see the green LED on for ~1 second (the yellow should go on too, but +the firmware may not be configured correctly). After that the green should blink according to the +patterns described in the README's LED indicators section. Follow the Wifi configuration details +section thereafter. + +The bottom also has a "sleep" jumper which connects the esp's reset pin with its gpio16 to enable deep-sleep +mode. This jumper is open and must be bridged to use the timed-wake-up feature of the esp's deep-sleep mode. + +To connect a JeeNode to the esp-bridge to flash the AVR or debug it, plug it into the FTDI +port straight, i.e. the component side of the JeeNode and of the wifi-link will be on the top. + +To connect an arduino, jumper gnd, tx, rx, and dtr for reset. The wifi-link-12-v2 has a 2.2K resistor on rx (serial going from AVR to esp) in order to protect it from 5v signals. + +To program the JeeNode or AVR, having set-up the Wifi through the web pages, run avrdude with an +option like "-Pnet:esp-link:23" (you can use an IP address instead of `esp-link`). My test command +line is as follows: +``` +/home/arduino/arduino-1.0.5/hardware/tools/avrdude \ + -C /home/arduino/arduino-1.0.5/hardware/tools/avrdude.conf -DV -patmega328p \ + -Pnet:esp-link:23 -carduino -b115200 -U flash:w:greenhouse.hex:i +``` +If you're using "edam's Arduino makefile" then you can simply set `SERIALDEV=net:esp-link:23` in your +sketch's Makefile. You can also use port 2323 which forces programming mode (no real benefit with avrdude, but can +enable programming PICs). + +Serially reflashing the wifi-link itself (as opposed to the attached uController): +_you should not need to do this!_, in general use the over-the-air reflashing by downloading the latest release. +If you cannot reflash over-the-air and need to reflash serially, follow this process: +- connect TX of the programmer (such as USB-BUB or a FDTI-friend) to DTR (FTDI pin 6) +- connect RX of the programmer to CTS (FTDI pin 2) +- short the flash jumper on the top of the board (push tweezers or a piece of wire into the jumper gap), + the green LED will be on solid when you have contact +- briefly interrupt the power to reset the esp +- it will now be in flash mode: the green LED should be off (assuming you release the flash jumper), + if you see the green LED come on it has rebooted into esp-link and you need to reset it again + (the whole proess take some fiddling, you can solder a real jumper or switch to the flash pads and there's a similar + reset jumper pad on the bottom of the PCB). +- use your favorite programming tool to reflash, you have time if the esp really entered programming mode, it often takes me + 2-3 tries until the programming works + +Serial flashing +--------------- + +Once you have a version of esp-link flashed to your module or if you received a pre-flashed +module from me you should not need this section. But sometimes things fail badly and your +module is "brocked", this is how you receover. + +### Installing esptool.py + +On Linux I am using [esptool.py](https://github.com/themadinventor/esptool) to flash the esp8266. +If you're a little python challenged (like I am) 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 + +### Flashing esp-link + +Using esptool.py a esp-link release can be flashed as follows: +``` +curl -L https://github.com/jeelabs/esp-link/releases/download/0.10.1/esp-link.tgz | tar xzf - +cd esp-link +esptool.py write_flash 0x00000 boot_v1.4\(b1\).bin 0x1000 user1.bin 0x7e000 blank.bin +``` +If you want to speed things up a bit and if you need to specify the port you can use a command +line like: +``` +esptool.py --port /dev/ttyUSB0 --baud 460880 write_flash 0x00000 boot_v1.4\(b1\).bin \ + 0x1000 user1.bin 0x7e000 blank.bin +``` diff --git a/ESP-LINK.adoc b/ESP-LINK.adoc new file mode 100644 index 0000000..d6f226f --- /dev/null +++ b/ESP-LINK.adoc @@ -0,0 +1,535 @@ +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 reqritten and no longer protocol compatible, thanks to tuanpmt for the +inspiration! + +Many thanks to https://github.com/brunnels for contributions in particular around the espduino +functionality. Thank you also to https://github.com/susisstrolch and https://github.com/bc547 for +additional contributions! + +[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 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. diff --git a/FLASH.md b/FLASH.md new file mode 100644 index 0000000..893d035 --- /dev/null +++ b/FLASH.md @@ -0,0 +1,78 @@ +ESP-LINK OTA Flash Layout +========================= + +The flash layout dictated by the bootloader is the following (all this assumes a 512KB flash chip +and is documented in Espressif's `99C-ESP8266__OTA_Upgrade__EN_v1.5.pdf`): + - @0x00000 4KB bootloader + - @0x01000 236KB partition1 + - @0x3E000 16KB esp-link parameters + - @0x40000 4KB unused + - @0x41000 236KB partition2 + - @0x7E000 16KB system wifi parameters + +What this means is that we can flash just about anything into partition1 or partition2 as long +as it doesn't take more than 236KB and has the right format that the boot loader understands. +We can't mess with the first 4KB nor the last 16KB of the flash. + +Now how does a code partition break down? that is reflected in the following definition found in +the loader scripts: +``` + dram0_0_seg : org = 0x3FFE8000, len = 0x14000 + iram1_0_seg : org = 0x40100000, len = 0x8000 + irom0_0_seg : org = 0x40201010, len = 0x2B000 +``` +This means that 80KB (0x14000) are reserved for "dram0_0", 32KB (0x8000) for "iram1_0" and +172KB (0x2B000) are reserved for irom0_0. The segments are used as follows: + - dram0_0 is the data RAM and some of that gets initialized at boot time from flash (static variable initialization) + - iram1_0 is the instruction RAM and all of that gets loaded at boot time from flash + - irom0_0 is the instruction cache which gets loaded on-demand from flash (all functions + with the `ICACHE_FLASH_ATTR` attribute go there) + +You might notice that 80KB+32KB+172KB is more than 236KB and that's because not the entire dram0_0 +segment needs to be loaded from flash, only the portion with statically initialized data. +You might also notice that while iram1_0 is as large as the chip's instruction RAM (at least +according to the info I've seen) the size of the irom0_0 segment is smaller than it could be, +since it's really not bounded by any limitation of the processor (it simply backs the cache). + +When putting the OTA flash process together I ran into loader issues, namely, while I was having +relatively little initialized data and also not 32KB of iram1_0 instructions I was overflowing +the allotted 172KB of irom0_0. To fix the problem the build process modifies the loader scripts +(see the `build/eagle.esphttpd1.v6.ld` target in the Makefile) to increase the irom0_0 segment +to 224KB (a somewhat arbitrary value). This doesn't mean that there will be 224KB of irom0_0 +in flash, it just means that that's the maximum the linker will put there without giving an error. +In the end what has to fit into the magic 236KB is the sum of the actual initialized data, +the actually used iram1_0 segment, and the irom0_0 segment. +In addition, the dram0_0 and iram1_0 segments can't exceed what's specified +in the loader script 'cause those are the limitations of the processor. + +Now that you hopefully understand the above you can understand the line printed by the Makefile +when linking the firmware, which looks something like: +``` +** user1.bin uses 218592 bytes of 241664 available +``` +Here 241664 is 236KB and 218592 is the size of what's getting flashed, so you can tell that you have +another 22KB to spend (modulo some 4KB flash segment rounding). +(Note that user2.bin has exactly the same size, so the Makefile doesn't print its info.) +The Makefile also prints a few more details: +``` +ls -ls eagle*bin + 4 -rwxrwxr-x 1 tve tve 2652 May 24 10:12 eagle.app.v6.data.bin +176 -rwxrwxr-x 1 tve tve 179732 May 24 10:12 eagle.app.v6.irom0text.bin + 8 -rwxrwxr-x 1 tve tve 5732 May 24 10:12 eagle.app.v6.rodata.bin + 32 -rwxrwxr-x 1 tve tve 30402 May 24 10:12 eagle.app.v6.text.bin +``` +This says that we have 179732 bytes of irom0_0, we have 5732+2652 bytes of dram0_0 (read-only data +plus initialized read-write data), and we have 30402 bytes of iram1_0. + +There's an additional twist to all this for the espfs "file system" that esphttpd uses. +The data for this is loaded at the end of irom0_0 and is called espfs. +The Makefile modifies the loader script to place the espfs at the start of irom0_0 and +ensure that it's 32-bit aligned. The size of the espfs is shown here: +``` +4026be14 g .irom0.text 00000000 _binary_espfs_img_end +40269e98 g .irom0.text 00000000 _binary_espfs_img_start +00001f7c g *ABS* 00000000 _binary_espfs_img_size +``` +Namely, 0x1f7c = 8060 bytes. + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..205b69a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,143 @@ +Copyright (c) Nemanja Stefanovic, 2016 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------------------- + +Portions of this software are derived from esp-link and have the following +license: + +Copyright (c) Thorsten von Eicken, 2015 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------------------- + +Portions of this software are derived from esphttpd and have the following +license: + +"THE BEER-WARE LICENSE" (Revision 42): +Jeroen Domburg wrote this file. As long as you retain +this notice you can do whatever you want with this stuff. If we meet some day, +and you think this stuff is worth it, you can buy me a beer in return. + +--------------------------------------------------------------------- + +Portions of this software are derived from Espressif's SDK and carry the +following copyright notice: + +This file is part of Espressif's AT+ command set program. +Copyright (C) 2013 - 2016, Espressif Systems + +This program is free software: you can redistribute it and/or modify +it under the terms of version 3 of the GNU General Public License as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . + +--------------------------------------------------------------------- + +The Pure CSS portions of this software carry the following license: + +Copyright 2014 Yahoo! Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of the Yahoo! Inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------------------- + +Normalize.css used in this firmware carries the following license: + +Copyright (c) Nicolas Gallagher and Jonathan Neal + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b55643 --- /dev/null +++ b/Makefile @@ -0,0 +1,493 @@ +# +# Makefile for esp-link - https://github.com/jeelabs/esp-link +# +# Makefile heavily adapted to esp-link and wireless flashing by Thorsten von Eicken +# Lots of work, in particular to support windows, by brunnels +# Original from esphttpd and others... +# VERBOSE=1 +# +# Start by setting the directories for the toolchain a few lines down +# the default target will build the firmware images +# `make flash` will flash the esp serially +# `make wiflash` will flash the esp over wifi +# `VERBOSE=1 make ...` will print debug info +# `ESP_HOSTNAME=my.esp.example.com make wiflash` is an easy way to override a variable + +# optional local configuration file +-include local.conf + +# The Wifi station configuration can be hard-coded here, which makes esp-link come up in STA+AP +# mode trying to connect to the specified AP *only* if the flash wireless settings are empty! +# This happens on a full serial flash and avoids having to hunt for the AP... +# STA_SSID ?= +# STA_PASS ?= + +# The SOFTAP configuration can be hard-coded here, the minimum parameters to set are AP_SSID && AP_PASS +# The AP SSID has to be at least 8 characters long, same for AP PASSWORD +# The AP AUTH MODE can be set to: +# 0 = AUTH_OPEN, +# 1 = AUTH_WEP, +# 2 = AUTH_WPA_PSK, +# 3 = AUTH_WPA2_PSK, +# 4 = AUTH_WPA_WPA2_PSK +# SSID hidden default 0, ( 0 | 1 ) +# Max connections default 4, ( 1 ~ 4 ) +# Beacon interval default 100, ( 100 ~ 60000ms ) +# +AP_SSID ?=happy_bubbles_nfc +# AP_PASS ?=esp_link_test +AP_AUTH_MODE ?=0 +# AP_SSID_HIDDEN ?=0 +# AP_MAX_CONN ?=4 +# AP_BEACON_INTERVAL ?=100 + +# If CHANGE_TO_STA is set to "yes" the esp-link module will switch to station mode +# once successfully connected to an access point. Else it will stay in STA+AP mode. +CHANGE_TO_STA ?= yes + +# hostname or IP address for wifi flashing +ESP_HOSTNAME ?= happy_bubbles_nfc + +# --------------- toolchain configuration --------------- + +# Base directory for the compiler. Needs a / at the end. +# Typically you'll install https://github.com/pfalcon/esp-open-sdk +XTENSA_TOOLS_ROOT ?= $(abspath ../esp-open-sdk/xtensa-lx106-elf/bin)/ + +# Base directory of the ESP8266 SDK package, absolute +# Typically you'll download from Espressif's BBS, http://bbs.espressif.com/viewforum.php?f=5 +SDK_BASE ?= $(abspath ../esp_iot_sdk_v1.5.1) + +# Esptool.py path and port, only used for 1-time serial flashing +# Typically you'll use https://github.com/themadinventor/esptool +# Windows users use the com port i.e: ESPPORT ?= com3 +ESPTOOL ?= $(abspath ../esp-open-sdk/esptool/esptool.py) +ESPPORT ?= /dev/ttyUSB0 +ESPBAUD ?= 460800 +#ESPBAUD ?= 115200 +#ESPBAUD ?= 9600 + +# --------------- chipset configuration --------------- + +# Pick your flash size: "512KB", "1MB", or "4MB" +FLASH_SIZE ?= 4MB + +# The pin assignments below are used when the settings in flash are invalid, they +# can be changed via the web interface +# GPIO pin used to reset attached microcontroller, acative low +MCU_RESET_PIN ?= 12 +# GPIO pin used with reset to reprogram MCU (ISP=in-system-programming, unused with AVRs), active low +MCU_ISP_PIN ?= 13 +# GPIO pin used for "connectivity" LED, active low +LED_CONN_PIN ?= 0 +# GPIO pin used for "serial activity" LED, active low +LED_SERIAL_PIN ?= 14 + +# --------------- esp-link modules config options --------------- + +# Optional Modules mqtt +#MODULES ?= mqtt rest syslog serial + +MODULES ?= mqtt + +# --------------- esphttpd config options --------------- + +# If GZIP_COMPRESSION is set to "yes" then the static css, js, and html files will be compressed +# with gzip before added to the espfs image and will be served with gzip Content-Encoding header. +# This could speed up the downloading of these files, but might break compatibility with older +# web browsers not supporting gzip encoding because Accept-Encoding is simply ignored. +# Enable this option if you have large static files to serve (for e.g. JQuery, Twitter bootstrap) +# If you have text based static files with different extensions what you want to serve compressed +# then you will need to add the extension to the following places: +# - Add the extension to this Makefile at the webpages.espfs target to the find command +# - Add the extension to the gzippedFileTypes array in the user/httpd.c file +# +# Adding JPG or PNG files (and any other compressed formats) is not recommended, because GZIP +# compression does not work effectively on compressed files. + +#Static gzipping is disabled by default. +GZIP_COMPRESSION ?= yes + +# If COMPRESS_W_HTMLCOMPRESSOR is set to "yes" then the static css and js files will be compressed with +# htmlcompressor and yui-compressor. This option works only when GZIP_COMPRESSION is set to "yes". +# https://code.google.com/p/htmlcompressor/#For_Non-Java_Projects +# http://yui.github.io/yuicompressor/ +# enabled by default. +COMPRESS_W_HTMLCOMPRESSOR ?= yes +HTML_COMPRESSOR ?= htmlcompressor-1.5.3.jar +YUI_COMPRESSOR ?= yuicompressor-2.4.8.jar + +# -------------- End of config options ------------- + +HTML_PATH = $(abspath ./html)/ +WIFI_PATH = $(HTML_PATH)wifi/ + +ESP_FLASH_MAX ?= 503808 # max bin file + +ifeq ("$(FLASH_SIZE)","512KB") +# Winbond 25Q40 512KB flash, typ for esp-01 thru esp-11 +ESP_SPI_SIZE ?= 0 # 0->512KB (256KB+256KB) +ESP_FLASH_MODE ?= 0 # 0->QIO +ESP_FLASH_FREQ_DIV ?= 0 # 0->40Mhz +ET_FS ?= 4m # 4Mbit flash size in esptool flash command +ET_FF ?= 40m # 40Mhz flash speed in esptool flash command +ET_BLANK ?= 0x7E000 # where to flash blank.bin to erase wireless settings + +else ifeq ("$(FLASH_SIZE)","1MB") +# ESP-01E +ESP_SPI_SIZE ?= 2 # 2->1MB (512KB+512KB) +ESP_FLASH_MODE ?= 0 # 0->QIO +ESP_FLASH_FREQ_DIV ?= 15 # 15->80MHz +ET_FS ?= 8m # 8Mbit flash size in esptool flash command +ET_FF ?= 80m # 80Mhz flash speed in esptool flash command +ET_BLANK ?= 0xFE000 # where to flash blank.bin to erase wireless settings + +else ifeq ("$(FLASH_SIZE)","2MB") +# Manuf 0xA1 Chip 0x4015 found on wroom-02 modules +# Here we're using two partitions of approx 0.5MB because that's what's easily available in terms +# of linker scripts in the SDK. Ideally we'd use two partitions of approx 1MB, the remaining 2MB +# cannot be used for code (esp8266 limitation). +ESP_SPI_SIZE ?= 4 # 6->4MB (1MB+1MB) or 4->4MB (512KB+512KB) +ESP_FLASH_MODE ?= 0 # 0->QIO, 2->DIO +ESP_FLASH_FREQ_DIV ?= 15 # 15->80Mhz +ET_FS ?= 16m # 16Mbit flash size in esptool flash command +ET_FF ?= 80m # 80Mhz flash speed in esptool flash command +ET_BLANK ?= 0x1FE000 # where to flash blank.bin to erase wireless settings + +else +# Winbond 25Q32 4MB flash, typ for esp-12 +# Here we're using two partitions of approx 0.5MB because that's what's easily available in terms +# of linker scripts in the SDK. Ideally we'd use two partitions of approx 1MB, the remaining 2MB +# cannot be used for code (esp8266 limitation). +ESP_SPI_SIZE ?= 4 # 6->4MB (1MB+1MB) or 4->4MB (512KB+512KB) +ESP_FLASH_MODE ?= 0 # 0->QIO, 2->DIO +ESP_FLASH_FREQ_DIV ?= 15 # 15->80Mhz +ET_FS ?= 32m # 32Mbit flash size in esptool flash command +ET_FF ?= 80m # 80Mhz flash speed in esptool flash command +ET_BLANK ?= 0x3FE000 # where to flash blank.bin to erase wireless settings +endif + +# --------------- esp-link version --------------- + +# This queries git to produce a version string like "esp-link v0.9.0 2015-06-01 34bc76" +# If you don't have a proper git checkout or are on windows, then simply swap for the constant +# Steps to release: create release on github, git pull, git describe --tags to verify you're +# on the release tag, make release, upload esp-link.tgz into the release files +#VERSION ?= "esp-link custom version" +DATE := $(shell date '+%F %T') +BRANCH ?= $(shell if git diff --quiet HEAD; then git describe --tags; \ + else git symbolic-ref --short HEAD; fi) +SHA := $(shell if git diff --quiet HEAD; then git rev-parse --short HEAD | cut -d"/" -f 3; \ + else echo "development"; fi) +#VERSION ?=esp-link $(BRANCH) - $(DATE) - $(SHA) + +VERSION ?=version 7 + +# Output directors to store intermediate compiled files +# relative to the project directory +BUILD_BASE = build +FW_BASE = firmware + +# name for the target project +TARGET = httpd + +# espressif tool to concatenate sections for OTA upload using bootloader v1.2+ +APPGEN_TOOL ?= gen_appbin.py + +CFLAGS= + +# set defines for optional modules +ifneq (,$(findstring mqtt,$(MODULES))) + CFLAGS += -DMQTT +endif + +ifneq (,$(findstring rest,$(MODULES))) + CFLAGS += -DREST +endif + +ifneq (,$(findstring syslog,$(MODULES))) + CFLAGS += -DSYSLOG +endif + +# which modules (subdirectories) of the project to include in compiling +LIBRARIES_DIR = libraries +MODULES += espfs httpd user serial cmd esp-link +MODULES += $(foreach sdir,$(LIBRARIES_DIR),$(wildcard $(sdir)/*)) +EXTRA_INCDIR = include . + +# libraries used in this project, mainly provided by the SDK +LIBS = c gcc hal phy pp net80211 wpa main lwip crypto ssl + +# compiler flags using during compilation of source files +#CFLAGS += -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ +#CFLAGS += -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wl,-EL -fno-inline-functions \ + +CFLAGS += -Os -ggdb -std=c99 -Wpointer-arith -Wundef -Wl,-EL -fno-inline-functions \ + -nostdlib -mlongcalls -mtext-section-literals -ffunction-sections -fdata-sections \ + -D__ets__ -DICACHE_FLASH -D_STDINT_H -Wno-address -DFIRMWARE_SIZE=$(ESP_FLASH_MAX) \ + -DMCU_RESET_PIN=$(MCU_RESET_PIN) -DMCU_ISP_PIN=$(MCU_ISP_PIN) \ + -DLED_CONN_PIN=$(LED_CONN_PIN) -DLED_SERIAL_PIN=$(LED_SERIAL_PIN) \ + -DVERSION="$(VERSION)" + +# linker flags used to generate the main object file +LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static -Wl,--gc-sections + +# linker script used for the above linker step +LD_SCRIPT := build/eagle.esphttpd.v6.ld +LD_SCRIPT1 := build/eagle.esphttpd1.v6.ld +LD_SCRIPT2 := build/eagle.esphttpd2.v6.ld + +# various paths from the SDK used in this project +SDK_LIBDIR = lib +SDK_LDDIR = ld +SDK_INCDIR = include include/json +SDK_TOOLSDIR = tools + +# select which tools to use as compiler, librarian and linker +CC := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc +AR := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-ar +LD := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc +OBJCP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objcopy +OBJDP := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objdump + + +#### +SRC_DIR := $(MODULES) +BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES)) + +SDK_LIBDIR := $(addprefix $(SDK_BASE)/,$(SDK_LIBDIR)) +SDK_LDDIR := $(addprefix $(SDK_BASE)/,$(SDK_LDDIR)) +SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR)) +SDK_TOOLS := $(addprefix $(SDK_BASE)/,$(SDK_TOOLSDIR)) +APPGEN_TOOL := $(addprefix $(SDK_TOOLS)/,$(APPGEN_TOOL)) + +SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c)) +OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC)) $(BUILD_BASE)/espfs_img.o +LIBS := $(addprefix -l,$(LIBS)) +APP_AR := $(addprefix $(BUILD_BASE)/,$(TARGET)_app.a) +USER1_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).user1.out) +USER2_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).user2.out) + +INCDIR := $(addprefix -I,$(SRC_DIR)) +EXTRA_INCDIR := $(addprefix -I,$(EXTRA_INCDIR)) +MODULE_INCDIR := $(addsuffix /include,$(INCDIR)) + +V ?= $(VERBOSE) +ifeq ("$(V)","1") +Q := +vecho := @true +else +Q := @ +vecho := @echo +endif + +ifneq ($(strip $(STA_SSID)),) +CFLAGS += -DSTA_SSID="$(STA_SSID)" +endif + +ifneq ($(strip $(STA_PASS)),) +CFLAGS += -DSTA_PASS="$(STA_PASS)" +endif + +ifneq ($(strip $(AP_SSID)),) +CFLAGS += -DAP_SSID="$(AP_SSID)" +endif + +ifneq ($(strip $(AP_PASS)),) +CFLAGS += -DAP_PASS="$(AP_PASS)" +endif + +ifneq ($(strip $(AP_AUTH_MODE)),) +CFLAGS += -DAP_AUTH_MODE="$(AP_AUTH_MODE)" +endif + +ifneq ($(strip $(AP_SSID_HIDDEN)),) +CFLAGS += -DAP_SSID_HIDDEN="$(AP_SSID_HIDDEN)" +endif + +ifneq ($(strip $(AP_MAX_CONN)),) +CFLAGS += -DAP_MAX_CONN="$(AP_MAX_CONN)" +endif + +ifneq ($(strip $(AP_BEACON_INTERVAL)),) +CFLAGS += -DAP_BEACON_INTERVAL="$(AP_BEACON_INTERVAL)" +endif + +ifeq ("$(GZIP_COMPRESSION)","yes") +CFLAGS += -DGZIP_COMPRESSION +endif + +ifeq ("$(CHANGE_TO_STA)","yes") +CFLAGS += -DCHANGE_TO_STA +endif + +vpath %.c $(SRC_DIR) + +define compile-objects +$1/%.o: %.c + $(vecho) "CC $$<" + $(Q)$(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ +endef + +.PHONY: all checkdirs clean webpages.espfs wiflash + +all: echo_version checkdirs $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin + +echo_version: + @echo VERSION: $(VERSION) + +$(USER1_OUT): $(APP_AR) $(LD_SCRIPT1) + $(vecho) "LD $@" + $(Q) $(LD) -L$(SDK_LIBDIR) -T$(LD_SCRIPT1) $(LDFLAGS) -Wl,--start-group $(LIBS) $(APP_AR) -Wl,--end-group -o $@ + @echo Dump : $(OBJDP) -x $(USER1_OUT) + @echo Disass: $(OBJDP) -d -l -x $(USER1_OUT) +# $(Q) $(OBJDP) -x $(TARGET_OUT) | egrep espfs_img + +$(USER2_OUT): $(APP_AR) $(LD_SCRIPT2) + $(vecho) "LD $@" + $(Q) $(LD) -L$(SDK_LIBDIR) -T$(LD_SCRIPT2) $(LDFLAGS) -Wl,--start-group $(LIBS) $(APP_AR) -Wl,--end-group -o $@ +# $(Q) $(OBJDP) -x $(TARGET_OUT) | egrep espfs_img + +$(FW_BASE): + $(vecho) "FW $@" + $(Q) mkdir -p $@ + +$(FW_BASE)/user1.bin: $(USER1_OUT) $(FW_BASE) + $(Q) $(OBJCP) --only-section .text -O binary $(USER1_OUT) eagle.app.v6.text.bin + $(Q) $(OBJCP) --only-section .data -O binary $(USER1_OUT) eagle.app.v6.data.bin + $(Q) $(OBJCP) --only-section .rodata -O binary $(USER1_OUT) eagle.app.v6.rodata.bin + $(Q) $(OBJCP) --only-section .irom0.text -O binary $(USER1_OUT) eagle.app.v6.irom0text.bin + ls -ls eagle*bin + $(Q) COMPILE=gcc PATH=$(XTENSA_TOOLS_ROOT):$(PATH) python $(APPGEN_TOOL) $(USER1_OUT) 2 $(ESP_FLASH_MODE) $(ESP_FLASH_FREQ_DIV) $(ESP_SPI_SIZE) 0 + $(Q) rm -f eagle.app.v6.*.bin + $(Q) mv eagle.app.flash.bin $@ + @echo "** user1.bin uses $$(stat -c '%s' $@) bytes of" $(ESP_FLASH_MAX) "available" + $(Q) if [ $$(stat -c '%s' $@) -gt $$(( $(ESP_FLASH_MAX) )) ]; then echo "$@ too big!"; false; fi + +$(FW_BASE)/user2.bin: $(USER2_OUT) $(FW_BASE) + $(Q) $(OBJCP) --only-section .text -O binary $(USER2_OUT) eagle.app.v6.text.bin + $(Q) $(OBJCP) --only-section .data -O binary $(USER2_OUT) eagle.app.v6.data.bin + $(Q) $(OBJCP) --only-section .rodata -O binary $(USER2_OUT) eagle.app.v6.rodata.bin + $(Q) $(OBJCP) --only-section .irom0.text -O binary $(USER2_OUT) eagle.app.v6.irom0text.bin + $(Q) COMPILE=gcc PATH=$(XTENSA_TOOLS_ROOT):$(PATH) python $(APPGEN_TOOL) $(USER2_OUT) 2 $(ESP_FLASH_MODE) $(ESP_FLASH_FREQ_DIV) $(ESP_SPI_SIZE) 0 + $(Q) rm -f eagle.app.v6.*.bin + $(Q) mv eagle.app.flash.bin $@ + $(Q) if [ $$(stat -c '%s' $@) -gt $$(( $(ESP_FLASH_MAX) )) ]; then echo "$@ too big!"; false; fi + +$(APP_AR): $(OBJ) + $(vecho) "AR $@" + $(Q) $(AR) cru $@ $^ + +checkdirs: $(BUILD_DIR) + +$(BUILD_DIR): + $(Q) mkdir -p $@ + +wiflash: all + ./wiflash $(ESP_HOSTNAME) $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin + +baseflash: all + $(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash 0x01000 $(FW_BASE)/user1.bin + +flash: all + $(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash -fs $(ET_FS) -ff $(ET_FF) \ + 0x00000 "$(SDK_BASE)/bin/boot_v1.5.bin" 0x01000 $(FW_BASE)/user1.bin \ + $(ET_BLANK) $(SDK_BASE)/bin/blank.bin + +ifeq ($(OS),Windows_NT) +tools/$(HTML_COMPRESSOR): + $(Q) mkdir -p tools + cd tools; wget --no-check-certificate https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR) -O $(YUI_COMPRESSOR) + cd tools; wget --no-check-certificate https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) -O $(HTML_COMPRESSOR) +else +tools/$(HTML_COMPRESSOR): + $(Q) mkdir -p tools + cd tools; wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR) + cd tools; wget https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) +endif + +ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") +$(BUILD_BASE)/espfs_img.o: tools/$(HTML_COMPRESSOR) +endif + +$(BUILD_BASE)/espfs_img.o: html/ html/wifi/ espfs/mkespfsimage/mkespfsimage + $(Q) rm -rf html_compressed; mkdir html_compressed; mkdir html_compressed/wifi; + $(Q) cp -r html/*.ico html_compressed; + $(Q) cp -r html/*.css html_compressed; + $(Q) cp -r html/*.js html_compressed; + $(Q) cp -r html/wifi/*.png html_compressed/wifi; + $(Q) cp -r html/wifi/*.js html_compressed/wifi; + $(Q) cp -r html/hippo.svg html_compressed/hippo.svg; +ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") + $(Q) echo "Compression assets with htmlcompressor. This may take a while..." + $(Q) java -jar tools/$(HTML_COMPRESSOR) \ + -t html --remove-surrounding-spaces max --remove-quotes --remove-intertag-spaces \ + -o $(abspath ./html_compressed)/ \ + $(HTML_PATH)head- \ + $(HTML_PATH)*.html + $(Q) java -jar tools/$(HTML_COMPRESSOR) \ + -t html --remove-surrounding-spaces max --remove-quotes --remove-intertag-spaces \ + -o $(abspath ./html_compressed)/wifi/ \ + $(WIFI_PATH)*.html + $(Q) echo "Compression assets with yui-compressor. This may take a while..." + $(Q) for file in `find html_compressed -type f -name "*.js"`; do \ + java -jar tools/$(YUI_COMPRESSOR) $$file --line-break 0 -o $$file; \ + done + $(Q) for file in `find html_compressed -type f -name "*.css"`; do \ + java -jar tools/$(YUI_COMPRESSOR) $$file -o $$file; \ + done +else + $(Q) cp -r html/head- html_compressed; + $(Q) cp -r html/*.html html_compressed; + $(Q) cp -r html/wifi/*.html html_compressed/wifi; +endif +ifeq (,$(findstring mqtt,$(MODULES))) + $(Q) rm -rf html_compressed/mqtt.html + $(Q) rm -rf html_compressed/mqtt.js +endif + $(Q) for file in `find html_compressed -type f -name "*.htm*"`; do \ + cat html_compressed/head- $$file >$${file}-; \ + mv $$file- $$file; \ + done + $(Q) rm html_compressed/head- + $(Q) cd html_compressed; find . \! -name \*- | ../espfs/mkespfsimage/mkespfsimage > ../build/espfs.img; cd ..; + $(Q) ls -sl build/espfs.img + $(Q) cd build; $(OBJCP) -I binary -O elf32-xtensa-le -B xtensa --rename-section .data=.espfs \ + espfs.img espfs_img.o; cd .. + +# edit the loader script to add the espfs section to the end of irom with a 4 byte alignment. +# we also adjust the sizes of the segments 'cause we need more irom0 +build/eagle.esphttpd1.v6.ld: $(SDK_LDDIR)/eagle.app.v6.new.1024.app1.ld + $(Q) sed -e '/\.irom\.text/{' -e 'a . = ALIGN (4);' -e 'a *(.espfs)' -e '}' \ + -e '/^ irom0_0_seg/ s/6B000/7C000/' \ + $(SDK_LDDIR)/eagle.app.v6.new.1024.app1.ld >$@ +build/eagle.esphttpd2.v6.ld: $(SDK_LDDIR)/eagle.app.v6.new.1024.app2.ld + $(Q) sed -e '/\.irom\.text/{' -e 'a . = ALIGN (4);' -e 'a *(.espfs)' -e '}' \ + -e '/^ irom0_0_seg/ s/6B000/7C000/' \ + $(SDK_LDDIR)/eagle.app.v6.new.1024.app2.ld >$@ + +espfs/mkespfsimage/mkespfsimage: espfs/mkespfsimage/ + $(Q) $(MAKE) -C espfs/mkespfsimage GZIP_COMPRESSION="$(GZIP_COMPRESSION)" + +release: all + $(Q) rm -rf release; mkdir -p release/esp-link-$(BRANCH) + $(Q) egrep -a 'esp-link [a-z0-9.]+ - 201' $(FW_BASE)/user1.bin | cut -b 1-80 + $(Q) egrep -a 'esp-link [a-z0-9.]+ - 201' $(FW_BASE)/user2.bin | cut -b 1-80 + $(Q) cp $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin $(SDK_BASE)/bin/blank.bin \ + "$(SDK_BASE)/bin/boot_v1.5.bin" wiflash avrflash release/esp-link-$(BRANCH) + $(Q) tar zcf esp-link-$(BRANCH).tgz -C release esp-link-$(BRANCH) + $(Q) echo "Release file: esp-link-$(BRANCH).tgz" + $(Q) rm -rf release + +clean: + $(Q) rm -f $(APP_AR) + $(Q) rm -f $(TARGET_OUT) + $(Q) find $(BUILD_BASE) -type f | xargs rm -f + $(Q) make -C espfs/mkespfsimage/ clean + $(Q) rm -rf $(FW_BASE) + $(Q) rm -f webpages.espfs +ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") + $(Q) rm -rf html_compressed +endif + +$(foreach bdir,$(BUILD_DIR),$(eval $(call compile-objects,$(bdir)))) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1806f1b --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# Happy Bubbles - Wifi NFC Device + +This device is in the "Happy Bubbles" series of open hardware and open source IoT and home-automation devices. The Wifi NFC device is easy to use, easy to make yourself, and cheap. + +Its purpose is to send NFC/RFID tags that you touch to it to an HTTP URL on whatever server you'd like. + +What you need to build it: + * a NodeMCU ESP8266 development kit - [buy one here](http://amzn.to/1S03qNE) + * an RC522 RFID module - [buy one here](http://amzn.to/1U1rVxE) + * (optional) a WS2812B RGB LED + +## How to set it up + +#### 0. Initial flashing + +Easiest is to download the 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, thanks tve! + +#### 1. Wire it up +| RC522 NFC pin | NodeMCU pin | ESP8266 GPIO | +|----------|:-------------:|------:| +| SDA | D8 | GPIO 15 | +| SCK | D5 | GPIO 14 | +| MOSI | D7 | GPIO 13 | +| MISO | D6 | GPIO 12 | +| IRQ | Not connected| Not connected | +| GND | Ground / GND | Ground | +| RST | D4 | GPIO 2 | +| 3.3V | 3.3V / VCC | VCC | + +If there is interest, I can create a PCB for you guys to make this easier. Let me know if anyone would like these and I could put them up for sale. + +#### 2. Optional WS2812B LED + +An RGB LED is useful as a status indicator to tell you when you're in config-mode, when a tag is hit, and the server response can even change its colour! + +| WS2812 pin | NodeMCU pin | ESP8266 GPIO | +|----------|:-------------:|------:| +| GND | GND | GND | +| Data-In | D1 | GPIO 5 | +| 5V Power | VIN | not applicable | + +#### 3. Powering it up +Just insert a microUSB cable in the NodeMCU! When it boots up, its red LED should be lit. If you have a WS2812b hooked up, it will glow orange. + +If it doesn't, try holding the "FLASH: button on the nodeMCU board held down for about 10 seconds. Holding the button down lets to change between config mode and RFID-scanning mode. I left these two seperate for security, so that while your device is acting as an RFID scanner, someone can't just connect to its hostname and start messing with your settings. + +#### 4. Connect it to your wifi network +1. With the board powered up, the Happy Bubbles NFC device will automatically start an access point you can connect to called "happy-bubbles-nfc". +2. Connect to this access point with your computer +3. Open a browser and visit http://192.168.4.1/ +4. You'll now be at the Happy Bubbles config screen! You can change the hostname of the device here. +5. On the left side with the navigation bar, go to "WiFi Setup". It will populate a bunch of wifi access points it can see. Find yours and type in the password. You should see it get an IP address from it, and now you're connected! + +#### 5. Make it hit your server +1. From the same config mode, go to "NFC Setup". In there, you can set some parameters for how the JSON request from the Happy Bubbles device will go to your server. +2. The most important one here is the HTTP URL, this URL will receive a POST request from the device when a tag is scanned. +3. The 'Device ID' is a name you can give this device that will also be included in the JSON. This is useful if you have more than one Happy Bubbles NFC device around and you want to know which one the request is coming from. +4. The 'Device Secret' and 'Device counter' are optional but useful if you want to authenticate the requests to make sure someone isn't spoofing a tag. + +#### 5. Make it run! + +Hold down the "FLASH" button on the NodeMCU board for about 10 seconds. The red LED on the NodeMCU should turn on, and if you have an RGB LED hooked up, it will blink purple a few times, fast. It's now reading tags! Every time you touch a tag, the RGB LED will light pink quickly. + +## Communication + +#### HTTP Request +Every time you touch a tag to the device, it will send an HTTP POST request to the URL you specified in the config. The REQUEST payload will be JSON that looks like this: +``` +{ + "device_id": "CONFIG-DEVICE-ID", + "tag_uid": "THE-TAGS-UUID", + "counter": 3, + "hash": "7cac4442e6caf834ce20a359ff7ee9ff34525691" +} +``` + +The "device_id" is the "Device ID" you specified in the config. The "tag_uuid" is the unique ID of the tag that was detected by the reader. The "counter" and "hash" are optional security authentication parameters described below in the "Optional Authentication" section. + +#### HTTP Response +After you touch the tag, the device expects a 200 OK response with a JSON payload. Don't worry about having to send one, it's optional: +``` +{ + "r":5, + "g":250, + "b":2, + "delay":2000 +} +``` + +The "r", "g", and "b" are RGB colour values for the optional WS2812B LED. The server could tell the LED to light up a certain way after the request. This way you could give the user some kind of indication of what happened. + +The "delay" is the number of milliseconds to keep the color after a request. If you make it 0, the LED will stay lit that way forever, or until the next reqest...or until you remove power! + + + +## Optional Authentication +The way this authentication works is that the Happy Bubbles NFC device will send a "hash" string in its JSON response. This "hash" is an SHA1-based HMAC, its input is the device-id, the NFC tag-ID, and the counter all concatenated together with no spaces. The 'counter' increments every time your server sends back a 200 OK response. It is used as a 'nonce' to prevent replay attacks on this authentication mechanism. +This string is then SHA1-HMAC'd hashed against the "device secret" from the config menu. You can use this on the backend to ensure no one is spoofing tag-hits, it's optional. + + +### Credits + +This project wouldn't have been possible without my standing on the shoulders of giants such as: + * [NodeMCU](https://github.com/nodemcu/nodemcu-devkit-v1.0) - The ESP8266-based hardware that this project uses. + * [esp-link](https://github.com/jeelabs/esp-link) - The firmware for this project is a modified subset of the what JeeLabs' "esp-link" project has built and made this whole thing really nice and easy to work with. + * [Espressif](http://espressif.com) - for making the amazing ESP8266 project that enables things like this to even be possible. A wonderful product from a wonderful company. + * [esphttpd](http://www.esp8266.com/wiki/doku.php?id=esp-httpd) - SpriteTM made an awesome little HTTP webserver for the ESP8266 that powers the config menu of the esp-link and the Happy Bubbles NFC device + * everyone in the ESP8266 community who shares their code and advice. + diff --git a/WINDOWS.md b/WINDOWS.md new file mode 100644 index 0000000..08e7a03 --- /dev/null +++ b/WINDOWS.md @@ -0,0 +1,14 @@ +* Install [SourceTree](https://www.sourcetreeapp.com) and check CLI git or other git distribution to obtain git from CLI +* Install the latest Java JRE +* Install Python 2.7 to C:\Python27 +* Install link shell extension from [here](http://schinagl.priv.at/nt/hardlinkshellext/linkshellextension.html) +* Download and install the [Windows Unofficial Development Kit for Espressif ESP8266](http://programs74.ru/get.php?file=EspressifESP8266DevKit) to c:\espressif +* Create a symbolic link for java/bin and git/bin directories under C:\espressif\git-bin and C:\espressif\java-bin. You must do this because "make" doesn't work properly with paths like "program files(x86)". You can see all the expected paths in the [espmake.cmd](https://github.com/jeelabs/esp-link/blob/master/espmake.cmd) +* [Download](http://sourceforge.net/projects/mingw/files/Installer/) and install MinGW. Run mingw-get-setup.exe. During the installation process select without GUI. (uncheck "... also install support for the graphical user interface") +* [Download](http://programs74.ru/get.php?file=EspressifESP8266DevKitAddon) the scripts to automate the installation of additional modules for MinGW. +* Run install-mingw-package.bat. This will install the basic modules required for MinGW to build esp8266. +* Checkout esp-link from git to C:\espressif\esp-link +* When you're done open a command prompt and run: espmake.cmd "make all wiflash" +* For a new flash over serial use: espmake.cmd "make all flash" +* If you want to program with serial but not loose your config each time use: espmake.cmd "make all baseflash" +* You can open the esp-link.sln file in Visual Studio 2013. "Build Solution" will issue "make all wiflash". "Clean Solution" will issue "make clean". "Rebuild Solution" will issue "make clean all". This can be changed under solution properties -> Configuration Properties -> NMake \ No newline at end of file diff --git a/avrflash b/avrflash new file mode 100755 index 0000000..20fe890 --- /dev/null +++ b/avrflash @@ -0,0 +1,110 @@ +#! /bin/bash +# +# Flash an AVR with optiboot using the esp-link built-in programmer +# Basically we first reset the AVR and get in sync, and then send the hex file +# +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# Thorsten von Eicken wrote this file. As long as you retain +# this notice you can do whatever you want with this stuff. If we meet some day, +# and you think this stuff is worth it, you can buy me a beer in return. +# ---------------------------------------------------------------------------- + +show_help() { + cat </dev/null; then + echo "ERROR: Cannot find curl: it is required for this script." >&2 + exit 1 +fi + +start=`date +%s` + +# ===== Parse arguments + +verbose= + +while getopts "hvx:" opt; do + case "$opt" in + h) show_help; exit 0 ;; + v) verbose=1 ;; + x) foo="$OPTARG" ;; + '?') show_help >&2; exit 1 ;; + esac +done + +# Shift off the options and optional --. +shift "$((OPTIND-1))" + +# Get the fixed arguments +if [[ $# != 2 ]]; then + show_help >&2 + exit 1 +fi +hostname=$1 +hex=$2 + +re='[-A-Za-z0-9.]+' +if [[ ! "$hostname" =~ $re ]]; then + echo "ERROR: hostname ${hostname} is not a valid hostname or ip address" >&2 + exit 1 +fi + +if [[ ! -r "$hex" ]]; then + echo "ERROR: cannot read hex file ($hex)" >&2 + exit 1 +fi + +# ===== Get AVR in sync + +[[ -n "$verbose" ]] && echo "Resetting AVR with http://$hostname/pgm/sync" >&2 +v=; [[ -n "$verbose" ]] && v=-v +sync=`curl -m 10 $v -s -w '%{http_code}' -XPOST "http://$hostname/pgm/sync"` +if [[ $? != 0 || "$sync" != 204 ]]; then + echo "Error resetting AVR" >&2 + exit 1 +fi + +while true; do + sync=`curl -m 10 $v -s "http://$hostname/pgm/sync"` + if [[ $? != 0 ]]; then + echo "Error checking sync" >&2 + exit 1 + fi + case "$sync" in + SYNC*) + echo "AVR in $sync" >&2 + break;; + "NOT READY"*) + [[ -n "$verbose" ]] && echo " Waiting for sync..." >&2 + ;; + *) + echo "Error checking sync: $sync" >&2 + exit 1 + ;; + esac + sleep 0.1 +done + +# ===== Send HEX file + +[[ -n "$verbose" ]] && echo "Sending HEX file for programming" >&2 +sync=`curl -m 10 $v -s -g -d "@$hex" "http://$hostname/pgm/upload"` +echo $sync +if [[ $? != 0 || ! "$sync" =~ ^Success ]]; then + echo "Error programming AVR" >&2 + exit 1 +fi + +sec=$(( `date +%s` - $start )) +echo "Success, took $sec seconds" >&2 +exit 0 diff --git a/debug b/debug new file mode 100644 index 0000000..39efe5d --- /dev/null +++ b/debug @@ -0,0 +1,2 @@ +../esp-open-sdk/xtensa-lx106-elf/bin/xtensa-lx106-elf-objdump -S build/httpd.user1.out > /tmp/ss +vim /tmp/ss diff --git a/esp-link.sln b/esp-link.sln new file mode 100644 index 0000000..fb7b468 --- /dev/null +++ b/esp-link.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "esp-link", "esp-link.vcxproj", "{A92F0CAA-F89B-4F78-AD2A-A042429BD87F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Release|ARM = Release|ARM + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Debug|ARM.ActiveCfg = Debug|ARM + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Debug|ARM.Build.0 = Debug|ARM + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Release|ARM.ActiveCfg = Release|ARM + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F}.Release|ARM.Build.0 = Release|ARM + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/esp-link.vcxproj b/esp-link.vcxproj new file mode 100644 index 0000000..4a1ef3a --- /dev/null +++ b/esp-link.vcxproj @@ -0,0 +1,171 @@ + + + + + Debug + ARM + + + Release + ARM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {A92F0CAA-F89B-4F78-AD2A-A042429BD87F} + MakeFileProj + + + + Makefile + + + + + + + + + + + __ets__;_STDINT_H;ICACHE_FLASH;__MINGW32__;__WIN32__;MQTT;REST;SYSLOG;FIRMWARE_SIZE + .\syslog;.\rest;.\esp-link;.\mqtt;.\cmd;.\serial;.\user;.\espfs;.\httpd;.\include;..\esp_iot_sdk_v1.5.0\include;..\xtensa-lx106-elf\xtensa-lx106-elf\include;c:\tools\mingw64\x86_64-w64-mingw32\include;c:\tools\mingw64\lib\gcc\x86_64-w64-mingw32\4.8.3\include + + + + + + + bin + build + + + espmake all wiflash + espmake clean all + espmake clean + + + espmake clean all wiflash + espmake clean all + espmake clean + + + + + + + + + + \ No newline at end of file diff --git a/esp-link/cgi.c b/esp-link/cgi.c new file mode 100644 index 0000000..882aa98 --- /dev/null +++ b/esp-link/cgi.c @@ -0,0 +1,219 @@ +/* +Some random cgi routines. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + * Heavily modified and enhanced by Thorsten von Eicken in 2015 + * ---------------------------------------------------------------------------- + */ + +#include +#include "cgi.h" +#include "config.h" + +#ifdef CGI_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + +void ICACHE_FLASH_ATTR noCacheHeaders(HttpdConnData *connData, int code) { + httpdStartResponse(connData, code); + httpdHeader(connData, "Cache-Control", "no-cache, no-store, must-revalidate"); + httpdHeader(connData, "Pragma", "no-cache"); + httpdHeader(connData, "Expires", "0"); +} + +void ICACHE_FLASH_ATTR jsonHeader(HttpdConnData *connData, int code) { + noCacheHeaders(connData, code); + httpdHeader(connData, "Content-Type", "application/json"); + httpdEndHeaders(connData); +} + +void ICACHE_FLASH_ATTR errorResponse(HttpdConnData *connData, int code, char *message) { + noCacheHeaders(connData, code); + httpdEndHeaders(connData); + httpdSend(connData, message, -1); + DBG("HTTP %d error response: \"%s\"\n", code, message); +} + +// look for the HTTP arg 'name' and store it at 'config' with max length 'max_len' (incl +// terminating zero), returns -1 on error, 0 if not found, 1 if found and OK +int8_t ICACHE_FLASH_ATTR getStringArg(HttpdConnData *connData, char *name, char *config, int max_len) { + char buff[128]; + int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); + if (len < 0) return 0; // not found, skip + if (len >= max_len) { + os_sprintf(buff, "Value for %s too long (%d > %d allowed)", name, len, max_len-1); + errorResponse(connData, 400, buff); + return -1; + } + os_strcpy(config, buff); + return 1; +} + +// look for the HTTP arg 'name' and store it at 'config' as an 8-bit integer +// returns -1 on error, 0 if not found, 1 if found and OK +int8_t ICACHE_FLASH_ATTR getInt8Arg(HttpdConnData *connData, char *name, int8_t *config) { + char buff[16]; + int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); + if (len < 0) return 0; // not found, skip + int m = atoi(buff); + if (len > 5 || m < -127 || m > 127) { + os_sprintf(buff, "Value for %s out of range", name); + errorResponse(connData, 400, buff); + return -1; + } + *config = m; + return 1; +} + +// look for the HTTP arg 'name' and store it at 'config' as an unsigned 8-bit integer +// returns -1 on error, 0 if not found, 1 if found and OK +int8_t ICACHE_FLASH_ATTR getUInt8Arg(HttpdConnData *connData, char *name, uint8_t *config) { + char buff[16]; + int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); + if (len < 0) return 0; // not found, skip + int m = atoi(buff); + if (len > 4 || m < 0 || m > 255) { + os_sprintf(buff, "Value for %s out of range", name); + errorResponse(connData, 400, buff); + return -1; + } + *config = m; + return 1; +} + +// look for the HTTP arg 'name' and store it at 'config' as an unsigned 16-bit integer +// returns -1 on error, 0 if not found, 1 if found and OK +int8_t ICACHE_FLASH_ATTR getUInt16Arg(HttpdConnData *connData, char *name, uint16_t *config) { + char buff[16]; + int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); + if (len < 0) return 0; // not found, skip + int m = atoi(buff); + if (len > 6 || m < 0 || m > 65535) { + os_sprintf(buff, "Value for %s out of range", name); + errorResponse(connData, 400, buff); + return -1; + } + *config = m; + return 1; +} + +int8_t ICACHE_FLASH_ATTR getBoolArg(HttpdConnData *connData, char *name, bool *config) { + char buff[16]; + int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); + if (len < 0) return 0; // not found, skip + + if (os_strcmp(buff, "1") == 0 || os_strcmp(buff, "true") == 0) { + *config = true; + return 1; + } + + if (os_strcmp(buff, "0") == 0 || os_strcmp(buff, "false") == 0) { + *config = false; + return 1; + } + + os_sprintf(buff, "Invalid value for %s", name); + errorResponse(connData, 400, buff); + return -1; +} + +uint8_t ICACHE_FLASH_ATTR UTILS_StrToIP(const char* str, void *ip){ + /* The count of the number of bytes processed. */ + int i; + /* A pointer to the next digit to process. */ + const char * start; + + start = str; + for (i = 0; i < 4; i++) { + /* The digit being processed. */ + char c; + /* The value of this byte. */ + int n = 0; + while (1) { + c = *start; + start++; + if (c >= '0' && c <= '9') { + n *= 10; + n += c - '0'; + } + /* We insist on stopping at "." if we are still parsing + the first, second, or third numbers. If we have reached + the end of the numbers, we will allow any character. */ + else if ((i < 3 && c == '.') || i == 3) { + break; + } + else { + return 0; + } + } + if (n >= 256) { + return 0; + } + ((uint8_t*)ip)[i] = n; + } + return 1; +} + +#if 0 +#define TOKEN(x) (os_strcmp(token, x) == 0) +// Handle system information variables and print their value, returns the number of +// characters appended to buff +int ICACHE_FLASH_ATTR printGlobalInfo(char *buff, int buflen, char *token) { + if (TOKEN("si_chip_id")) { + return os_sprintf(buff, "0x%x", system_get_chip_id()); + } else if (TOKEN("si_freeheap")) { + return os_sprintf(buff, "%dKB", system_get_free_heap_size()/1024); + } else if (TOKEN("si_uptime")) { + uint32 t = system_get_time() / 1000000; // in seconds + return os_sprintf(buff, "%dd%dh%dm%ds", t/(24*3600), (t/(3600))%24, (t/60)%60, t%60); + } else if (TOKEN("si_boot_version")) { + return os_sprintf(buff, "%d", system_get_boot_version()); + } else if (TOKEN("si_boot_address")) { + return os_sprintf(buff, "0x%x", system_get_userbin_addr()); + } else if (TOKEN("si_cpu_freq")) { + return os_sprintf(buff, "%dMhz", system_get_cpu_freq()); + } else { + return 0; + } +} +#endif + +extern char *esp_link_version; // in user_main.c + +int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + char buff[1024]; + // don't use jsonHeader so the response does get cached + httpdStartResponse(connData, 200); + httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); + httpdHeader(connData, "Content-Type", "application/json"); + httpdEndHeaders(connData); + // limit hostname to 12 chars + char name[13]; + os_strncpy(name, flashConfig.hostname, 12); + name[12] = 0; + // construct json response + os_sprintf(buff, + "{ " + "\"menu\": [ " + "\"Home\", \"/home.html\", " + "\"WiFi Setup\", \"/wifi/wifiSta.html\", " + "\"NFC Setup\", \"/nfc.html\"" + " ], " + "\"version\": \"%s\", " + "\"name\": \"%s\"" + " }", + esp_link_version, name); + + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} diff --git a/esp-link/cgi.h b/esp-link/cgi.h new file mode 100644 index 0000000..cb897b0 --- /dev/null +++ b/esp-link/cgi.h @@ -0,0 +1,35 @@ +#ifndef CGI_H +#define CGI_H + +#include +#include "httpd.h" + +void noCacheHeaders(HttpdConnData *connData, int code); +void jsonHeader(HttpdConnData *connData, int code); +void errorResponse(HttpdConnData *connData, int code, char *message); + +// Get the HTTP query-string param 'name' and store it at 'config' with max length +// 'max_len' (incl terminating zero), returns -1 on error, 0 if not found, 1 if found +int8_t getStringArg(HttpdConnData *connData, char *name, char *config, int max_len); + +// Get the HTTP query-string param 'name' and store it as a int8_t value at 'config', +// supports signed and unsigned, returns -1 on error, 0 if not found, 1 if found +int8_t getInt8Arg(HttpdConnData *connData, char *name, int8_t *config); + +// Get the HTTP query-string param 'name' and store it as a uint8_t value at 'config', +// supports signed and unsigned, returns -1 on error, 0 if not found, 1 if found +int8_t getUInt8Arg(HttpdConnData *connData, char *name, uint8_t *config); + +// Get the HTTP query-string param 'name' and store it as a uint16_t value at 'config', +// supports signed and unsigned, returns -1 on error, 0 if not found, 1 if found +int8_t getUInt16Arg(HttpdConnData *connData, char *name, uint16_t *config); + +// Get the HTTP query-string param 'name' and store it boolean value at 'config', +// supports 1/true and 0/false, returns -1 on error, 0 if not found, 1 if found +int8_t getBoolArg(HttpdConnData *connData, char *name, bool *config); + +int cgiMenu(HttpdConnData *connData); + +uint8_t UTILS_StrToIP(const char *str, void *ip); + +#endif diff --git a/esp-link/cgiflash.c b/esp-link/cgiflash.c new file mode 100644 index 0000000..8c39e36 --- /dev/null +++ b/esp-link/cgiflash.c @@ -0,0 +1,200 @@ +/* +Some flash handling cgi routines. Used for reading the existing flash and updating the ESPFS image. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + * Heavily modified and enhanced by Thorsten von Eicken in 2015 + * ---------------------------------------------------------------------------- + */ + + +#include +#include +#include "cgi.h" +#include "cgiflash.h" + +#ifdef CGIFLASH_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + +// Check that the header of the firmware blob looks like actual firmware... +static char* ICACHE_FLASH_ATTR check_header(void *buf) { + uint8_t *cd = (uint8_t *)buf; +#ifdef CGIFLASH_DBG + uint32_t *buf32 = buf; + os_printf("%p: %08lX %08lX %08lX %08lX\n", buf, buf32[0], buf32[1], buf32[2], buf32[3]); +#endif + if (cd[0] != 0xEA) return "IROM magic missing"; + if (cd[1] != 4 || cd[2] > 3 || (cd[3]>>4) > 6) return "bad flash header"; + if (((uint16_t *)buf)[3] != 0x4010) return "Invalid entry addr"; + if (((uint32_t *)buf)[2] != 0) return "Invalid start offset"; + return NULL; +} + +// check whether the flash map/size we have allows for OTA upgrade +static bool canOTA(void) { + enum flash_size_map map = system_get_flash_size_map(); + return map >= FLASH_SIZE_8M_MAP_512_512; +} + +static char *flash_too_small = "Flash too small for OTA update"; + +//===== Cgi to query which firmware needs to be uploaded next +int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + if (!canOTA()) { + errorResponse(connData, 400, flash_too_small); + return HTTPD_CGI_DONE; + } + + uint8 id = system_upgrade_userbin_check(); + httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Type", "text/plain"); + httpdHeader(connData, "Content-Length", "9"); + httpdEndHeaders(connData); + char *next = id == 1 ? "user1.bin" : "user2.bin"; + httpdSend(connData, next, -1); + DBG("Next firmware: %s (got %d)\n", next, id); + return HTTPD_CGI_DONE; +} + +//===== Cgi that allows the firmware to be replaced via http POST +int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + if (!canOTA()) { + errorResponse(connData, 400, flash_too_small); + return HTTPD_CGI_DONE; + } + + int offset = connData->post->received - connData->post->buffLen; + if (offset == 0) { + connData->cgiPrivData = NULL; + } else if (connData->cgiPrivData != NULL) { + // we have an error condition, do nothing + return HTTPD_CGI_DONE; + } + + // assume no error yet... + char *err = NULL; + int code = 400; + + // check overall size + //os_printf("FW: %d (max %d)\n", connData->post->len, FIRMWARE_SIZE); + if (connData->post->len > FIRMWARE_SIZE) err = "Firmware image too large"; + if (connData->post->buff == NULL || connData->requestType != HTTPD_METHOD_POST || + connData->post->len < 1024) err = "Invalid request"; + + // check that data starts with an appropriate header + if (err == NULL && offset == 0) err = check_header(connData->post->buff); + + // make sure we're buffering in 1024 byte chunks + if (err == NULL && offset % 1024 != 0) { + err = "Buffering problem"; + code = 500; + } + + // return an error if there is one + if (err != NULL) { + DBG("Error %d: %s\n", code, err); + httpdStartResponse(connData, code); + httpdHeader(connData, "Content-Type", "text/plain"); + //httpdHeader(connData, "Content-Length", strlen(err)+2); + httpdEndHeaders(connData); + httpdSend(connData, err, -1); + httpdSend(connData, "\r\n", -1); + connData->cgiPrivData = (void *)1; + return HTTPD_CGI_DONE; + } + + // let's see which partition we need to flash and what flash address that puts us at + uint8 id = system_upgrade_userbin_check(); + int address = id == 1 ? 4*1024 // either start after 4KB boot partition + : 4*1024 + FIRMWARE_SIZE + 16*1024 + 4*1024; // 4KB boot, fw1, 16KB user param, 4KB reserved + address += offset; + + // erase next flash block if necessary + if (address % SPI_FLASH_SEC_SIZE == 0){ + DBG("Flashing 0x%05x (id=%d)\n", address, 2 - id); + spi_flash_erase_sector(address/SPI_FLASH_SEC_SIZE); + } + + // Write the data + //DBG("Writing %d bytes at 0x%05x (%d of %d)\n", connData->post->buffSize, address, + // connData->post->received, connData->post->len); + spi_flash_write(address, (uint32 *)connData->post->buff, connData->post->buffLen); + + if (connData->post->received == connData->post->len){ + httpdStartResponse(connData, 200); + httpdEndHeaders(connData); + return HTTPD_CGI_DONE; + } else { + return HTTPD_CGI_MORE; + } +} + +static ETSTimer flash_reboot_timer; + +// Handle request to reboot into the new firmware +int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + if (!canOTA()) { + errorResponse(connData, 400, flash_too_small); + return HTTPD_CGI_DONE; + } + + // sanity-check that the 'next' partition actually contains something that looks like + // valid firmware + uint8 id = system_upgrade_userbin_check(); + int address = id == 1 ? 4*1024 // either start after 4KB boot partition + : 4*1024 + FIRMWARE_SIZE + 16*1024 + 4*1024; // 4KB boot, fw1, 16KB user param, 4KB reserved + uint32 buf[8]; + DBG("Checking %p\n", (void *)address); + spi_flash_read(address, buf, sizeof(buf)); + char *err = check_header(buf); + if (err != NULL) { + DBG("Error %d: %s\n", 400, err); + httpdStartResponse(connData, 400); + httpdHeader(connData, "Content-Type", "text/plain"); + //httpdHeader(connData, "Content-Length", strlen(err)+2); + httpdEndHeaders(connData); + httpdSend(connData, err, -1); + httpdSend(connData, "\r\n", -1); + return HTTPD_CGI_DONE; + } + + httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Length", "0"); + httpdEndHeaders(connData); + + // Schedule a reboot + system_upgrade_flag_set(UPGRADE_FLAG_FINISH); + os_timer_disarm(&flash_reboot_timer); + os_timer_setfn(&flash_reboot_timer, (os_timer_func_t *)system_upgrade_reboot, NULL); + os_timer_arm(&flash_reboot_timer, 2000, 1); + return HTTPD_CGI_DONE; +} + +int ICACHE_FLASH_ATTR cgiReset(HttpdConnData *connData) { + if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Length", "0"); + httpdEndHeaders(connData); + + // Schedule a reboot + os_timer_disarm(&flash_reboot_timer); + os_timer_setfn(&flash_reboot_timer, (os_timer_func_t *)system_restart, NULL); + os_timer_arm(&flash_reboot_timer, 2000, 1); + return HTTPD_CGI_DONE; +} \ No newline at end of file diff --git a/esp-link/cgiflash.h b/esp-link/cgiflash.h new file mode 100644 index 0000000..f03b8e0 --- /dev/null +++ b/esp-link/cgiflash.h @@ -0,0 +1,12 @@ +#ifndef CGIFLASH_H +#define CGIFLASH_H + +#include "httpd.h" + +int cgiReadFlash(HttpdConnData *connData); +int cgiGetFirmwareNext(HttpdConnData *connData); +int cgiUploadFirmware(HttpdConnData *connData); +int cgiRebootFirmware(HttpdConnData *connData); +int cgiReset(HttpdConnData *connData); + +#endif diff --git a/esp-link/cginfc.c b/esp-link/cginfc.c new file mode 100644 index 0000000..5e23b43 --- /dev/null +++ b/esp-link/cginfc.c @@ -0,0 +1,88 @@ +// Copyright 2016 by nemik, see LICENSE.txt + +#include +#include "cgi.h" +#include "config.h" +#include "cginfc.h" + +#ifdef CGINFC_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + +// Cgi to return NFC settings +int ICACHE_FLASH_ATTR cgiNFCGet(HttpdConnData *connData) { + char buff[1024]; + int len; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + len = os_sprintf(buff, "{ " + "\"nfc-url\":\"%s\", " + "\"nfc-device-id\":\"%s\", " + "\"nfc-device-secret\":\"%s\", " + "\"nfc-counter\":%d }", + flashConfig.nfc_url, + flashConfig.nfc_device_id, + flashConfig.nfc_device_secret, + flashConfig.nfc_counter); + + jsonHeader(connData, 200); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +int ICACHE_FLASH_ATTR cgiNFCSet(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + // handle NFC server settings + int8_t nfc_server = 0; // accumulator for changes/errors + nfc_server |= getStringArg(connData, "nfc-url", + flashConfig.nfc_url, sizeof(flashConfig.nfc_url)); + if (nfc_server < 0) return HTTPD_CGI_DONE; + nfc_server |= getStringArg(connData, "nfc-device-id", + flashConfig.nfc_device_id, sizeof(flashConfig.nfc_device_id)); + + if (nfc_server < 0) return HTTPD_CGI_DONE; + nfc_server |= getStringArg(connData, "nfc-device-secret", + flashConfig.nfc_device_secret, sizeof(flashConfig.nfc_device_secret)); + if (nfc_server < 0) return HTTPD_CGI_DONE; + + char buff[16]; + + // handle nfc counter + if (httpdFindArg(connData->getArgs, "nfc-counter", buff, sizeof(buff)) > 0) { + uint32_t counter = atoi(buff); + if (counter > 0 && counter < 4294967295) { + flashConfig.nfc_counter = counter; + nfc_server |= 1; + } else { + errorResponse(connData, 400, "Invalid NFC counter!"); + return HTTPD_CGI_DONE; + } + } + + DBG("Saving config\n"); + + if (configSave()) { + httpdStartResponse(connData, 200); + httpdEndHeaders(connData); + } else { + httpdStartResponse(connData, 500); + httpdEndHeaders(connData); + httpdSend(connData, "Failed to save config", -1); + } + return HTTPD_CGI_DONE; +} + +int ICACHE_FLASH_ATTR cgiNFC(HttpdConnData *connData) { + if (connData->requestType == HTTPD_METHOD_GET) { + return cgiNFCGet(connData); + } else if (connData->requestType == HTTPD_METHOD_POST) { + return cgiNFCSet(connData); + } else { + jsonHeader(connData, 404); + return HTTPD_CGI_DONE; + } +} diff --git a/esp-link/cginfc.h b/esp-link/cginfc.h new file mode 100644 index 0000000..4987e1c --- /dev/null +++ b/esp-link/cginfc.h @@ -0,0 +1,7 @@ +#ifndef CGINFC_H +#define CGINFC_H + +#include "httpd.h" +int cgiNFC(HttpdConnData *connData); + +#endif // CGINFC_H diff --git a/esp-link/cgiservices.c b/esp-link/cgiservices.c new file mode 100644 index 0000000..847a3f3 --- /dev/null +++ b/esp-link/cgiservices.c @@ -0,0 +1,182 @@ +#include +#include "cgiwifi.h" +#include "cgi.h" +#include "config.h" +#include "sntp.h" + +#ifdef CGISERVICES_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + +char* rst_codes[7] = { + "normal", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "external", +}; + +char* flash_maps[7] = { + "512KB:256/256", "256KB", "1MB:512/512", "2MB:512/512", "4MB:512/512", + "2MB:1024/1024", "4MB:1024/1024" +}; + +static ETSTimer reassTimer; + +// Cgi to update system info (name/description) +int ICACHE_FLASH_ATTR cgiSystemSet(HttpdConnData *connData) { + if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + int8_t n = getStringArg(connData, "name", flashConfig.hostname, sizeof(flashConfig.hostname)); + int8_t d = getStringArg(connData, "description", flashConfig.sys_descr, sizeof(flashConfig.sys_descr)); + + if (n < 0 || d < 0) return HTTPD_CGI_DONE; // getStringArg has produced an error response + + if (n > 0) { + // schedule hostname change-over + os_timer_disarm(&reassTimer); + os_timer_setfn(&reassTimer, configWifiIP, NULL); + os_timer_arm(&reassTimer, 1000, 0); // 1 second for the response of this request to make it + } + + if (configSave()) { + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + } + else { + httpdStartResponse(connData, 500); + httpdEndHeaders(connData); + httpdSend(connData, "Failed to save config", -1); + } + return HTTPD_CGI_DONE; +} + +// Cgi to return various System information +int ICACHE_FLASH_ATTR cgiSystemInfo(HttpdConnData *connData) { + char buff[1024]; + + if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + uint8 part_id = system_upgrade_userbin_check(); + uint32_t fid = spi_flash_get_id(); + struct rst_info *rst_info = system_get_rst_info(); + + os_sprintf(buff, + "{ " + "\"name\": \"%s\", " + "\"reset cause\": \"%d=%s\", " + "\"size\": \"%s\", " + "\"id\": \"0x%02lX 0x%04lX\", " + "\"partition\": \"%s\", " + "\"baud\": \"%ld\", " + "\"description\": \"%s\"" + " }", + flashConfig.hostname, + rst_info->reason, + rst_codes[rst_info->reason], + flash_maps[system_get_flash_size_map()], + fid & 0xff, (fid & 0xff00) | ((fid >> 16) & 0xff), + part_id ? "user2.bin" : "user1.bin", + flashConfig.baud_rate, + flashConfig.sys_descr + ); + + jsonHeader(connData, 200); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} + +void ICACHE_FLASH_ATTR cgiServicesSNTPInit() { + if (flashConfig.sntp_server[0] != '\0') { + sntp_stop(); + if (true == sntp_set_timezone(flashConfig.timezone_offset)) { + sntp_setservername(0, flashConfig.sntp_server); + sntp_init(); + } + DBG("SNTP timesource set to %s with offset %d\n", flashConfig.sntp_server, flashConfig.timezone_offset); + } +} + +int ICACHE_FLASH_ATTR cgiServicesInfo(HttpdConnData *connData) { + char buff[1024]; + + if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + os_sprintf(buff, + "{ " + "\"timezone_offset\": %d, " + "\"sntp_server\": \"%s\", " + "\"mdns_enable\": \"%s\", " + "\"mdns_servername\": \"%s\"" + " }", + flashConfig.timezone_offset, + flashConfig.sntp_server, + flashConfig.mdns_enable ? "enabled" : "disabled", + flashConfig.mdns_servername + ); + + jsonHeader(connData, 200); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} + +int ICACHE_FLASH_ATTR cgiServicesSet(HttpdConnData *connData) { + if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + int8_t sntp = 0; + sntp |= getInt8Arg(connData, "timezone_offset", &flashConfig.timezone_offset); + if (sntp < 0) return HTTPD_CGI_DONE; + sntp |= getStringArg(connData, "sntp_server", flashConfig.sntp_server, sizeof(flashConfig.sntp_server)); + if (sntp < 0) return HTTPD_CGI_DONE; + + if (sntp > 0) { + cgiServicesSNTPInit(); + } + + int8_t mdns = 0; + mdns |= getBoolArg(connData, "mdns_enable", &flashConfig.mdns_enable); + if (mdns < 0) return HTTPD_CGI_DONE; + + if (mdns > 0) { + if (flashConfig.mdns_enable){ + DBG("Services: MDNS Enabled\n"); + struct ip_info ipconfig; + wifi_get_ip_info(STATION_IF, &ipconfig); + + if (wifiState == wifiGotIP && ipconfig.ip.addr != 0) { + wifiStartMDNS(ipconfig.ip); + } + } + else { + DBG("Services: MDNS Disabled\n"); + espconn_mdns_server_unregister(); + espconn_mdns_close(); + mdns_started = true; + } + } + else { + mdns |= getStringArg(connData, "mdns_servername", flashConfig.mdns_servername, sizeof(flashConfig.mdns_servername)); + if (mdns < 0) return HTTPD_CGI_DONE; + + if (mdns > 0 && mdns_started) { + DBG("Services: MDNS Servername Updated\n"); + espconn_mdns_server_unregister(); + espconn_mdns_close(); + struct ip_info ipconfig; + wifi_get_ip_info(STATION_IF, &ipconfig); + + if (wifiState == wifiGotIP && ipconfig.ip.addr != 0) { + wifiStartMDNS(ipconfig.ip); + } + } + } + + if (configSave()) { + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + } + else { + httpdStartResponse(connData, 500); + httpdEndHeaders(connData); + httpdSend(connData, "Failed to save config", -1); + } + return HTTPD_CGI_DONE; +} diff --git a/esp-link/cgiservices.h b/esp-link/cgiservices.h new file mode 100644 index 0000000..9c44242 --- /dev/null +++ b/esp-link/cgiservices.h @@ -0,0 +1,16 @@ +#ifndef CGISERVICES_H +#define CGISERVICES_H + +#include "httpd.h" + +int cgiSystemSet(HttpdConnData *connData); +int cgiSystemInfo(HttpdConnData *connData); + +void cgiServicesSNTPInit(); +int cgiServicesInfo(HttpdConnData *connData); +int cgiServicesSet(HttpdConnData *connData); + +extern char* rst_codes[7]; +extern char* flash_maps[7]; + +#endif // CGISERVICES_H diff --git a/esp-link/cgitcp.c b/esp-link/cgitcp.c new file mode 100644 index 0000000..f8d9036 --- /dev/null +++ b/esp-link/cgitcp.c @@ -0,0 +1,74 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// // TCP Client settings + +#include +#include "cgi.h" +#include "config.h" +#include "cgitcp.h" + +// Cgi to return TCP client settings +int ICACHE_FLASH_ATTR cgiTcpGet(HttpdConnData *connData) { + char buff[1024]; + int len; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + len = os_sprintf(buff, "{ \"tcp_enable\":%d, \"rssi_enable\": %d, \"api_key\":\"%s\" }", + flashConfig.tcp_enable, flashConfig.rssi_enable, flashConfig.api_key); + + jsonHeader(connData, 200); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +// Cgi to change choice of pin assignments +int ICACHE_FLASH_ATTR cgiTcpSet(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + // Handle tcp_enable flag + char buff[128]; + int len = httpdFindArg(connData->getArgs, "tcp_enable", buff, sizeof(buff)); + if (len <= 0) { + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + } + flashConfig.tcp_enable = os_strcmp(buff, "true") == 0; + + // Handle rssi_enable flag + len = httpdFindArg(connData->getArgs, "rssi_enable", buff, sizeof(buff)); + if (len <= 0) { + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + } + flashConfig.rssi_enable = os_strcmp(buff, "true") == 0; + + // Handle api_key flag + len = httpdFindArg(connData->getArgs, "api_key", buff, sizeof(buff)); + if (len < 0) { + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + } + buff[sizeof(flashConfig.api_key)-1] = 0; // ensure we don't get an overrun + os_strcpy(flashConfig.api_key, buff); + + if (configSave()) { + httpdStartResponse(connData, 200); + httpdEndHeaders(connData); + } else { + httpdStartResponse(connData, 500); + httpdEndHeaders(connData); + httpdSend(connData, "Failed to save config", -1); + } + return HTTPD_CGI_DONE; +} + +int ICACHE_FLASH_ATTR cgiTcp(HttpdConnData *connData) { + if (connData->requestType == HTTPD_METHOD_GET) { + return cgiTcpGet(connData); + } else if (connData->requestType == HTTPD_METHOD_POST) { + return cgiTcpSet(connData); + } else { + jsonHeader(connData, 404); + return HTTPD_CGI_DONE; + } +} diff --git a/esp-link/cgitcp.h b/esp-link/cgitcp.h new file mode 100644 index 0000000..2b7b2a9 --- /dev/null +++ b/esp-link/cgitcp.h @@ -0,0 +1,8 @@ +#ifndef CGITCP_H +#define CGITCP_H + +#include "httpd.h" + +int cgiTcp(HttpdConnData *connData); + +#endif diff --git a/esp-link/cgiwifi.c b/esp-link/cgiwifi.c new file mode 100755 index 0000000..85c06f8 --- /dev/null +++ b/esp-link/cgiwifi.c @@ -0,0 +1,922 @@ +/* +Cgi/template routines for the /wifi url. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + * Heavily modified and enhanced by Thorsten von Eicken in 2015 + * ---------------------------------------------------------------------------- + */ + +#include +#include "cgiwifi.h" +#include "cgi.h" +#include "config.h" +#include "log.h" + +#ifdef CGIWIFI_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + +# define VERS_STR_STR(V) #V +# define VERS_STR(V) VERS_STR_STR(V) + +bool mdns_started = false; + +// ===== wifi status change callbacks +static WifiStateChangeCb wifi_state_change_cb[4]; + +// Temp store for new staion config +struct station_config stconf; + +// Temp store for new ap config +struct softap_config apconf; + +uint8_t wifiState = wifiIsDisconnected; +// reasons for which a connection failed +uint8_t wifiReason = 0; +static char *wifiReasons[] = { + "", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", + "not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", + "ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", + "ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", + "unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", + "beacon_timeout", "no_ap_found" }; + +static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; +static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; + +void (*wifiStatusCb)(uint8_t); // callback when wifi status changes + +static char* ICACHE_FLASH_ATTR wifiGetReason(void) { + if (wifiReason <= 24) return wifiReasons[wifiReason]; + if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; + return wifiReasons[1]; +} + +// handler for wifi status change callback coming in from espressif library +static void ICACHE_FLASH_ATTR wifiHandleEventCb(System_Event_t *evt) { + switch (evt->event) { + case EVENT_STAMODE_CONNECTED: + wifiState = wifiIsConnected; + wifiReason = 0; + DBG("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, + evt->event_info.connected.channel); + break; + case EVENT_STAMODE_DISCONNECTED: + wifiState = wifiIsDisconnected; + wifiReason = evt->event_info.disconnected.reason; + DBG("Wifi disconnected from ssid %s, reason %s (%d)\n", + evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); + break; + case EVENT_STAMODE_AUTHMODE_CHANGE: + DBG("Wifi auth mode: %d -> %d\n", + evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); + break; + case EVENT_STAMODE_GOT_IP: + wifiState = wifiGotIP; + wifiReason = 0; + DBG("Wifi got ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR "\n", + IP2STR(&evt->event_info.got_ip.ip), IP2STR(&evt->event_info.got_ip.mask), + IP2STR(&evt->event_info.got_ip.gw)); + if (!mdns_started) + wifiStartMDNS(evt->event_info.got_ip.ip); + break; + case EVENT_SOFTAPMODE_STACONNECTED: + DBG("Wifi AP: station " MACSTR " joined, AID = %d\n", + MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); + break; + case EVENT_SOFTAPMODE_STADISCONNECTED: + DBG("Wifi AP: station " MACSTR " left, AID = %d\n", + MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); + break; + default: + break; + } + + for (int i = 0; i < 4; i++) { + if (wifi_state_change_cb[i] != NULL) (wifi_state_change_cb[i])(wifiState); + } +} + +void ICACHE_FLASH_ATTR wifiAddStateChangeCb(WifiStateChangeCb cb) { + for (int i = 0; i < 4; i++) { + if (wifi_state_change_cb[i] == cb) return; + if (wifi_state_change_cb[i] == NULL) { + wifi_state_change_cb[i] = cb; + return; + } + } + DBG("WIFI: max state change cb count exceeded\n"); +} + +void ICACHE_FLASH_ATTR wifiStartMDNS(struct ip_addr ip) { + if (flashConfig.mdns_enable) { + struct mdns_info *mdns_info = (struct mdns_info *)os_zalloc(sizeof(struct mdns_info)); + mdns_info->host_name = flashConfig.hostname; + mdns_info->server_name = flashConfig.mdns_servername; + mdns_info->server_port = 80; + mdns_info->ipAddr = ip.addr; + espconn_mdns_init(mdns_info); + } + else { + espconn_mdns_server_unregister(); + espconn_mdns_close(); + } + mdns_started = true; +} + +// ===== wifi scanning + +//WiFi access point data +typedef struct { + char ssid[32]; + sint8 rssi; + char enc; +} ApData; + +//Scan result +typedef struct { + char scanInProgress; //if 1, don't access the underlying stuff from the webpage. + ApData **apData; + int noAps; +} ScanResultData; + +//Static scan status storage. +static ScanResultData cgiWifiAps; + +//Callback the code calls when a wlan ap scan is done. Basically stores the result in +//the cgiWifiAps struct. +void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { + int n; + struct bss_info *bss_link = (struct bss_info *)arg; + + if (status!=OK) { + DBG("wifiScanDoneCb status=%d\n", status); + cgiWifiAps.scanInProgress=0; + return; + } + + //Clear prev ap data if needed. + if (cgiWifiAps.apData!=NULL) { + for (n=0; nnext.stqe_next; + n++; + } + //Allocate memory for access point data + cgiWifiAps.apData=(ApData **)os_malloc(sizeof(ApData *)*n); + cgiWifiAps.noAps=n; + DBG("Scan done: found %d APs\n", n); + + //Copy access point data to the static struct + n=0; + bss_link = (struct bss_info *)arg; + while (bss_link != NULL) { + if (n>=cgiWifiAps.noAps) { + //This means the bss_link changed under our nose. Shouldn't happen! + //Break because otherwise we will write in unallocated memory. + DBG("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); + break; + } + //Save the ap data. + cgiWifiAps.apData[n]=(ApData *)os_malloc(sizeof(ApData)); + cgiWifiAps.apData[n]->rssi=bss_link->rssi; + cgiWifiAps.apData[n]->enc=bss_link->authmode; + strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); + DBG("bss%d: %s (%d)\n", n+1, (char*)bss_link->ssid, bss_link->rssi); + + bss_link = bss_link->next.stqe_next; + n++; + } + //We're done. + cgiWifiAps.scanInProgress=0; +} + +static ETSTimer scanTimer; +static void ICACHE_FLASH_ATTR scanStartCb(void *arg) { + DBG("Starting a scan\n"); + wifi_station_scan(NULL, wifiScanDoneCb); +} + +static int ICACHE_FLASH_ATTR cgiWiFiStartScan(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + jsonHeader(connData, 200); + if (!cgiWifiAps.scanInProgress) { + cgiWifiAps.scanInProgress = 1; + os_timer_disarm(&scanTimer); + os_timer_setfn(&scanTimer, scanStartCb, NULL); + os_timer_arm(&scanTimer, 200, 0); + } + return HTTPD_CGI_DONE; +} + +static int ICACHE_FLASH_ATTR cgiWiFiGetScan(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + char buff[1460]; + const int chunk = 1460/64; // ssid is up to 32 chars + int len = 0; + + os_printf("GET scan: cgiData=%d noAps=%d\n", (int)connData->cgiData, cgiWifiAps.noAps); + + // handle continuation call, connData->cgiData-1 is the position in the scan results where we + // we need to continue sending from (using -1 'cause 0 means it's the first call) + if (connData->cgiData) { + int next = (int)connData->cgiData-1; + int pos = next; + while (pos < cgiWifiAps.noAps && pos < next+chunk) { + len += os_sprintf(buff+len, "{\"essid\": \"%s\", \"rssi\": %d, \"enc\": \"%d\"}%c\n", + cgiWifiAps.apData[pos]->ssid, cgiWifiAps.apData[pos]->rssi, cgiWifiAps.apData[pos]->enc, + (pos+1 == cgiWifiAps.noAps) ? ' ' : ','); + pos++; + } + // done or more? + if (pos == cgiWifiAps.noAps) { + len += os_sprintf(buff+len, "]}}\n"); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; + } else { + connData->cgiData = (void*)(pos+1); + httpdSend(connData, buff, len); + return HTTPD_CGI_MORE; + } + } + + jsonHeader(connData, 200); + + if (cgiWifiAps.scanInProgress==1) { + //We're still scanning. Tell Javascript code that. + len = os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n"); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; + } + + len = os_sprintf(buff, "{\"result\": {\"inProgress\": \"0\", \"APs\": [\n"); + connData->cgiData = (void *)1; // start with first result next time we're called + httpdSend(connData, buff, len); + return HTTPD_CGI_MORE; +} + +int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { + if (connData->requestType == HTTPD_METHOD_GET) { + return cgiWiFiGetScan(connData); + }else if(connData->requestType == HTTPD_METHOD_POST) { + // DO NOT start APs scan in AP mode + int mode = wifi_get_opmode(); + if(mode==2){ + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + }else{ + return cgiWiFiStartScan(connData); + } + }else{ + jsonHeader(connData, 404); + return HTTPD_CGI_DONE; + } +} + +// ===== timers to change state and rescue from failed associations + +// reset timer changes back to STA+AP if we can't associate +#define RESET_TIMEOUT (15000) // 15 seconds +static ETSTimer resetTimer; + +// This routine is ran some time after a connection attempt to an access point. If +// the connect succeeds, this gets the module in STA-only mode. If it fails, it ensures +// that the module is in STA+AP mode so the user has a chance to recover. +static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { + int x = wifi_station_get_connect_status(); + int m = wifi_get_opmode() & 0x3; + DBG("Wifi check: mode=%s status=%d\n", wifiMode[m], x); + + if(m!=2){ + if ( x == STATION_GOT_IP ) { + if (m != 1) { +#ifdef CHANGE_TO_STA + // We're happily connected, go to STA mode + DBG("Wifi got IP. Going into STA mode..\n"); + wifi_set_opmode(1); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); // check one more time after switching to STA-only +#endif + } + log_uart(false); + // no more resetTimer at this point, gotta use physical reset to recover if in trouble + } else { + if (m != 3) { + DBG("Wifi connect failed. Going into STA+AP mode..\n"); + wifi_set_opmode(3); + wifi_softap_set_config(&apconf); + } + log_uart(true); + DBG("Enabling/continuing uart log\n"); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); + } + } +} + +// Reassociate timer to delay change of association so the original request can finish +static ETSTimer reassTimer; + +// Callback actually doing reassociation +static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { + DBG("Wifi changing association\n"); + wifi_station_disconnect(); + stconf.bssid_set = 0; + wifi_station_set_config(&stconf); + wifi_station_connect(); + // Schedule check, we give some extra time (4x) 'cause the reassociation can cause the AP + // to have to change channel, and then the client needs to follow before it can see the + // IP address + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, 4*RESET_TIMEOUT, 0); +} + +// This cgi uses the routines above to connect to a specific access point with the +// given ESSID using the given password. +int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { + int mode = wifi_get_opmode(); + if(mode == 2){ + jsonHeader(connData, 400); + httpdSend(connData, "Can't associate to an AP en SoftAP mode", -1); + return HTTPD_CGI_DONE; + } + char essid[128]; + char passwd[128]; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + int el = httpdFindArg(connData->getArgs, "essid", essid, sizeof(essid)); + int pl = httpdFindArg(connData->getArgs, "passwd", passwd, sizeof(passwd)); + + if (el > 0 && pl >= 0) { + //Set to 0 if you want to disable the actual reconnecting bit + os_strncpy((char*)stconf.ssid, essid, 32); + os_strncpy((char*)stconf.password, passwd, 64); + DBG("Wifi try to connect to AP %s pw %s\n", essid, passwd); + + //Schedule disconnect/connect + os_timer_disarm(&reassTimer); + os_timer_setfn(&reassTimer, reassTimerCb, NULL); + os_timer_arm(&reassTimer, 1000, 0); // 1 second for the response of this request to make it + jsonHeader(connData, 200); + } else { + jsonHeader(connData, 400); + httpdSend(connData, "Cannot parse ssid or password", -1); + } + return HTTPD_CGI_DONE; +} + +static bool ICACHE_FLASH_ATTR parse_ip(char *buff, ip_addr_t *ip_ptr) { + char *next = buff; // where to start parsing next integer + int found = 0; // number of integers parsed + uint32_t ip = 0; // the ip addres parsed + for (int i=0; i<32; i++) { // 32 is just a safety limit + char c = buff[i]; + if (c == '.' || c == 0) { + // parse the preceding integer and accumulate into IP address + bool last = c == 0; + buff[i] = 0; + uint32_t v = atoi(next); + ip = ip | ((v&0xff)<<(found*8)); + next = buff+i+1; // next integer starts after the '.' + found++; + if (last) { // if at end of string we better got 4 integers + ip_ptr->addr = ip; + return found == 4; + } + continue; + } + if (c < '0' || c > '9') return false; + } + return false; +} + +#ifdef DEBUGIP +static void ICACHE_FLASH_ATTR debugIP() { + struct ip_info info; + if (wifi_get_ip_info(0, &info)) { + os_printf("\"ip\": \"%d.%d.%d.%d\"\n", IP2STR(&info.ip.addr)); + os_printf("\"netmask\": \"%d.%d.%d.%d\"\n", IP2STR(&info.netmask.addr)); + os_printf("\"gateway\": \"%d.%d.%d.%d\"\n", IP2STR(&info.gw.addr)); + os_printf("\"hostname\": \"%s\"\n", wifi_station_get_hostname()); + } else { + os_printf("\"ip\": \"-none-\"\n"); + } +} +#endif + +// configure Wifi, specifically DHCP vs static IP address based on flash config +void ICACHE_FLASH_ATTR configWifiIP() { + if (flashConfig.staticip == 0) { + // let's DHCP! + wifi_station_set_hostname(flashConfig.hostname); + if (wifi_station_dhcpc_status() == DHCP_STARTED) + wifi_station_dhcpc_stop(); + wifi_station_dhcpc_start(); + DBG("Wifi uses DHCP, hostname=%s\n", flashConfig.hostname); + } else { + // no DHCP, we got static network config! + wifi_station_dhcpc_stop(); + struct ip_info ipi; + ipi.ip.addr = flashConfig.staticip; + ipi.netmask.addr = flashConfig.netmask; + ipi.gw.addr = flashConfig.gateway; + wifi_set_ip_info(0, &ipi); + DBG("Wifi uses static IP %d.%d.%d.%d\n", IP2STR(&ipi.ip.addr)); + } +#ifdef DEBUGIP + debugIP(); +#endif +} + +// Change special settings +int ICACHE_FLASH_ATTR cgiWiFiSpecial(HttpdConnData *connData) { + char dhcp[8]; + char staticip[20]; + char netmask[20]; + char gateway[20]; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; + + // get args and their string lengths + int dl = httpdFindArg(connData->getArgs, "dhcp", dhcp, sizeof(dhcp)); + int sl = httpdFindArg(connData->getArgs, "staticip", staticip, sizeof(staticip)); + int nl = httpdFindArg(connData->getArgs, "netmask", netmask, sizeof(netmask)); + int gl = httpdFindArg(connData->getArgs, "gateway", gateway, sizeof(gateway)); + + if (!(dl > 0 && sl >= 0 && nl >= 0 && gl >= 0)) { + jsonHeader(connData, 400); + httpdSend(connData, "Request is missing fields", -1); + return HTTPD_CGI_DONE; + } + + char url[64]; // redirect URL + if (os_strcmp(dhcp, "off") == 0) { + // parse static IP params + struct ip_info ipi; + bool ok = parse_ip(staticip, &ipi.ip); + if (nl > 0) ok = ok && parse_ip(netmask, &ipi.netmask); + else IP4_ADDR(&ipi.netmask, 255, 255, 255, 0); + if (gl > 0) ok = ok && parse_ip(gateway, &ipi.gw); + else ipi.gw.addr = 0; + if (!ok) { + jsonHeader(connData, 400); + httpdSend(connData, "Cannot parse static IP config", -1); + return HTTPD_CGI_DONE; + } + // save the params in flash + flashConfig.staticip = ipi.ip.addr; + flashConfig.netmask = ipi.netmask.addr; + flashConfig.gateway = ipi.gw.addr; + // construct redirect URL + os_sprintf(url, "{\"url\": \"http://%d.%d.%d.%d\"}", IP2STR(&ipi.ip)); + + } else { + // dynamic IP + flashConfig.staticip = 0; + os_sprintf(url, "{\"url\": \"http://%s\"}", flashConfig.hostname); + } + + configSave(); // ignore error... + // schedule change-over + os_timer_disarm(&reassTimer); + os_timer_setfn(&reassTimer, configWifiIP, NULL); + os_timer_arm(&reassTimer, 1000, 0); // 1 second for the response of this request to make it + // return redirect info + jsonHeader(connData, 200); + httpdSend(connData, url, -1); + return HTTPD_CGI_DONE; +} + +// ==== Soft-AP related functions + +// Change Soft-AP main settings +int ICACHE_FLASH_ATTR cgiApSettingsChange(HttpdConnData *connData) { + + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + // No changes for Soft-AP in STA mode + int mode = wifi_get_opmode(); + if ( mode == 1 ){ + jsonHeader(connData, 400); + httpdSend(connData, "No changes allowed in STA mode", -1); + return HTTPD_CGI_DONE; + } + + char buff[96]; + int len; + + // Check extra security measure, this must be 1 + len=httpdFindArg(connData->getArgs, "100", buff, sizeof(buff)); + if(len>0){ + if(atoi(buff)!=1){ + jsonHeader(connData, 400); + return HTTPD_CGI_DONE; + } + } + // Set new SSID + len=httpdFindArg(connData->getArgs, "ap_ssid", buff, sizeof(buff)); + if(checkString(buff) && len>7 && len<32){ + // STRING PREPROCESSING DONE IN CLIENT SIDE + os_memset(apconf.ssid, 0, 32); + os_memcpy(apconf.ssid, buff, len); + apconf.ssid_len = len; + }else{ + jsonHeader(connData, 400); + httpdSend(connData, "SSID not valid or out of range", -1); + return HTTPD_CGI_DONE; + } + // Set new PASSWORD + len=httpdFindArg(connData->getArgs, "ap_password", buff, sizeof(buff)); + if(checkString(buff) && len>7 && len<64){ + // String preprocessing done in client side, wifiap.js line 31 + os_memset(apconf.password, 0, 64); + os_memcpy(apconf.password, buff, len); + }else if (len == 0){ + os_memset(apconf.password, 0, 64); + }else{ + jsonHeader(connData, 400); + httpdSend(connData, "PASSWORD not valid or out of range", -1); + return HTTPD_CGI_DONE; + } + // Set auth mode + if(len != 0){ + // Set authentication mode, before password to check open settings + len=httpdFindArg(connData->getArgs, "ap_authmode", buff, sizeof(buff)); + if(len>0){ + int value = atoi(buff); + if(value >= 0 && value <= 4){ + apconf.authmode = value; + }else{ + // If out of range set by default + apconf.authmode = 4; + } + }else{ + // Valid password but wrong auth mode, default 4 + apconf.authmode = 4; + } + }else{ + apconf.authmode = 0; + } + // Set max connection number + len=httpdFindArg(connData->getArgs, "ap_maxconn", buff, sizeof(buff)); + if(len>0){ + + int value = atoi(buff); + if(value > 0 && value <= 4){ + apconf.max_connection = value; + }else{ + // If out of range, set by default + apconf.max_connection = 4; + } + } + // Set beacon interval value + len=httpdFindArg(connData->getArgs, "ap_beacon", buff, sizeof(buff)); + if(len>0){ + int value = atoi(buff); + if(value >= 100 && value <= 60000){ + apconf.beacon_interval = value; + }else{ + // If out of range, set by default + apconf.beacon_interval = 100; + } + } + // Set ssid to be hidden or not + len=httpdFindArg(connData->getArgs, "ap_hidden", buff, sizeof(buff)); + if(len>0){ + int value = atoi(buff); + if(value == 0 || value == 1){ + apconf.ssid_hidden = value; + }else{ + // If out of range, set by default + apconf.ssid_hidden = 0; + } + } + // Store new configuration + wifi_softap_set_config(&apconf); + + jsonHeader(connData, 200); + return HTTPD_CGI_DONE; +} + +// Get current Soft-AP settings +int ICACHE_FLASH_ATTR cgiApSettingsInfo(HttpdConnData *connData) { + + char buff[1024]; + if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + os_sprintf(buff, + "{ " + "\"ap_ssid\": \"%s\", " + "\"ap_password\": \"%s\", " + "\"ap_authmode\": %d, " + "\"ap_maxconn\": %d, " + "\"ap_beacon\": %d, " + "\"ap_hidden\": \"%s\" " + " }", + apconf.ssid, + apconf.password, + apconf.authmode, + apconf.max_connection, + apconf.beacon_interval, + apconf.ssid_hidden ? "enabled" : "disabled" + ); + + jsonHeader(connData, 200); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} + +//This cgi changes the operating mode: STA / AP / STA+AP +int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { + int len; + char buff[1024]; + int previous_mode = wifi_get_opmode(); + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); + int next_mode = atoi(buff); + + if (len!=0) { + if (next_mode == 2){ + // moving to AP mode, so disconnect before leave STA mode + wifi_station_disconnect(); + } + + DBG("Wifi switching to mode %d\n", next_mode); + wifi_set_opmode(next_mode&3); + + if (previous_mode == 2) { + // moving to STA or STA+AP mode from AP, try to connect and set timer + stconf.bssid_set = 0; + wifi_station_set_config(&stconf); + wifi_station_connect(); + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); + } + if(previous_mode == 1){ + // moving to AP or STA+AP from STA, so softap config call needed + wifi_softap_set_config(&apconf); + } + jsonHeader(connData, 200); + } else { + jsonHeader(connData, 400); + } + return HTTPD_CGI_DONE; +} + +static char *connStatuses[] = { "idle", "connecting", "wrong password", "AP not found", + "failed", "got IP address" }; + +static char *wifiWarn[] = { 0, + "Switch to STA+AP mode", + "Switch to STA+AP mode", + "Switch to STA mode", + "Switch to AP mode", +}; + +static char *apAuthMode[] = { "OPEN", + "WEP", + "WPA_PSK", + "WPA2_PSK", + "WPA_WPA2_PSK", +}; + +#ifdef CHANGE_TO_STA +#define MODECHANGE "yes" +#else +#define MODECHANGE "no" +#endif + +// print various Wifi information into json buffer +int ICACHE_FLASH_ATTR printWifiInfo(char *buff) { + int len; + //struct station_config stconf; + wifi_station_get_config(&stconf); + //struct softap_config apconf; + wifi_softap_get_config(&apconf); + + uint8_t op = wifi_get_opmode() & 0x3; + char *mode = wifiMode[op]; + char *status = "unknown"; + int st = wifi_station_get_connect_status(); + if (st >= 0 && st < sizeof(connStatuses)) status = connStatuses[st]; + int p = wifi_get_phy_mode(); + char *phy = wifiPhy[p&3]; + char *warn = wifiWarn[op]; + if (op == 3) op = 4; // Done to let user switch to AP only mode from Soft-AP settings page, using only one set of warnings + char *apwarn = wifiWarn[op]; + char *apauth = apAuthMode[apconf.authmode]; + sint8 rssi = wifi_station_get_rssi(); + if (rssi > 0) rssi = 0; + uint8 mac_addr[6]; + uint8 apmac_addr[6]; + wifi_get_macaddr(0, mac_addr); + wifi_get_macaddr(1, apmac_addr); + uint8_t chan = wifi_get_channel(); + + len = os_sprintf(buff, + "\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", " + "\"rssi\": \"%ddB\", \"warn\": \"%s\", \"apwarn\": \"%s\",\"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":\"%d\", \"apssid\": \"%s\", " + "\"appass\": \"%s\", \"apchan\": \"%d\", \"apmaxc\": \"%d\", \"aphidd\": \"%s\", \"apbeac\": \"%d\", \"apauth\": \"%s\",\"apmac\":\"%02x:%02x:%02x:%02x:%02x:%02x\"", + mode, MODECHANGE, (char*)stconf.ssid, status, phy, rssi, warn, apwarn, + mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], chan, (char*)apconf.ssid,(char*)apconf.password,apconf.channel,apconf.max_connection,apconf.ssid_hidden?"enabled":"disabled",apconf.beacon_interval, apauth,apmac_addr[0], apmac_addr[1], apmac_addr[2], apmac_addr[3], apmac_addr[4], apmac_addr[5]); + + struct ip_info info; + if (wifi_get_ip_info(0, &info)) { + len += os_sprintf(buff+len, ", \"ip\": \"%d.%d.%d.%d\"", IP2STR(&info.ip.addr)); + len += os_sprintf(buff+len, ", \"netmask\": \"%d.%d.%d.%d\"", IP2STR(&info.netmask.addr)); + len += os_sprintf(buff+len, ", \"gateway\": \"%d.%d.%d.%d\"", IP2STR(&info.gw.addr)); + len += os_sprintf(buff+len, ", \"hostname\": \"%s\"", flashConfig.hostname); + } else { + len += os_sprintf(buff+len, ", \"ip\": \"-none-\""); + } + len += os_sprintf(buff+len, ", \"staticip\": \"%d.%d.%d.%d\"", IP2STR(&flashConfig.staticip)); + len += os_sprintf(buff+len, ", \"dhcp\": \"%s\"", flashConfig.staticip > 0 ? "off" : "on"); + + return len; +} + +int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { + char buff[1024]; + int len; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + jsonHeader(connData, 200); + + len = os_sprintf(buff, "{"); + len += printWifiInfo(buff+len); + len += os_sprintf(buff+len, ", "); + + if (wifiReason != 0) { + len += os_sprintf(buff+len, "\"reason\": \"%s\", ", wifiGetReason()); + } + +#if 0 + // commented out 'cause often the client that requested the change can't get a request in to + // find out that it succeeded. Better to just wait the std 15 seconds... + int st=wifi_station_get_connect_status(); + if (st == STATION_GOT_IP) { + if (wifi_get_opmode() != 1) { + // Reset into AP-only mode sooner. + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, 1000, 0); + } + } +#endif + + len += os_sprintf(buff+len, "\"x\":0}\n"); + //DBG(" -> %s\n", buff); + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +// Cgi to return various Wifi information +int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) { + char buff[1024]; + + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + os_strcpy(buff, "{"); + printWifiInfo(buff+1); + os_strcat(buff, "}"); + + jsonHeader(connData, 200); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} + +// Check string againt invalid characters +int ICACHE_FLASH_ATTR checkString(char *str){ + int i = 0; + for(; i < os_strlen(str); i++) + { + // Alphanumeric and underscore allowed + if (!(isalnum((unsigned char)str[i]) || str[i] == '_')) + { + DBG("Error: String has non alphanumeric chars\n"); + return 0; + } + } + return 1; +} + +/* Init the wireless + * + * Call both Soft-AP and Station default config + * Change values according to Makefile hard-coded variables + * Anyway set wifi opmode to STA+AP, it will change to STA if CHANGE_TO_STA is set to yes in Makefile + * Call a timer to check the STA connection + */ +void ICACHE_FLASH_ATTR wifiInit() { + + // Check te wifi opmode + int x = wifi_get_opmode() & 0x3; + + // Set opmode to 3 to let system scan aps, otherwise it won't scan + wifi_set_opmode(3); + + // Call both STATION and SOFTAP default config + wifi_station_get_config_default(&stconf); + wifi_softap_get_config_default(&apconf); + + DBG("Wifi init, mode=%s\n",wifiMode[x]); + + // STATION parameters +#if defined(STA_SSID) && defined(STA_PASS) + // Set parameters + if (os_strlen((char*)stconf.ssid) == 0 && os_strlen((char*)stconf.password) == 0) { + os_strncpy((char*)stconf.ssid, VERS_STR(STA_SSID), 32); + os_strncpy((char*)stconf.password, VERS_STR(STA_PASS), 64); + + DBG("Wifi pre-config trying to connect to AP %s pw %s\n",(char*)stconf.ssid, (char*)stconf.password); + + // wifi_set_phy_mode(2); // limit to 802.11b/g 'cause n is flaky + stconf.bssid_set = 0; + wifi_station_set_config(&stconf); + } +#endif + + // Change SOFT_AP settings if defined +#if defined(AP_SSID) + // Check if ssid and pass are alphanumeric values + int ssidlen = os_strlen(VERS_STR(AP_SSID)); + if(checkString(VERS_STR(AP_SSID)) && ssidlen > 7 && ssidlen < 32){ + // Clean memory and set the value of SSID + os_memset(apconf.ssid, 0, 32); + os_memcpy(apconf.ssid, VERS_STR(AP_SSID), os_strlen(VERS_STR(AP_SSID))); + // Specify the length of ssid + apconf.ssid_len= ssidlen; +#if defined(AP_PASS) + // If pass is at least 8 and less than 64 + int passlen = os_strlen(VERS_STR(AP_PASS)); + if( checkString(VERS_STR(AP_PASS)) && passlen > 7 && passlen < 64 ){ + // Clean memory and set the value of PASS + os_memset(apconf.password, 0, 64); + os_memcpy(apconf.password, VERS_STR(AP_PASS), passlen); + // Can't choose auth mode without a valid ssid and password +#ifdef AP_AUTH_MODE + // If set, use specified auth mode + if(AP_AUTH_MODE >= 0 && AP_AUTH_MODE <=4) + apconf.authmode = AP_AUTH_MODE; +#else + // If not, use WPA2 + apconf.authmode = AUTH_WPA_WPA2_PSK; +#endif + }else if ( passlen == 0){ + // If ssid is ok and no pass, set auth open + apconf.authmode = AUTH_OPEN; + // Remove stored password + os_memset(apconf.password, 0, 64); + } +#endif + }// end of ssid and pass check +#ifdef AP_SSID_HIDDEN + // If set, use specified ssid hidden parameter + if(AP_SSID_HIDDEN == 0 || AP_SSID_HIDDEN ==1) + apconf.ssid_hidden = AP_SSID_HIDDEN; +#endif +#ifdef AP_MAX_CONN + // If set, use specified max conn number + if(AP_MAX_CONN > 0 && AP_MAX_CONN <5) + apconf.max_connection = AP_MAX_CONN; +#endif +#ifdef AP_BEACON_INTERVAL + // If set use specified beacon interval + if(AP_BEACON_INTERVAL >= 100 && AP_BEACON_INTERVAL <= 60000) + apconf.beacon_interval = AP_BEACON_INTERVAL; +#endif + // Check softap config + bool softap_set_conf = wifi_softap_set_config(&apconf); + // Debug info + + DBG("Wifi Soft-AP parameters change: %s\n",softap_set_conf? "success":"fail"); +#endif // AP_SSID && AP_PASS + + configWifiIP(); + + // The default sleep mode should be modem_sleep, but we set it here explicitly for good + // measure. We can't use light_sleep because that powers off everthing and we would loose + // all connections. + wifi_set_sleep_type(MODEM_SLEEP_T); + + wifi_set_event_handler_cb(wifiHandleEventCb); + // check on the wifi in a few seconds to see whether we need to switch mode + os_timer_disarm(&resetTimer); + os_timer_setfn(&resetTimer, resetTimerCb, NULL); + os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); +} diff --git a/esp-link/cgiwifi.h b/esp-link/cgiwifi.h new file mode 100644 index 0000000..667f27d --- /dev/null +++ b/esp-link/cgiwifi.h @@ -0,0 +1,27 @@ +#ifndef CGIWIFI_H +#define CGIWIFI_H + +#include "httpd.h" + +enum { wifiIsDisconnected, wifiIsConnected, wifiGotIP }; +typedef void(*WifiStateChangeCb)(uint8_t wifiStatus); + +int cgiWiFiScan(HttpdConnData *connData); +int cgiWifiInfo(HttpdConnData *connData); +int cgiWiFi(HttpdConnData *connData); +int cgiWiFiConnect(HttpdConnData *connData); +int cgiWiFiSetMode(HttpdConnData *connData); +int cgiWiFiConnStatus(HttpdConnData *connData); +int cgiWiFiSpecial(HttpdConnData *connData); +int cgiApSettingsChange(HttpdConnData *connData); +int cgiApSettingsInfo(HttpdConnData *connData); +void configWifiIP(); +void wifiInit(void); +void wifiAddStateChangeCb(WifiStateChangeCb cb); +void wifiStartMDNS(struct ip_addr); +int checkString(char *str); + +extern uint8_t wifiState; +extern bool mdns_started; + +#endif diff --git a/esp-link/config.c b/esp-link/config.c new file mode 100644 index 0000000..cdf4b1e --- /dev/null +++ b/esp-link/config.c @@ -0,0 +1,186 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +/* Configuration stored in flash */ + +#include +#include +#include "config.h" +#include "espfs.h" +#include "crc16.h" + +FlashConfig flashConfig; +FlashConfig flashDefault = { + .seq = 33, .magic = 0, .crc = 0, + .reset_pin = MCU_RESET_PIN, .isp_pin = MCU_ISP_PIN, + .conn_led_pin = LED_CONN_PIN, .ser_led_pin = LED_SERIAL_PIN, + .baud_rate = 115200, + .hostname = "happy-bubbles-nfc\0", + .staticip = 0, + .netmask = 0x00ffffff, + .gateway = 0, + .log_mode = 0, + .swap_uart = 0, + .tcp_enable = 1, .rssi_enable = 0, + .api_key = "", + .nfc_counter = 3, + .nfc_url = "http://example.com/api/rfid\0", + .nfc_device_id = "device_id\0", + .nfc_device_secret = "secret\0", + .sys_descr = "\0", + .rx_pullup = 1, + .sntp_server = "us.pool.ntp.org\0", + .syslog_host = "\0", .syslog_minheap = 8192, .syslog_filter = 7, .syslog_showtick = 1, .syslog_showdate = 0, + .mdns_enable = 1, .mdns_servername = "http\0", .timezone_offset = 0, + .configMode = true +}; + +typedef union { + FlashConfig fc; + uint8_t block[1024]; +} FlashFull; + +// magic number to recognize thet these are our flash settings as opposed to some random stuff +#define FLASH_MAGIC (0xaa55) + +// size of the setting sector +#define FLASH_SECT (4096) + +// address where to flash the settings: if we have >512KB flash then there are 16KB of reserved +// space at the end of the first flash partition, we use the upper 8KB (2 sectors). If we only +// have 512KB then that space is used by the SDK and we use the 8KB just before that. +static uint32_t ICACHE_FLASH_ATTR flashAddr(void) { + enum flash_size_map map = system_get_flash_size_map(); + return map >= FLASH_SIZE_8M_MAP_512_512 + ? FLASH_SECT + FIRMWARE_SIZE + 2*FLASH_SECT // bootloader + firmware + 8KB free + : FLASH_SECT + FIRMWARE_SIZE - 2*FLASH_SECT;// bootloader + firmware - 8KB (risky...) +} + +static int flash_pri; // primary flash sector (0 or 1, or -1 for error) + +#if 0 +static void memDump(void *addr, int len) { + for (int i=0; i>12) != SPI_FLASH_RESULT_OK) + goto fail; // no harm done, give up + // calculate CRC + ff.fc.seq = seq; + ff.fc.magic = FLASH_MAGIC; + ff.fc.crc = 0; + //os_printf("cksum of: "); + //memDump(&ff, sizeof(ff)); + ff.fc.crc = crc16_data((unsigned char*)&ff, sizeof(ff), 0); + //os_printf("cksum is %04x\n", ff.fc.crc); + // write primary with incorrect seq + ff.fc.seq = 0xffffffff; + if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) + goto fail; // no harm done, give up + // fill in correct seq + ff.fc.seq = seq; + if (spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)) != SPI_FLASH_RESULT_OK) + goto fail; // most likely failed, but no harm if successful + // now that we have safely written the new version, erase old primary + addr = flashAddr() + flash_pri*FLASH_SECT; + flash_pri = 1-flash_pri; + if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK) + return true; // no back-up but we're OK + // write secondary + ff.fc.seq = 0xffffffff; + if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) + return true; // no back-up but we're OK + ff.fc.seq = seq; + spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)); + return true; +fail: +#ifdef CONFIG_DBG + os_printf("*** Failed to save config ***\n"); +#endif + return false; +} + +void ICACHE_FLASH_ATTR configWipe(void) { + spi_flash_erase_sector(flashAddr()>>12); + spi_flash_erase_sector((flashAddr()+FLASH_SECT)>>12); +} + +static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *fc0, FlashFull *fc1); + +bool ICACHE_FLASH_ATTR configRestore(void) { + FlashFull ff0, ff1; + // read both flash sectors + if (spi_flash_read(flashAddr(), (void *)&ff0, sizeof(ff0)) != SPI_FLASH_RESULT_OK) + os_memset(&ff0, 0, sizeof(ff0)); // clear in case of error + if (spi_flash_read(flashAddr()+FLASH_SECT, (void *)&ff1, sizeof(ff1)) != SPI_FLASH_RESULT_OK) + os_memset(&ff1, 0, sizeof(ff1)); // clear in case of error + // figure out which one is good + flash_pri = selectPrimary(&ff0, &ff1); + // if neither is OK, we revert to defaults + if (flash_pri < 0) { + os_memcpy(&flashConfig, &flashDefault, sizeof(FlashConfig)); + char chipIdStr[6]; + os_sprintf(chipIdStr, "%06x", system_get_chip_id()); +#ifdef CHIP_IN_HOSTNAME + char hostname[16]; + os_strcpy(hostname, "happy-bubbles-nfc-"); + os_strcat(hostname, chipIdStr); + os_memcpy(&flashConfig.hostname, hostname, os_strlen(hostname)); +#endif + flash_pri = 0; + return false; + } + // copy good one into global var and return + os_memcpy(&flashConfig, flash_pri == 0 ? &ff0.fc : &ff1.fc, sizeof(FlashConfig)); + return true; +} + +static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *ff0, FlashFull *ff1) { + // check CRC of ff0 + uint16_t crc = ff0->fc.crc; + ff0->fc.crc = 0; + bool ff0_crc_ok = crc16_data((unsigned char*)ff0, sizeof(FlashFull), 0) == crc; +#ifdef CONFIG_DBG + os_printf("FLASH chk=0x%04x crc=0x%04x full_sz=%d sz=%d chip_sz=%d\n", + crc16_data((unsigned char*)ff0, sizeof(FlashFull), 0), + crc, + sizeof(FlashFull), + sizeof(FlashConfig), + getFlashSize()); +#endif + + // check CRC of ff1 + crc = ff1->fc.crc; + ff1->fc.crc = 0; + bool ff1_crc_ok = crc16_data((unsigned char*)ff1, sizeof(FlashFull), 0) == crc; + + // decided which we like better + if (ff0_crc_ok) + if (!ff1_crc_ok || ff0->fc.seq >= ff1->fc.seq) + return 0; // use first sector as primary + else + return 1; // second sector is newer + else + return ff1_crc_ok ? 1 : -1; +} + +// returns the flash chip's size, in BYTES +const size_t ICACHE_FLASH_ATTR +getFlashSize() { + uint32_t id = spi_flash_get_id(); + uint8_t mfgr_id = id & 0xff; + //uint8_t type_id = (id >> 8) & 0xff; // not relevant for size calculation + uint8_t size_id = (id >> 16) & 0xff; // lucky for us, WinBond ID's their chips as a form that lets us calculate the size + if (mfgr_id != 0xEF) // 0xEF is WinBond; that's all we care about (for now) + return 0; + return 1 << size_id; +} diff --git a/esp-link/config.h b/esp-link/config.h new file mode 100644 index 0000000..ba9067b --- /dev/null +++ b/esp-link/config.h @@ -0,0 +1,43 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// Flash configuration settings. When adding new items always add them at the end and formulate +// them such that a value of zero is an appropriate default or backwards compatible. Existing +// modules that are upgraded will have zero in the new fields. This ensures that an upgrade does +// not wipe out the old settings. +typedef struct { + uint32_t seq; // flash write sequence number + uint16_t magic, crc; + int8_t reset_pin, isp_pin, conn_led_pin, ser_led_pin; + int32_t baud_rate; + char hostname[32]; // if using DHCP + uint32_t staticip, netmask, gateway; // using DHCP if staticip==0 + uint8_t log_mode; // UART log debug mode + int8_t swap_uart; // swap uart0 to gpio 13&15 + uint8_t tcp_enable, rssi_enable; // TCP client settings + char api_key[48]; // RSSI submission API key (Grovestreams for now) + uint32_t nfc_counter; + char nfc_url[64], + nfc_device_id[48], + nfc_device_secret[48]; + char sys_descr[129]; // system description + int8_t rx_pullup; // internal pull-up on RX pin + char sntp_server[32]; + char syslog_host[32]; + uint16_t syslog_minheap; // min. heap to allow queuing + uint8_t syslog_filter, // min. severity + syslog_showtick, // show system tick (µs) + syslog_showdate; // populate SYSLOG date field + uint8_t mdns_enable; + char mdns_servername[32]; + int8_t timezone_offset; + bool configMode; +} FlashConfig; +extern FlashConfig flashConfig; + +bool configSave(void); +bool configRestore(void); +void configWipe(void); +const size_t getFlashSize(); + +#endif diff --git a/esp-link/crc16.c b/esp-link/crc16.c new file mode 100644 index 0000000..22a6651 --- /dev/null +++ b/esp-link/crc16.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2005, Swedish Institute of Computer Science + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + */ + +/** \addtogroup crc16 + * @{ */ + +/** + * \file + * Implementation of the CRC16 calculcation + * \author + * Adam Dunkels + * + */ + +/* CITT CRC16 polynomial ^16 + ^12 + ^5 + 1 */ +/*---------------------------------------------------------------------------*/ +unsigned short +crc16_add(unsigned char b, unsigned short acc) +{ + /* + acc = (unsigned char)(acc >> 8) | (acc << 8); + acc ^= b; + acc ^= (unsigned char)(acc & 0xff) >> 4; + acc ^= (acc << 8) << 4; + acc ^= ((acc & 0xff) << 4) << 1; + */ + + acc ^= b; + acc = (acc >> 8) | (acc << 8); + acc ^= (acc & 0xff00) << 4; + acc ^= (acc >> 8) >> 4; + acc ^= (acc & 0xff00) >> 5; + return acc; +} +/*---------------------------------------------------------------------------*/ +unsigned short +crc16_data(const unsigned char *data, int len, unsigned short acc) +{ + int i; + + for(i = 0; i < len; ++i) { + acc = crc16_add(*data, acc); + ++data; + } + return acc; +} +/*---------------------------------------------------------------------------*/ + +/** @} */ diff --git a/esp-link/crc16.h b/esp-link/crc16.h new file mode 100644 index 0000000..bd4c52e --- /dev/null +++ b/esp-link/crc16.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2005, Swedish Institute of Computer Science + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * Header file for the CRC16 calculcation + * \author + * Adam Dunkels + * + */ + +/** \addtogroup lib + * @{ */ + +/** + * \defgroup crc16 Cyclic Redundancy Check 16 (CRC16) calculation + * + * The Cyclic Redundancy Check 16 is a hash function that produces a + * checksum that is used to detect errors in transmissions. The CRC16 + * calculation module is an iterative CRC calculator that can be used + * to cumulatively update a CRC checksum for every incoming byte. + * + * @{ + */ + +#ifndef CRC16_H_ +#define CRC16_H_ +#ifdef __cplusplus +extern "C" { +#endif +/** + * \brief Update an accumulated CRC16 checksum with one byte. + * \param b The byte to be added to the checksum + * \param crc The accumulated CRC that is to be updated. + * \return The updated CRC checksum. + * + * This function updates an accumulated CRC16 checksum + * with one byte. It can be used as a running checksum, or + * to checksum an entire data block. + * + * \note The algorithm used in this implementation is + * tailored for a running checksum and does not perform as + * well as a table-driven algorithm when checksumming an + * entire data block. + * + */ +unsigned short crc16_add(unsigned char b, unsigned short crc); + +/** + * \brief Calculate the CRC16 over a data area + * \param data Pointer to the data + * \param datalen The length of the data + * \param acc The accumulated CRC that is to be updated (or zero). + * \return The CRC16 checksum. + * + * This function calculates the CRC16 checksum of a data area. + * + * \note The algorithm used in this implementation is + * tailored for a running checksum and does not perform as + * well as a table-driven algorithm when checksumming an + * entire data block. + */ +unsigned short crc16_data(const unsigned char *data, int datalen, + unsigned short acc); +#ifdef __cplusplus +} +#endif +#endif /* CRC16_H_ */ + +/** @} */ +/** @} */ diff --git a/esp-link/log.c b/esp-link/log.c new file mode 100644 index 0000000..821641f --- /dev/null +++ b/esp-link/log.c @@ -0,0 +1,182 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt + +#include +#include "uart.h" +#include "cgi.h" +#include "config.h" +#include "log.h" + +#ifdef LOG_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + +// Web log for the esp8266 to replace outputting to uart1. +// The web log has a 1KB circular in-memory buffer which os_printf prints into and +// the HTTP handler simply displays the buffer content on a web page. + +// see console.c for invariants (same here) +#define BUF_MAX (1400) +static char log_buf[BUF_MAX]; +static int log_wr, log_rd; +static int log_pos; +static bool log_no_uart; // start out printing to uart +static bool log_newline; // at start of a new line + +// write to the uart designated for logging +static void uart_write_char(char c) { + if (flashConfig.log_mode == LOG_MODE_ON1) + uart1_write_char(c); + else + uart0_write_char(c); +} + +// called from wifi reset timer to turn UART on when we loose wifi and back off +// when we connect to wifi AP. Here this is gated by the flash setting +void ICACHE_FLASH_ATTR +log_uart(bool enable) { + if (!enable && !log_no_uart && flashConfig.log_mode < LOG_MODE_ON0) { + // we're asked to turn uart off, and uart is on, and the flash setting isn't always-on + DBG("Turning OFF uart log\n"); + os_delay_us(4*1000L); // time for uart to flush + log_no_uart = !enable; + } else if (enable && log_no_uart && flashConfig.log_mode != LOG_MODE_OFF) { + // we're asked to turn uart on, and uart is off, and the flash setting isn't always-off + log_no_uart = !enable; + DBG("Turning ON uart log\n"); + } +} + +// write a character into the log buffer +static void ICACHE_FLASH_ATTR +log_write(char c) { + log_buf[log_wr] = c; + log_wr = (log_wr+1) % BUF_MAX; + if (log_wr == log_rd) { + log_rd = (log_rd+1) % BUF_MAX; // full, eat first char + log_pos++; + } +} + +// write a character to the log buffer and the uart, and handle newlines specially +static void ICACHE_FLASH_ATTR +log_write_char(char c) { + // log timestamp + if (log_newline) { + char buff[16]; + int l = os_sprintf(buff, "%6d> ", (system_get_time()/1000)%1000000); + if (!log_no_uart) + for (int i=0; iconn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + jsonHeader(connData, 200); + + // figure out where to start in buffer based on URI param + len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); + if (len > 0) { + start = atoi(buff); + if (start < log_pos) { + start = 0; + } else if (start >= log_pos+log_len) { + start = log_len; + } else { + start = start - log_pos; + } + } + + // start outputting + len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", + log_len-start, log_pos+start); + + int rd = (log_rd+start) % BUF_MAX; + while (len < 2040 && rd != log_wr) { + uint8_t c = log_buf[rd]; + if (c == '\\' || c == '"') { + buff[len++] = '\\'; + buff[len++] = c; + } else if (c < ' ') { + len += os_sprintf(buff+len, "\\u%04x", c); + } else { + buff[len++] = c; + } + rd = (rd + 1) % BUF_MAX; + } + os_strcpy(buff+len, "\"}"); len+=2; + httpdSend(connData, buff, len); + return HTTPD_CGI_DONE; +} + +static char *dbg_mode[] = { "auto", "off", "on0", "on1" }; + +int ICACHE_FLASH_ATTR +ajaxLogDbg(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + char buff[512]; + int len, status = 400; + len = httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); + if (len > 0) { + int8_t mode = -1; + if (os_strcmp(buff, "auto") == 0) mode = LOG_MODE_AUTO; + if (os_strcmp(buff, "off") == 0) mode = LOG_MODE_OFF; + if (os_strcmp(buff, "on0") == 0) mode = LOG_MODE_ON0; + if (os_strcmp(buff, "on1") == 0) mode = LOG_MODE_ON1; + if (mode >= 0) { + flashConfig.log_mode = mode; + if (mode != LOG_MODE_AUTO) log_uart(mode >= LOG_MODE_ON0); + status = configSave() ? 200 : 400; + } + } else if (connData->requestType == HTTPD_METHOD_GET) { + status = 200; + } + + jsonHeader(connData, status); + os_sprintf(buff, "{\"mode\": \"%s\"}", dbg_mode[flashConfig.log_mode]); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} + +void ICACHE_FLASH_ATTR dumpMem(void *addr, int len) { + uint8_t *a = addr; + int off = 0; + while (off < len) { + os_printf("%p ", a); + for (int i = 0; i < 16 && off + i < len; i++) { + os_printf(" %02x", a[i]); + } + os_printf(" "); + for (int i=0; i<16 && off 0x20 && *a < 0x3f ? *a : '.'); + os_printf("\n"); + } +} + +void ICACHE_FLASH_ATTR logInit() { + log_no_uart = flashConfig.log_mode == LOG_MODE_OFF; // ON unless set to always-off + log_wr = 0; + log_rd = 0; + os_install_putc1((void *)log_write_char); +} + + diff --git a/esp-link/log.h b/esp-link/log.h new file mode 100644 index 0000000..3b7e03a --- /dev/null +++ b/esp-link/log.h @@ -0,0 +1,18 @@ +#ifndef LOG_H +#define LOG_H + +#include "httpd.h" + +#define LOG_MODE_AUTO 0 // start by logging to uart0, turn aff after we get an IP +#define LOG_MODE_OFF 1 // always off +#define LOG_MODE_ON0 2 // always log to uart0 +#define LOG_MODE_ON1 3 // always log to uart1 + +void logInit(void); +void log_uart(bool enable); +int ajaxLog(HttpdConnData *connData); +int ajaxLogDbg(HttpdConnData *connData); + +void dumpMem(void *addr, int len); + +#endif diff --git a/esp-link/main.c b/esp-link/main.c new file mode 100644 index 0000000..76e7f4d --- /dev/null +++ b/esp-link/main.c @@ -0,0 +1,236 @@ +/* +* ---------------------------------------------------------------------------- +* "THE BEER-WARE LICENSE" (Revision 42): +* Jeroen Domburg wrote this file. As long as you retain +* this notice you can do whatever you want with this stuff. If we meet some day, +* and you think this stuff is worth it, you can buy me a beer in return. +* ---------------------------------------------------------------------------- +* Heavily modified and enhanced by Thorsten von Eicken in 2015 +* ---------------------------------------------------------------------------- +*/ + +#include +#include "httpd.h" +#include "httpdespfs.h" +#include "cgi.h" +#include "cgiwifi.h" +#include "cgitcp.h" +#include "cginfc.h" +#include "cgiflash.h" +#include "auth.h" +#include "espfs.h" +#include "uart.h" +#include "config.h" +#include "log.h" +#include "gpio.h" +#include "cgiservices.h" + +#include "gpio16.h" + +#define PERIPHS_IO_MUX_PULLDWN BIT6 +#define PIN_PULLDWN_DIS(PIN_NAME) CLEAR_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLDWN) + +/* +#define NOTICE(format, ...) do { \ + LOG_NOTICE(format, ## __VA_ARGS__ ); \ + os_printf(format "\n", ## __VA_ARGS__); \ +} while ( 0 ) +*/ +#define NOTICE(format, ...) do { \ + os_printf(format "\n", ## __VA_ARGS__); \ +} while ( 0 ) + +/* +This is the main url->function dispatching data struct. +In short, it's a struct with various URLs plus their handlers. The handlers can +be 'standard' CGI functions you wrote, or 'special' CGIs requiring an argument. +They can also be auth-functions. An asterisk will match any url starting with +everything before the asterisks; "*" matches everything. The list will be +handled top-down, so make sure to put more specific rules above the more +general ones. Authorization things (like authBasic) act as a 'barrier' and +should be placed above the URLs they protect. +*/ +HttpdBuiltInUrl builtInUrls[] = { + { "/", cgiRedirect, "/home.html" }, + { "/menu", cgiMenu, NULL }, + { "/flash/next", cgiGetFirmwareNext, NULL }, + { "/flash/upload", cgiUploadFirmware, NULL }, + { "/flash/reboot", cgiRebootFirmware, NULL }, +// { "/pgm/sync", cgiOptibootSync, NULL }, +// { "/pgm/upload", cgiOptibootData, NULL }, +// { "/log/text", ajaxLog, NULL }, +// { "/log/dbg", ajaxLogDbg, NULL }, +// { "/log/reset", cgiReset, NULL }, +// { "/console/reset", ajaxConsoleReset, NULL }, +// { "/console/baud", ajaxConsoleBaud, NULL }, +// { "/console/text", ajaxConsole, NULL }, +// { "/console/send", ajaxConsoleSend, NULL }, + //Enable the line below to protect the WiFi configuration with an username/password combo. + // {"/wifi/*", authBasic, myPassFn}, + { "/wifi", cgiRedirect, "/wifi/wifi.html" }, + { "/wifi/", cgiRedirect, "/wifi/wifi.html" }, + { "/wifi/info", cgiWifiInfo, NULL }, + { "/wifi/scan", cgiWiFiScan, NULL }, + { "/wifi/connect", cgiWiFiConnect, NULL }, + { "/wifi/connstatus", cgiWiFiConnStatus, NULL }, + { "/wifi/setmode", cgiWiFiSetMode, NULL }, + { "/wifi/special", cgiWiFiSpecial, NULL }, + { "/wifi/apinfo", cgiApSettingsInfo, NULL }, + { "/wifi/apchange", cgiApSettingsChange, NULL }, + { "/system/info", cgiSystemInfo, NULL }, + { "/system/update", cgiSystemSet, NULL }, + { "/services/info", cgiServicesInfo, NULL }, + { "/services/update", cgiServicesSet, NULL }, + + { "/nfc", cgiNFC, NULL }, + + { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem + + { NULL, NULL, NULL } +}; + +static ETSTimer configModeTimer; + +#ifdef SHOW_HEAP_USE +static ETSTimer prHeapTimer; +static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) { + os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size()); +} +#endif + +# define VERS_STR_STR(V) #V +# define VERS_STR(V) VERS_STR_STR(V) +char* esp_link_version = VERS_STR(VERSION); + +// address of espfs binary blob +extern uint32_t _binary_espfs_img_start; + +extern void app_init(void); + +void user_rf_pre_init(void) { + //default is enabled + system_set_os_print(DEBUG_SDK); +} + +#define BUTTON_GPIO 0 + +bool is_config_button_pressed(void) +{ + if(!GPIO_INPUT_GET(BUTTON_GPIO)) + { + return true; + } + return false; +} + +void stupid_restart(void) +{ + //force watchdog reset + while(1){}; +} + +static int config_button_count = 0; +bool should_config(void) +{ + if(is_config_button_pressed()) + { + NOTICE("config button was pressed"); + if(config_button_count > 2) + { + //toggle config mode, on next restart + flashConfig.configMode = !flashConfig.configMode; + configSave(); + NOTICE("config button was pressed > 3, doing RESTART!"); + stupid_restart(); + } + config_button_count++; + } + else + { + //restart counter if not pressed + config_button_count = 0; + } +} + +// Main routine to initialize esp-link. +void user_init(void) { + // get the flash config so we know how to init things + //configWipe(); // uncomment to reset the config for testing purposes + bool restoreOk = configRestore(); + // Init gpio pin registers + gpio_init(); + gpio_output_set(0, 0, 0, (1<<15)); // some people tie it to GND, gotta ensure it's disabled + // init UART + //uart_init(flashConfig.baud_rate, 115200); + uart_init(115200, 115200); + logInit(); // must come after init of uart + // Say hello (leave some time to cause break in TX after boot loader's msg + os_delay_us(10000L); + os_printf("\n\n** %s\n", esp_link_version); + os_printf("Flash config restore %s\n", restoreOk ? "ok" : "*FAILED*"); + + // //turn off the red LED on the boards + gpio16_output_conf(); + gpio16_output_set(1); + + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_GPIO0_U); + PIN_PULLDWN_DIS(PERIPHS_IO_MUX_GPIO0_U); + + // Wifi + wifiInit(); + + os_timer_disarm(&configModeTimer); + os_timer_setfn(&configModeTimer, should_config, NULL); + os_timer_arm(&configModeTimer, 1000, 1); + + // init the flash filesystem with the html stuff + espFsInit(&_binary_espfs_img_start); + + if(flashConfig.configMode) + { + EspFsInitResult res = espFsInit(&_binary_espfs_img_start); + os_printf("espFsInit %s\n", res?"ERR":"ok"); + // mount the http handlers + httpdInit(builtInUrls, 80); +#ifdef SHOW_HEAP_USE + os_timer_disarm(&prHeapTimer); + os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL); + os_timer_arm(&prHeapTimer, 10000, 1); +#endif + } + + struct rst_info *rst_info = system_get_rst_info(); + NOTICE("Reset cause: %d=%s", rst_info->reason, rst_codes[rst_info->reason]); + NOTICE("exccause=%d epc1=0x%x epc2=0x%x epc3=0x%x excvaddr=0x%x depc=0x%x", + rst_info->exccause, rst_info->epc1, rst_info->epc2, rst_info->epc3, + rst_info->excvaddr, rst_info->depc); + uint32_t fid = spi_flash_get_id(); + NOTICE("Flash map %s, manuf 0x%02lX chip 0x%04lX", flash_maps[system_get_flash_size_map()], + fid & 0xff, (fid&0xff00)|((fid>>16)&0xff)); + NOTICE("** esp-link ready"); + + float brightness = 0.5; + + set_led(0, 0, 0, 0); + os_delay_us(10); + // orange for config + set_led((int)(255*brightness), (int)(69*brightness), 0, 0); + + if(flashConfig.configMode) + { + NOTICE("ENTERING CONFIG MODE"); + cgiServicesSNTPInit(); + + // turn on the red LED on the boards + gpio16_output_conf(); + gpio16_output_set(0); + + } + else + { + NOTICE("INITIALIZING USER APPLICATION"); + app_init(); + + } +} diff --git a/esp-link/stk500.h b/esp-link/stk500.h new file mode 100644 index 0000000..c75908c --- /dev/null +++ b/esp-link/stk500.h @@ -0,0 +1,44 @@ +/* STK500 constants list, from AVRDUDE + * + * Trivial set of constants derived from Atmel App Note AVR061 + * Not copyrighted. Released to the public domain. + */ + +#define STK_OK 0x10 +#define STK_FAILED 0x11 // Not used +#define STK_UNKNOWN 0x12 // Not used +#define STK_NODEVICE 0x13 // Not used +#define STK_INSYNC 0x14 // ' ' +#define STK_NOSYNC 0x15 // Not used +#define ADC_CHANNEL_ERROR 0x16 // Not used +#define ADC_MEASURE_OK 0x17 // Not used +#define PWM_CHANNEL_ERROR 0x18 // Not used +#define PWM_ADJUST_OK 0x19 // Not used +#define CRC_EOP 0x20 // 'SPACE' +#define STK_GET_SYNC 0x30 // '0' +#define STK_GET_SIGN_ON 0x31 // '1' +#define STK_SET_PARAMETER 0x40 // '@' +#define STK_GET_PARAMETER 0x41 // 'A' +#define STK_SET_DEVICE 0x42 // 'B' +#define STK_SET_DEVICE_EXT 0x45 // 'E' +#define STK_ENTER_PROGMODE 0x50 // 'P' +#define STK_LEAVE_PROGMODE 0x51 // 'Q' +#define STK_CHIP_ERASE 0x52 // 'R' +#define STK_CHECK_AUTOINC 0x53 // 'S' +#define STK_LOAD_ADDRESS 0x55 // 'U' +#define STK_UNIVERSAL 0x56 // 'V' +#define STK_PROG_FLASH 0x60 // '`' +#define STK_PROG_DATA 0x61 // 'a' +#define STK_PROG_FUSE 0x62 // 'b' +#define STK_PROG_LOCK 0x63 // 'c' +#define STK_PROG_PAGE 0x64 // 'd' +#define STK_PROG_FUSE_EXT 0x65 // 'e' +#define STK_READ_FLASH 0x70 // 'p' +#define STK_READ_DATA 0x71 // 'q' +#define STK_READ_FUSE 0x72 // 'r' +#define STK_READ_LOCK 0x73 // 's' +#define STK_READ_PAGE 0x74 // 't' +#define STK_READ_SIGN 0x75 // 'u' +#define STK_READ_OSCCAL 0x76 // 'v' +#define STK_READ_FUSE_EXT 0x77 // 'w' +#define STK_READ_OSCCAL_EXT 0x78 // 'x' diff --git a/esp-link/task.c b/esp-link/task.c new file mode 100644 index 0000000..879a694 --- /dev/null +++ b/esp-link/task.c @@ -0,0 +1,78 @@ +/* + * task.c + * + * Copyright 2015 Susi's Strolch + * + * For license information see projects "License.txt" + * + * Not sure if it's save to use ICACHE_FLASH_ATTR, so we're running from RAM + */ + +#undef USRTASK_DBG + +#include "esp8266.h" +#include + +#define MAXUSRTASKS 8 + +#ifdef USRTASK_DBG +#define DBG_USRTASK(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG_USRTASK(format, ...) do { } while(0) +#endif + +LOCAL os_event_t *_task_queue = NULL; // system_os_task queue +LOCAL os_task_t *usr_task_queue = NULL; // user task queue + +// it seems save to run the usr_event_handler from RAM, so no ICACHE_FLASH_ATTR here... + +LOCAL void usr_event_handler(os_event_t *e) +{ + DBG_USRTASK("usr_event_handler: event %p (sig=%d, par=%p)\n", e, (int)e->sig, (void *)e->par); + if (usr_task_queue[e->sig] == NULL || e->sig < 0 || e->sig >= MAXUSRTASKS) { + os_printf("usr_event_handler: task %d %s\n", (int)e->sig, + usr_task_queue[e->sig] == NULL ? "not registered" : "out of range"); + return; + } + (usr_task_queue[e->sig])(e); +} + +LOCAL void init_usr_task() { + if (_task_queue == NULL) + _task_queue = (os_event_t *)os_zalloc(sizeof(os_event_t) * _task_queueLen); + + if (usr_task_queue == NULL) + usr_task_queue = (os_task_t *)os_zalloc(sizeof(os_task_t) * MAXUSRTASKS); + + system_os_task(usr_event_handler, _taskPrio, _task_queue, _task_queueLen); +} + +// public functions +bool post_usr_task(uint8_t task, os_param_t par) +{ + return system_os_post(_taskPrio, task, par); +} + +uint8_t register_usr_task (os_task_t event) +{ + int task; + + DBG_USRTASK("register_usr_task: %p\n", event); + if (_task_queue == NULL || usr_task_queue == NULL) + init_usr_task(); + + for (task = 0; task < MAXUSRTASKS; task++) { + if (usr_task_queue[task] == event) + return task; // task already registered - bail out... + } + + for (task = 0; task < MAXUSRTASKS; task++) { + if (usr_task_queue[task] == NULL) { + DBG_USRTASK("register_usr_task: assign task #%d\n", task); + usr_task_queue[task] = event; + break; + } + } + return task; +} + diff --git a/esp-link/task.h b/esp-link/task.h new file mode 100644 index 0000000..2dfd5d8 --- /dev/null +++ b/esp-link/task.h @@ -0,0 +1,20 @@ +/* + * task.h + * + * Copyright 2015 Susi's Strolch + * + * For license information see projects "License.txt" + * + * + */ + +#ifndef USRTASK_H +#define USRTASK_H + +#define _taskPrio 1 +#define _task_queueLen 64 + +uint8_t register_usr_task (os_task_t event); +bool post_usr_task(uint8_t task, os_param_t par); + +#endif diff --git a/esp-link/uart.c b/esp-link/uart.c new file mode 100644 index 0000000..f3a5604 --- /dev/null +++ b/esp-link/uart.c @@ -0,0 +1,304 @@ +/* + * File : uart.c + * This file is part of Espressif's AT+ command set program. + * Copyright (C) 2013 - 2016, Espressif Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 3 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * ---------------------------------------------------------------------------- + * Heavily modified and enhanced by Thorsten von Eicken in 2015 + */ +#include "esp8266.h" +#include "task.h" +#include "uart.h" + +#ifdef UART_DBG +#define DBG_UART(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG_UART(format, ...) do { } while(0) +#endif + +LOCAL uint8_t uart_recvTaskNum; + +// UartDev is defined and initialized in rom code. +extern UartDevice UartDev; +#define MAX_CB 4 +static UartRecv_cb uart_recv_cb[4]; + +static void uart0_rx_intr_handler(void *para); + +/****************************************************************************** + * FunctionName : uart_config + * Description : Internal used function + * UART0 used for data TX/RX, RX buffer size is 0x100, interrupt enabled + * UART1 just used for debug output + * Parameters : uart_no, use UART0 or UART1 defined ahead + * Returns : NONE +*******************************************************************************/ +static void ICACHE_FLASH_ATTR +uart_config(uint8 uart_no) +{ + if (uart_no == UART1) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_GPIO2_U); + } else { + /* rcv_buff size is 0x100 */ + ETS_UART_INTR_ATTACH(uart0_rx_intr_handler, &(UartDev.rcv_buff)); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0); // FUNC_U0RXD==0 + //PIN_PULLUP_DIS (PERIPHS_IO_MUX_U0TXD_U); now done in serbridgeInitPins + //PIN_PULLUP_DIS (PERIPHS_IO_MUX_U0RXD_U); + } + + uart_div_modify(uart_no, UART_CLK_FREQ / UartDev.baut_rate); + + if (uart_no == UART1) //UART 1 always 8 N 1 + WRITE_PERI_REG(UART_CONF0(uart_no), + CALC_UARTMODE(EIGHT_BITS, NONE_BITS, ONE_STOP_BIT)); + else + WRITE_PERI_REG(UART_CONF0(uart_no), + CALC_UARTMODE(UartDev.data_bits, UartDev.parity, UartDev.stop_bits)); + + //clear rx and tx fifo,not ready + SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST); + CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST); + + if (uart_no == UART0) { + // Configure RX interrupt conditions as follows: trigger rx-full when there are 127 characters + // in the buffer, trigger rx-timeout when the fifo is non-empty and nothing further has been + // received for 4 character periods. + // Set the hardware flow-control to trigger when the FIFO holds 100 characters, although + // we don't really expect the signals to actually be wired up to anything. It doesn't hurt + // to set the threshold here... + // We do not enable framing error interrupts 'cause they tend to cause an interrupt avalanche + // and instead just poll for them when we get a std RX interrupt. + WRITE_PERI_REG(UART_CONF1(uart_no), + ((127 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S) | + ((100 & UART_RX_FLOW_THRHD) << UART_RX_FLOW_THRHD_S) | + UART_RX_FLOW_EN | + (4 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S | + UART_RX_TOUT_EN); + SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA); + } else { + WRITE_PERI_REG(UART_CONF1(uart_no), + ((UartDev.rcv_buff.TrigLvl & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S)); + } + + //clear all interrupt + WRITE_PERI_REG(UART_INT_CLR(uart_no), 0xffff); +} + +/****************************************************************************** + * FunctionName : uart1_tx_one_char + * Description : Internal used function + * Use uart1 interface to transfer one char + * Parameters : uint8 TxChar - character to tx + * Returns : OK +*******************************************************************************/ +STATUS +uart_tx_one_char(uint8 uart, uint8 c) +{ + //Wait until there is room in the FIFO + while (((READ_PERI_REG(UART_STATUS(uart))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=100) ; + //Send the character + WRITE_PERI_REG(UART_FIFO(uart), c); + return OK; +} + +/****************************************************************************** + * FunctionName : uart1_write_char + * Description : Internal used function + * Do some special deal while tx char is '\r' or '\n' + * Parameters : char c - character to tx + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +uart1_write_char(char c) +{ + //if (c == '\n') uart_tx_one_char(UART1, '\r'); + uart_tx_one_char(UART1, c); +} +void ICACHE_FLASH_ATTR +uart0_write_char(char c) +{ + //if (c == '\n') uart_tx_one_char(UART0, '\r'); + uart_tx_one_char(UART0, c); +} +/****************************************************************************** + * FunctionName : uart0_tx_buffer + * Description : use uart0 to transfer buffer + * Parameters : uint8 *buf - point to send buffer + * uint16 len - buffer len + * Returns : +*******************************************************************************/ +void ICACHE_FLASH_ATTR +uart0_tx_buffer(char *buf, uint16 len) +{ + uint16 i; + + for (i = 0; i < len; i++) + { + uart_tx_one_char(UART0, buf[i]); + } +} + +/****************************************************************************** + * FunctionName : uart0_sendStr + * Description : use uart0 to transfer buffer + * Parameters : uint8 *buf - point to send buffer + * uint16 len - buffer len + * Returns : +*******************************************************************************/ +void ICACHE_FLASH_ATTR +uart0_sendStr(const char *str) +{ + while(*str) + { + uart_tx_one_char(UART0, *str++); + } +} + +static uint32 last_frm_err; // time in us when last framing error message was printed + +/****************************************************************************** + * FunctionName : uart0_rx_intr_handler + * Description : Internal used function + * UART0 interrupt handler, add self handle code inside + * Parameters : void *para - point to ETS_UART_INTR_ATTACH's arg + * Returns : NONE +*******************************************************************************/ +static void // must not use ICACHE_FLASH_ATTR ! +uart0_rx_intr_handler(void *para) +{ + // we assume that uart1 has interrupts disabled (it uses the same interrupt vector) + uint8 uart_no = UART0; + const uint32 one_sec = 1000000; // one second in usecs + + // we end up largely ignoring framing errors and we just print a warning every second max + if (READ_PERI_REG(UART_INT_RAW(uart_no)) & UART_FRM_ERR_INT_RAW) { + uint32 now = system_get_time(); + if (last_frm_err == 0 || (now - last_frm_err) > one_sec) { + os_printf("UART framing error (bad baud rate?)\n"); + last_frm_err = now; + } + // clear rx fifo (apparently this is not optional at this point) + SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST); + CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST); + // reset framing error + WRITE_PERI_REG(UART_INT_CLR(UART0), UART_FRM_ERR_INT_CLR); + // once framing errors are gone for 10 secs we forget about having seen them + } else if (last_frm_err != 0 && (system_get_time() - last_frm_err) > 10*one_sec) { + last_frm_err = 0; + } + + if (UART_RXFIFO_FULL_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_FULL_INT_ST) + || UART_RXFIFO_TOUT_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_TOUT_INT_ST)) + { + //DBG_UART("stat:%02X",*(uint8 *)UART_INT_ENA(uart_no)); + ETS_UART_INTR_DISABLE(); + post_usr_task(uart_recvTaskNum, 0); + } +} + +/****************************************************************************** + * FunctionName : uart_recvTask + * Description : system task triggered on receive interrupt, empties FIFO and calls callbacks +*******************************************************************************/ +static void ICACHE_FLASH_ATTR +uart_recvTask(os_event_t *events) +{ + while (READ_PERI_REG(UART_STATUS(UART0)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) { + //WRITE_PERI_REG(0X60000914, 0x73); //WTD // commented out by TvE + + // read a buffer-full from the uart + uint16 length = 0; + char buf[128]; + while ((READ_PERI_REG(UART_STATUS(UART0)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) && + (length < 128)) { + buf[length++] = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; + } + //DBG_UART("%d ix %d\n", system_get_time(), length); + + for (int i=0; i wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + + +//These routines can also be tested by comping them in with the espfstest tool. This +//simplifies debugging, but needs some slightly different headers. The #ifdef takes +//care of that. + +#ifdef __ets__ +//esp build +#include +#else +//Test build +#include +#include +#include +#include +#define os_malloc malloc +#define os_free free +#define os_memcpy memcpy +#define os_strncmp strncmp +#define os_strcmp strcmp +#define os_strcpy strcpy +#define os_printf printf +#define ICACHE_FLASH_ATTR +#endif + +#include "espfsformat.h" +#include "espfs.h" + +static char* espFsData = NULL; + +struct EspFsFile { + EspFsHeader *header; + char decompressor; + int32_t posDecomp; + char *posStart; + char *posComp; + void *decompData; +}; + +/* +Available locations, at least in my flash, with boundaries partially guessed. This +is using 0.9.1/0.9.2 SDK on a not-too-new module. +0x00000 (0x10000): Code/data (RAM data?) +0x10000 (0x02000): Gets erased by something? +0x12000 (0x2E000): Free (filled with zeroes) (parts used by ESPCloud and maybe SSL) +0x40000 (0x20000): Code/data (ROM data?) +0x60000 (0x1C000): Free +0x7c000 (0x04000): Param store +0x80000 - end of flash + +Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All accesses +*must* be aligned 32-bit accesses. Reading a short, byte or unaligned word will result in +a memory exception, crashing the program. +*/ + +EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) { + // base address must be aligned to 4 bytes + if (((int)flashAddress & 3) != 0) { + return ESPFS_INIT_RESULT_BAD_ALIGN; + } + + // check if there is valid header at address + EspFsHeader testHeader; + os_memcpy(&testHeader, flashAddress, sizeof(EspFsHeader)); + if (testHeader.magic != ESPFS_MAGIC) { + return ESPFS_INIT_RESULT_NO_IMAGE; + } + + espFsData = (char *)flashAddress; + return ESPFS_INIT_RESULT_OK; +} + +//Copies len bytes over from dst to src, but does it using *only* +//aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works. + +//ToDo: perhaps os_memcpy also does unaligned accesses? +#ifdef __ets__ +void ICACHE_FLASH_ATTR memcpyAligned(char *dst, char *src, int len) { + int x; + int w, b; + for (x=0; x>0); + if (b==1) *dst=(w>>8); + if (b==2) *dst=(w>>16); + if (b==3) *dst=(w>>24); + dst++; src++; + } +} +#else +#define memcpyAligned memcpy +#endif + +// Returns flags of opened file. +int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { + if (fh == NULL) { +#ifdef ESPFS_DBG + os_printf("File handle not ready\n"); +#endif + return -1; + } + + int8_t flags; + memcpyAligned((char*)&flags, (char*)&fh->header->flags, 1); + return (int)flags; +} + +//Open a file and return a pointer to the file desc struct. +EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { + if (espFsData == NULL) { +#ifdef ESPFS_DBG + os_printf("Call espFsInit first!\n"); +#endif + return NULL; + } + char *p=espFsData; + char *hpos; + char namebuf[256]; + EspFsHeader h; + EspFsFile *r; + //Strip initial slashes + while(fileName[0]=='/') fileName++; + //Go find that file! + while(1) { + hpos=p; + //Grab the next file header. + os_memcpy(&h, p, sizeof(EspFsHeader)); + if (h.magic!=ESPFS_MAGIC) { +#ifdef ESPFS_DBG + os_printf("Magic mismatch. EspFS image broken.\n"); +#endif + return NULL; + } + if (h.flags&FLAG_LASTFILE) { + //os_printf("End of image.\n"); + return NULL; + } + //Grab the name of the file. + p+=sizeof(EspFsHeader); + os_memcpy(namebuf, p, sizeof(namebuf)); +// os_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n", +// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags); + if (os_strcmp(namebuf, fileName)==0) { + //Yay, this is the file we need! + p+=h.nameLen; //Skip to content. + r=(EspFsFile *)os_malloc(sizeof(EspFsFile)); //Alloc file desc mem + //os_printf("Alloc %p[%d]\n", r, sizeof(EspFsFile)); + if (r==NULL) return NULL; + r->header=(EspFsHeader *)hpos; + r->decompressor=h.compression; + r->posComp=p; + r->posStart=p; + r->posDecomp=0; + if (h.compression==COMPRESS_NONE) { + r->decompData=NULL; + } else { +#ifdef ESPFS_DBG + os_printf("Invalid compression: %d\n", h.compression); +#endif + return NULL; + } + return r; + } + //We don't need this file. Skip name and file + p+=h.nameLen+h.fileLenComp; + if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val + } +} + +//Read len bytes from the given file into buff. Returns the actual amount of bytes read. +int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { + int flen, fdlen; + if (fh==NULL) return 0; + //Cache file length. + memcpyAligned((char*)&flen, (char*)&fh->header->fileLenComp, 4); + memcpyAligned((char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4); + //Do stuff depending on the way the file is compressed. + if (fh->decompressor==COMPRESS_NONE) { + int toRead; + toRead=flen-(fh->posComp-fh->posStart); + if (len>toRead) len=toRead; +// os_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp); + memcpyAligned(buff, fh->posComp, len); + fh->posDecomp+=len; + fh->posComp+=len; +// os_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp); + return len; + } + return 0; +} + +//Close the file. +void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { + if (fh==NULL) return; + //os_printf("Freed %p\n", fh); + os_free(fh); +} + + + diff --git a/espfs/espfs.h b/espfs/espfs.h new file mode 100644 index 0000000..c8e13e7 --- /dev/null +++ b/espfs/espfs.h @@ -0,0 +1,19 @@ +#ifndef ESPFS_H +#define ESPFS_H + +typedef enum { + ESPFS_INIT_RESULT_OK, + ESPFS_INIT_RESULT_NO_IMAGE, + ESPFS_INIT_RESULT_BAD_ALIGN, +} EspFsInitResult; + +typedef struct EspFsFile EspFsFile; + +EspFsInitResult espFsInit(void *flashAddress); +EspFsFile *espFsOpen(char *fileName); +int espFsFlags(EspFsFile *fh); +int espFsRead(EspFsFile *fh, char *buff, int len); +void espFsClose(EspFsFile *fh); + + +#endif \ No newline at end of file diff --git a/espfs/espfsformat.h b/espfs/espfsformat.h new file mode 100644 index 0000000..8ce5549 --- /dev/null +++ b/espfs/espfsformat.h @@ -0,0 +1,33 @@ +#ifndef ESPROFSFORMAT_H +#define ESPROFSFORMAT_H + +/* +Stupid cpio-like tool to make read-only 'filesystems' that live on the flash SPI chip of the module. +Can (will) use lzf compression (when I come around to it) to make shit quicker. Aligns names, files, +headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself +when trying to do a <4byte or unaligned read. +*/ + +/* +The idea 'borrows' from cpio: it's basically a concatenation of {header, filename, file} data. +Header, filename and file data is 32-bit aligned. The last file is indicated by data-less header +with the FLAG_LASTFILE flag set. +*/ + + +#define FLAG_LASTFILE (1<<0) +#define FLAG_GZIP (1<<1) +#define COMPRESS_NONE 0 +#define COMPRESS_HEATSHRINK 1 +#define ESPFS_MAGIC 0x73665345 + +typedef struct { + int32_t magic; + int8_t flags; + int8_t compression; + int16_t nameLen; + int32_t fileLenComp; + int32_t fileLenDecomp; +} __attribute__((packed)) EspFsHeader; + +#endif \ No newline at end of file diff --git a/espfs/mkespfsimage/Makefile b/espfs/mkespfsimage/Makefile new file mode 100644 index 0000000..2980331 --- /dev/null +++ b/espfs/mkespfsimage/Makefile @@ -0,0 +1,55 @@ +GZIP_COMPRESSION ?= no + +ifeq ($(OS),Windows_NT) + +TARGET = mkespfsimage.exe + +CC = gcc +LD = $(CC) +CFLAGS=-c -I.. -Imman-win32 -std=gnu99 +LDFLAGS=-Lmman-win32 -lmman + +ifeq ("$(GZIP_COMPRESSION)","yes") +CFLAGS += -DESPFS_GZIP +LDFLAGS += -lz +endif + +OBJECTS = main.o + +all: libmman $(TARGET) + +libmman: + $(Q) make -C mman-win32 + +$(TARGET): $(OBJECTS) + $(LD) -o $@ $^ $(LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(OBJECTS) $(TARGET) ./mman-win32/libmman.a ./mman-win32/mman.o + +.PHONY: all clean + +else + +CFLAGS=-I.. -std=gnu99 +ifeq ("$(GZIP_COMPRESSION)","yes") +CFLAGS += -DESPFS_GZIP +endif + +OBJS=main.o +TARGET=mkespfsimage + +$(TARGET): $(OBJS) +ifeq ("$(GZIP_COMPRESSION)","yes") + $(CC) -o $@ $^ -lz +else + $(CC) -o $@ $^ +endif + +clean: + rm -f $(TARGET) $(OBJS) + +endif \ No newline at end of file diff --git a/espfs/mkespfsimage/main.c b/espfs/mkespfsimage/main.c new file mode 100644 index 0000000..be3aeb7 --- /dev/null +++ b/espfs/mkespfsimage/main.c @@ -0,0 +1,310 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "espfs.h" +#ifdef __MINGW32__ +#include "mman-win32/mman.h" +#else +#include +#endif +#ifdef __WIN32__ +#include +#else +#include +#endif +#include "espfsformat.h" + +//Gzip +#ifdef ESPFS_GZIP +// If compiler complains about missing header, try running "sudo apt-get install zlib1g-dev" +// to install missing package. +#include +#endif + + + +//Routines to convert host format to the endianness used in the xtensa +short htoxs(short in) { + char r[2]; + r[0]=in; + r[1]=in>>8; + return *((short *)r); +} + +int htoxl(int in) { + unsigned char r[4]; + r[0]=in; + r[1]=in>>8; + r[2]=in>>16; + r[3]=in>>24; + return *((int *)r); +} + +#ifdef ESPFS_GZIP +size_t compressGzip(char *in, int insize, char *out, int outsize, int level) { + z_stream stream; + int zresult; + + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + stream.next_in = in; + stream.avail_in = insize; + stream.next_out = out; + stream.avail_out = outsize; + // 31 -> 15 window bits + 16 for gzip + zresult = deflateInit2 (&stream, level, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY); + if (zresult != Z_OK) { + fprintf(stderr, "DeflateInit2 failed with code %d\n", zresult); + exit(1); + } + + zresult = deflate(&stream, Z_FINISH); + if (zresult != Z_STREAM_END) { + fprintf(stderr, "Deflate failed with code %d\n", zresult); + exit(1); + } + + zresult = deflateEnd(&stream); + if (zresult != Z_OK) { + fprintf(stderr, "DeflateEnd failed with code %d\n", zresult); + exit(1); + } + + return stream.total_out; +} + +char **gzipExtensions = NULL; + +int shouldCompressGzip(char *name) { + char *ext = name + strlen(name); + while (*ext != '.') { + ext--; + if (ext < name) { + // no dot in file name -> no extension -> nothing to match against + return 0; + } + } + ext++; + + int i = 0; + while (gzipExtensions[i] != NULL) { + if (strcmp(ext,gzipExtensions[i]) == 0) { + return 1; + } + i++; + } + + return 0; +} + +int parseGzipExtensions(char *input) { + char *token; + char *extList = input; + int count = 2; // one for first element, second for terminator + + // count elements + while (*extList != 0) { + if (*extList == ',') count++; + extList++; + } + + // split string + extList = input; + gzipExtensions = malloc(count * sizeof(char*)); + count = 0; + token = strtok(extList, ","); + while (token) { + gzipExtensions[count++] = token; + token = strtok(NULL, ","); + } + // terminate list + gzipExtensions[count] = NULL; + + return 1; +} +#endif + +int handleFile(int f, char *name, int compression, int level, char **compName, off_t *csizePtr) { + char *fdat, *cdat; + off_t size, csize; + EspFsHeader h; + int nameLen; + int8_t flags = 0; + size=lseek(f, 0, SEEK_END); + fdat=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0); + if (fdat==MAP_FAILED) { + perror("mmap"); + return 0; + } + +#ifdef ESPFS_GZIP + if (shouldCompressGzip(name)) { + csize = size*3; + if (csize<100) // gzip has some headers that do not fit when trying to compress small files + csize = 100; // enlarge buffer if this is the case + cdat=malloc(csize); + csize=compressGzip(fdat, size, cdat, csize, level); + compression = COMPRESS_NONE; + flags = FLAG_GZIP; + } else +#endif + if (compression==COMPRESS_NONE) { + csize=size; + cdat=fdat; + } else { + fprintf(stderr, "Unknown compression - %d\n", compression); + exit(1); + } + + if (csize>size) { + //Compressing enbiggened this file. Revert to uncompressed store. + compression=COMPRESS_NONE; + csize=size; + cdat=fdat; + flags=0; + } + + //Fill header data + h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24); + h.flags=flags; + h.compression=compression; + h.nameLen=nameLen=strlen(name)+1; + if (h.nameLen&3) h.nameLen+=4-(h.nameLen&3); //Round to next 32bit boundary + h.nameLen=htoxs(h.nameLen); + h.fileLenComp=htoxl(csize); + h.fileLenDecomp=htoxl(size); + + write(1, &h, sizeof(EspFsHeader)); + write(1, name, nameLen); + while (nameLen&3) { + write(1, "\000", 1); + nameLen++; + } + write(1, cdat, csize); + //Pad out to 32bit boundary + while (csize&3) { + write(1, "\000", 1); + csize++; + } + munmap(fdat, size); + + if (compName != NULL) { + if (h.compression==COMPRESS_NONE) { + if (h.flags & FLAG_GZIP) { + *compName = "gzip"; + } else { + *compName = "none"; + } + } else { + *compName = "unknown"; + } + } + *csizePtr = csize; + return (csize*100)/size; +} + +//Write final dummy header with FLAG_LASTFILE set. +void finishArchive() { + EspFsHeader h; + h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24); + h.flags=FLAG_LASTFILE; + h.compression=COMPRESS_NONE; + h.nameLen=htoxs(0); + h.fileLenComp=htoxl(0); + h.fileLenDecomp=htoxl(0); + write(1, &h, sizeof(EspFsHeader)); +} + +int main(int argc, char **argv) { + int f, x; + char fileName[1024]; + char *realName; + struct stat statBuf; + int serr; + int rate; + int err=0; + int compType; //default compression type - heatshrink + int compLvl=-1; + + compType = COMPRESS_NONE; + + for (x=1; x=x-2) { + compType=atoi(argv[x+1]); + x++; + } else if (strcmp(argv[x], "-l")==0 && argc>=x-2) { + compLvl=atoi(argv[x+1]); + if (compLvl<1 || compLvl>9) err=1; + x++; +#ifdef ESPFS_GZIP + } else if (strcmp(argv[x], "-g")==0 && argc>=x-2) { + if (!parseGzipExtensions(argv[x+1])) err=1; + x++; +#endif + } else { + err=1; + } + } + +#ifdef ESPFS_GZIP + if (gzipExtensions == NULL) { + parseGzipExtensions(strdup("html,css,js,ico")); + } +#endif + + if (err) { + fprintf(stderr, "%s - Program to create espfs images\n", argv[0]); + fprintf(stderr, "Usage: \nfind | %s [-c compressor] [-l compression_level] ", argv[0]); +#ifdef ESPFS_GZIP + fprintf(stderr, "[-g gzipped_extensions] "); +#endif + fprintf(stderr, "> out.espfs\n"); + fprintf(stderr, "Compressors:\n"); + fprintf(stderr, "0 - None(default)\n"); + fprintf(stderr, "\nCompression level: 1 is worst but low RAM usage, higher is better compression \nbut uses more ram on decompression. -1 = compressors default.\n"); +#ifdef ESPFS_GZIP + fprintf(stderr, "\nGzipped extensions: list of comma separated, case sensitive file extensions \nthat will be gzipped. Defaults to 'html,css,js'\n"); +#endif + exit(0); + } + +#ifdef __WIN32__ + setmode(fileno(stdout), _O_BINARY); +#endif + + while(fgets(fileName, sizeof(fileName), stdin)) { + //Kill off '\n' at the end + fileName[strlen(fileName)-1]=0; + //Only include files + serr=stat(fileName, &statBuf); + if ((serr==0) && S_ISREG(statBuf.st_mode)) { + //Strip off './' or '/' madness. + realName=fileName; + if (fileName[0]=='.') realName++; + if (realName[0]=='/') realName++; + f=open(fileName, O_RDONLY); + if (f>0) { + char *compName = "unknown"; + off_t csize; + rate=handleFile(f, realName, compType, compLvl, &compName, &csize); + fprintf(stderr, "%-16s (%3d%%, %s, %4u bytes)\n", realName, rate, compName, (uint32_t)csize); + close(f); + } else { + perror(fileName); + } + } else { + if (serr!=0) { + perror(fileName); + } + } + } + finishArchive(); + return 0; +} + diff --git a/espfs/mkespfsimage/mman-win32/Makefile b/espfs/mkespfsimage/mman-win32/Makefile new file mode 100644 index 0000000..5624c1b --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/Makefile @@ -0,0 +1,48 @@ +# +# mman-win32 (mingw32) Makefile +# +include config.mak + +ifeq ($(BUILD_STATIC),yes) + TARGETS+=libmman.a + INSTALL+=static-install +endif +ifeq ($(BUILD_MSVC),yes) + SHFLAGS+=-Wl,--output-def,libmman.def + INSTALL+=lib-install +endif + +all: $(TARGETS) + +mman.o: mman.c mman.h + $(CC) -o mman.o -c mman.c -Wall -O3 -fomit-frame-pointer + +libmman.a: mman.o + $(AR) cru libmman.a mman.o + $(RANLIB) libmman.a + +static-install: + mkdir -p $(DESTDIR)$(libdir) + cp libmman.a $(DESTDIR)$(libdir) + mkdir -p $(DESTDIR)$(incdir) + cp mman.h $(DESTDIR)$(incdir) + +lib-install: + mkdir -p $(DESTDIR)$(libdir) + cp libmman.lib $(DESTDIR)$(libdir) + +install: $(INSTALL) + +test.exe: test.c mman.c mman.h + $(CC) -o test.exe test.c -L. -lmman + +test: $(TARGETS) test.exe + test.exe + +clean:: + rm -f mman.o libmman.a libmman.def libmman.lib test.exe *.dat + +distclean: clean + rm -f config.mak + +.PHONY: clean distclean install test diff --git a/espfs/mkespfsimage/mman-win32/config.mak b/espfs/mkespfsimage/mman-win32/config.mak new file mode 100644 index 0000000..2d2ae1b --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/config.mak @@ -0,0 +1,11 @@ +# Automatically generated by configure +PREFIX=/mingw +libdir=/mingw/lib +incdir=/mingw/include/sys +AR=ar +CC=gcc +RANLIB=ranlib +STRIP=strip +BUILD_STATIC=yes +BUILD_MSVC= +LIBCMD=echo ignoring lib diff --git a/espfs/mkespfsimage/mman-win32/configure b/espfs/mkespfsimage/mman-win32/configure new file mode 100644 index 0000000..c928f11 --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/configure @@ -0,0 +1,157 @@ +#!/bin/sh +# mmap-win32 configure script +# +# Parts copied from FFmpeg's configure +# + +set_all(){ + value=$1 + shift + for var in $*; do + eval $var=$value + done +} + +enable(){ + set_all yes $* +} + +disable(){ + set_all no $* +} + +enabled(){ + eval test "x\$$1" = "xyes" +} + +disabled(){ + eval test "x\$$1" = "xno" +} + +show_help(){ + echo "Usage: configure [options]" + echo "Options: [defaults in brackets after descriptions]" + echo "All \"enable\" options have \"disable\" counterparts" + echo + echo " --help print this message" + echo " --prefix=PREFIX install in PREFIX [$PREFIX]" + echo " --libdir=DIR install libs in DIR [$PREFIX/lib]" + echo " --incdir=DIR install includes in DIR [$PREFIX/include]" + echo " --enable-static build static libraries [yes]" + echo " --enable-msvc create msvc-compatible import lib [auto]" + echo + echo " --cc=CC use C compiler CC [$cc_default]" + echo " --cross-prefix=PREFIX use PREFIX for compilation tools [$cross_prefix]" + exit 1 +} + +die_unknown(){ + echo "Unknown option \"$1\"." + echo "See $0 --help for available options." + exit 1 +} + +PREFIX="/mingw" +libdir="${PREFIX}/lib" +incdir="${PREFIX}/include/sys" +ar="ar" +cc_default="gcc" +ranlib="ranlib" +strip="strip" + +DEFAULT="msvc +" + +DEFAULT_YES="static + stripping +" + +CMDLINE_SELECT="$DEFAULT + $DEFAULT_NO + $DEFAULT_YES +" + +enable $DEFAULT_YES +disable $DEFAULT_NO + +for opt do + optval="${opt#*=}" + case "$opt" in + --help) + show_help + ;; + --prefix=*) + PREFIX="$optval" + ;; + --libdir=*) + libdir="$optval" + ;; + --incdir=*) + incdir="$optval" + ;; + --cc=*) + cc="$optval" + ;; + --cross-prefix=*) + cross_prefix="$optval" + ;; + --enable-?*|--disable-?*) + eval `echo "$opt" | sed 's/--/action=/;s/-/ option=/;s/-/_/g'` + echo "$CMDLINE_SELECT" | grep -q "^ *$option\$" || die_unknown $opt + $action $option + ;; + *) + die_unknown $opt + ;; + esac +done + +ar="${cross_prefix}${ar}" +cc_default="${cross_prefix}${cc_default}" +ranlib="${cross_prefix}${ranlib}" +strip="${cross_prefix}${strip}" + +if ! test -z $cc; then + cc_default="${cc}" +fi +cc="${cc_default}" + +disabled static && { + echo "At least one library type must be set."; + exit 1; +} + +if enabled msvc; then + lib /? > /dev/null 2>&1 /dev/null || { + echo "MSVC's lib command not found." + echo "Make sure MSVC is installed and its bin folder is in your \$PATH." + exit 1 + } +fi + +if ! enabled stripping; then + strip="echo ignoring strip" +fi + +enabled msvc && libcmd="lib" || libcmd="echo ignoring lib" + +echo "# Automatically generated by configure" > config.mak +echo "PREFIX=$PREFIX" >> config.mak +echo "libdir=$libdir" >> config.mak +echo "incdir=$incdir" >> config.mak +echo "AR=$ar" >> config.mak +echo "CC=$cc" >> config.mak +echo "RANLIB=$ranlib" >> config.mak +echo "STRIP=$strip" >> config.mak +echo "BUILD_STATIC=$static" >> config.mak +echo "BUILD_MSVC=$msvc" >> config.mak +echo "LIBCMD=$libcmd" >> config.mak + +echo "prefix: $PREFIX" +echo "libdir: $libdir" +echo "incdir: $incdir" +echo "ar: $ar" +echo "cc: $cc" +echo "ranlib: $ranlib" +echo "strip: $strip" +echo "static: $static" diff --git a/espfs/mkespfsimage/mman-win32/mman.c b/espfs/mkespfsimage/mman-win32/mman.c new file mode 100644 index 0000000..486ed94 --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/mman.c @@ -0,0 +1,180 @@ + +#include +#include +#include + +#include "mman.h" + +#ifndef FILE_MAP_EXECUTE +#define FILE_MAP_EXECUTE 0x0020 +#endif /* FILE_MAP_EXECUTE */ + +static int __map_mman_error(const DWORD err, const int deferr) +{ + if (err == 0) + return 0; + //TODO: implement + return err; +} + +static DWORD __map_mmap_prot_page(const int prot) +{ + DWORD protect = 0; + + if (prot == PROT_NONE) + return protect; + + if ((prot & PROT_EXEC) != 0) + { + protect = ((prot & PROT_WRITE) != 0) ? + PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; + } + else + { + protect = ((prot & PROT_WRITE) != 0) ? + PAGE_READWRITE : PAGE_READONLY; + } + + return protect; +} + +static DWORD __map_mmap_prot_file(const int prot) +{ + DWORD desiredAccess = 0; + + if (prot == PROT_NONE) + return desiredAccess; + + if ((prot & PROT_READ) != 0) + desiredAccess |= FILE_MAP_READ; + if ((prot & PROT_WRITE) != 0) + desiredAccess |= FILE_MAP_WRITE; + if ((prot & PROT_EXEC) != 0) + desiredAccess |= FILE_MAP_EXECUTE; + + return desiredAccess; +} + +void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) +{ + HANDLE fm, h; + + void * map = MAP_FAILED; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4293) +#endif + + const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)off : (DWORD)(off & 0xFFFFFFFFL); + const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL); + const DWORD protect = __map_mmap_prot_page(prot); + const DWORD desiredAccess = __map_mmap_prot_file(prot); + + const off_t maxSize = off + (off_t)len; + + const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL); + const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL); + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + errno = 0; + + if (len == 0 + /* Unsupported flag combinations */ + || (flags & MAP_FIXED) != 0 + /* Usupported protection combinations */ + || prot == PROT_EXEC) + { + errno = EINVAL; + return MAP_FAILED; + } + + h = ((flags & MAP_ANONYMOUS) == 0) ? + (HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE; + + if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE) + { + errno = EBADF; + return MAP_FAILED; + } + + fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL); + + if (fm == NULL) + { + errno = __map_mman_error(GetLastError(), EPERM); + return MAP_FAILED; + } + + map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len); + + CloseHandle(fm); + + if (map == NULL) + { + errno = __map_mman_error(GetLastError(), EPERM); + return MAP_FAILED; + } + + return map; +} + +int munmap(void *addr, size_t len) +{ + if (UnmapViewOfFile(addr)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int mprotect(void *addr, size_t len, int prot) +{ + DWORD newProtect = __map_mmap_prot_page(prot); + DWORD oldProtect = 0; + + if (VirtualProtect(addr, len, newProtect, &oldProtect)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int msync(void *addr, size_t len, int flags) +{ + if (FlushViewOfFile(addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int mlock(const void *addr, size_t len) +{ + if (VirtualLock((LPVOID)addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int munlock(const void *addr, size_t len) +{ + if (VirtualUnlock((LPVOID)addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} diff --git a/espfs/mkespfsimage/mman-win32/mman.h b/espfs/mkespfsimage/mman-win32/mman.h new file mode 100644 index 0000000..ffa3748 --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/mman.h @@ -0,0 +1,55 @@ +/* + * sys/mman.h + * mman-win32 + */ + +#ifndef _SYS_MMAN_H_ +#define _SYS_MMAN_H_ + +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. +#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. +#endif + +/* All the headers include this file. */ +#ifndef _MSC_VER +#include <_mingw.h> +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_FILE 0 +#define MAP_SHARED 1 +#define MAP_PRIVATE 2 +#define MAP_TYPE 0xf +#define MAP_FIXED 0x10 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS + +#define MAP_FAILED ((void *)-1) + +/* Flags for msync. */ +#define MS_ASYNC 1 +#define MS_SYNC 2 +#define MS_INVALIDATE 4 + +void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); +int munmap(void *addr, size_t len); +int mprotect(void *addr, size_t len, int prot); +int msync(void *addr, size_t len, int flags); +int mlock(const void *addr, size_t len); +int munlock(const void *addr, size_t len); + +#ifdef __cplusplus +}; +#endif + +#endif /* _SYS_MMAN_H_ */ diff --git a/espfs/mkespfsimage/mman-win32/test.c b/espfs/mkespfsimage/mman-win32/test.c new file mode 100644 index 0000000..9374b9f --- /dev/null +++ b/espfs/mkespfsimage/mman-win32/test.c @@ -0,0 +1,235 @@ + +#include "mman.h" + +#include +#include +#include + +#ifndef NULL +#define NULL (void*)0 +#endif + +const char* map_file_name = "map_file.dat"; + +int test_anon_map_readwrite() +{ + void* map = mmap(NULL, 1024, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + { + printf("mmap (MAP_ANONYMOUS, PROT_READ | PROT_WRITE) returned unexpected error: %d\n", errno); + return -1; + } + + *((unsigned char*)map) = 1; + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap (MAP_ANONYMOUS, PROT_READ | PROT_WRITE) returned unexpected error: %d\n", errno); + + return result; +} + +int test_anon_map_readonly() +{ + void* map = mmap(NULL, 1024, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + { + printf("mmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); + return -1; + } + + *((unsigned char*)map) = 1; + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); + + return result; +} + +int test_anon_map_writeonly() +{ + void* map = mmap(NULL, 1024, PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + { + printf("mmap (MAP_ANONYMOUS, PROT_WRITE) returned unexpected error: %d\n", errno); + return -1; + } + + *((unsigned char*)map) = 1; + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap (MAP_ANONYMOUS, PROT_WRITE) returned unexpected error: %d\n", errno); + + return result; +} + +int test_anon_map_readonly_nowrite() +{ + void* map = mmap(NULL, 1024, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (map == MAP_FAILED) + { + printf("mmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); + return -1; + } + + if (*((unsigned char*)map) != 0) + printf("test_anon_map_readonly_nowrite (MAP_ANONYMOUS, PROT_READ) returned unexpected value: %d\n", + (int)*((unsigned char*)map)); + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); + + return result; +} + +int test_file_map_readwrite() +{ + mode_t mode = S_IRUSR | S_IWUSR; + int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); + + void* map = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); + if (map == MAP_FAILED) + { + printf("mmap returned unexpected error: %d\n", errno); + return -1; + } + + *((unsigned char*)map) = 1; + + int result = munmap(map, 1024); + + if (result != 0) + printf("munmap returned unexpected error: %d\n", errno); + + close(o); + + /*TODO: get file info and content and compare it with the sources conditions */ + unlink(map_file_name); + + return result; +} + +int test_file_map_mlock_munlock() +{ + const size_t map_size = 1024; + + int result = 0; + mode_t mode = S_IRUSR | S_IWUSR; + int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); + if (o == -1) + { + printf("unable to create file %s: %d\n", map_file_name, errno); + return -1; + } + + void* map = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); + if (map == MAP_FAILED) + { + printf("mmap returned unexpected error: %d\n", errno); + result = -1; + goto done_close; + } + + if (mlock(map, map_size) != 0) + { + printf("mlock returned unexpected error: %d\n", errno); + result = -1; + goto done_munmap; + } + + *((unsigned char*)map) = 1; + + if (munlock(map, map_size) != 0) + { + printf("munlock returned unexpected error: %d\n", errno); + result = -1; + } + +done_munmap: + result = munmap(map, map_size); + + if (result != 0) + printf("munmap returned unexpected error: %d\n", errno); + +done_close: + close(o); + + unlink(map_file_name); +done: + return result; +} + +int test_file_map_msync() +{ + const size_t map_size = 1024; + + int result = 0; + mode_t mode = S_IRUSR | S_IWUSR; + int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); + if (o == -1) + { + printf("unable to create file %s: %d\n", map_file_name, errno); + return -1; + } + + void* map = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); + if (map == MAP_FAILED) + { + printf("mmap returned unexpected error: %d\n", errno); + result = -1; + goto done_close; + } + + *((unsigned char*)map) = 1; + + if (msync(map, map_size, MS_SYNC) != 0) + { + printf("msync returned unexpected error: %d\n", errno); + result = -1; + } + + result = munmap(map, map_size); + + if (result != 0) + printf("munmap returned unexpected error: %d\n", errno); + +done_close: + close(o); + + unlink(map_file_name); +done: + return result; +} + +#define EXEC_TEST(name) \ + if (name() != 0) { result = -1; printf( #name ": fail\n"); } \ + else { printf(#name ": pass\n"); } + +int main() +{ + int result = 0; + + EXEC_TEST(test_anon_map_readwrite); + //NOTE: this test must cause an access violation exception + //EXEC_TEST(test_anon_map_readonly); + EXEC_TEST(test_anon_map_readonly_nowrite); + EXEC_TEST(test_anon_map_writeonly); + + EXEC_TEST(test_file_map_readwrite); + EXEC_TEST(test_file_map_mlock_munlock); + EXEC_TEST(test_file_map_msync); + //TODO: EXEC_TEST(test_file_map_mprotect); + + return result; +} diff --git a/espmake.cmd b/espmake.cmd new file mode 100644 index 0000000..42e25a6 --- /dev/null +++ b/espmake.cmd @@ -0,0 +1,7 @@ +@echo off + +REM remove automatic created obj folder +rd obj /S /Q >nul 2>&1 + +PATH=%PATH%;C:\Espressif\xtensa-lx106-elf\bin;C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\espressif\git-bin;C:\espressif\java-bin;C:\Python27 +make -f Makefile %1 %2 %3 %4 %5 \ No newline at end of file diff --git a/html/favicon.ico b/html/favicon.ico new file mode 100755 index 0000000..136cbe6 Binary files /dev/null and b/html/favicon.ico differ diff --git a/html/head- b/html/head- new file mode 100644 index 0000000..2f7b423 --- /dev/null +++ b/html/head- @@ -0,0 +1,10 @@ + + + Happy Bubbles Tech - NFC + + + + + + +
diff --git a/html/hippo.svg b/html/hippo.svg new file mode 100644 index 0000000..92536c0 --- /dev/null +++ b/html/hippo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/html/home.html b/html/home.html new file mode 100644 index 0000000..c9b5466 --- /dev/null +++ b/html/home.html @@ -0,0 +1,79 @@ +
+
+
+

Happy Bubbles NFC

+

+
+ +
+
+ +
+
+

System overview

+
+ + + + + + + + +
+
+ +
+
+

System details

+
+ + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/html/jl-400x110.png- b/html/jl-400x110.png- new file mode 100755 index 0000000..92d2d9e Binary files /dev/null and b/html/jl-400x110.png- differ diff --git a/html/nfc.html b/html/nfc.html new file mode 100644 index 0000000..0ccb769 --- /dev/null +++ b/html/nfc.html @@ -0,0 +1,63 @@ +
+
+

NFC

+
+ +
+
+
+
+

NFC +
+

+ +
+
+
+
+
+

The NFC support will send NFC tags touched to the Happy Bubbles NFC module to the selected HTTP URL. +

+

+ The payload to the HTTP URL will be JSON sent over a POST request.
+ The JSON will look like this: +

+

+ {"device_id": "CONFIG-DEVICE-ID", + "tag_uid": "THE-TAGS-UUID", + "counter": 3, + "hash": "7cac4442e6caf834ce20a359ff7ee9ff34525691"} +

+

+ The device_id is the one set in the config. The tag's UUID is the unique ID from each RFID tag. The counter will increment on every request, this is used as a nonce for optional security. The 'hash' is an SHA1-based HMAC, its input is the string of the device-id, tag-ID, and counter all concatenated together with no spaces. It is then HMAC-SHA1 hashed against the secret from the config. You can use this on the backend to ensure no one is spoofing tag-hits, it's optional. +

+
+
+ +
+
+ + + + + diff --git a/html/nfc.js b/html/nfc.js new file mode 100644 index 0000000..adbf2bb --- /dev/null +++ b/html/nfc.js @@ -0,0 +1,63 @@ +//===== NFC cards + +function changeNFC(e) { + e.preventDefault(); + var url = "nfc?1=1"; + var i, inputs = document.querySelectorAll('#nfc-form input'); + for (i = 0; i < inputs.length; i++) { + if (inputs[i].type != "checkbox") + url += "&" + inputs[i].name + "=" + inputs[i].value; + }; + + hideWarning(); + var cb = $("#nfc-button"); + addClass(cb, 'pure-button-disabled'); + ajaxSpin("POST", url, function (resp) { + showNotification("NFC updated"); + removeClass(cb, 'pure-button-disabled'); + }, function (s, st) { + showWarning("Error: " + st); + removeClass(cb, 'pure-button-disabled'); + window.setTimeout(fetchNFC, 100); + }); +} + +function displayNFC(data) { + Object.keys(data).forEach(function (v) { + el = $("#" + v); + if (el != null) { + if (el.nodeName === "INPUT") el.value = data[v]; + else el.innerHTML = data[v]; + return; + } + el = document.querySelector('input[name="' + v + '"]'); + if (el != null) { + if (el.type == "checkbox") el.checked = data[v] > 0; + else el.value = data[v]; + } + }); + $("#nfc-spinner").setAttribute("hidden", ""); + $("#nfc-form").removeAttribute("hidden"); + + var i, inputs = $("input"); + for (i = 0; i < inputs.length; i++) { + if (inputs[i].type == "checkbox") + inputs[i].onclick = function () { setNFC(this.name, this.checked) }; + } +} + +function fetchNFC() { + ajaxJson("GET", "/nfc", displayNFC, function () { + window.setTimeout(fetchNFC, 1000); + }); +} + +function setNFC(name, v) { + ajaxSpin("POST", "/nfc?" + name + "=" + (v ? 1 : 0), function () { + var n = name.replace("-enable", ""); + showNotification(n + " is now " + (v ? "enabled" : "disabled")); + }, function () { + showWarning("Enable/disable failed"); + window.setTimeout(fetchNFC, 100); + }); +} diff --git a/html/pure.css b/html/pure.css new file mode 100644 index 0000000..812c53a --- /dev/null +++ b/html/pure.css @@ -0,0 +1,1383 @@ +/*! +Pure v0.6.0 +Copyright 2014 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +https://github.com/yahoo/pure/blob/master/LICENSE.md +*/ +/*! +normalize.css v^3.0 | MIT License | git.io/normalize +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} + +/*csslint important:false*/ + +/* ========================================================================== + Pure Base Extras + ========================================================================== */ + +/** + * Extra rules that Pure adds on top of Normalize.css + */ + +/** + * Always hide an element when it has the `hidden` HTML attribute. + */ + +.hidden, +[hidden] { + display: none !important; +} + +/** + * Add this class to an image to make it fit within it's fluid parent wrapper while maintaining + * aspect ratio. + */ +.pure-img { + max-width: 100%; + height: auto; + display: block; +} + +/*csslint regex-selectors:false, known-properties:false, duplicate-properties:false*/ + +.pure-g { + letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ + *letter-spacing: normal; /* reset IE < 8 */ + *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */ + text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ + + /* + Sets the font stack to fonts known to work properly with the above letter + and word spacings. See: https://github.com/yahoo/pure/issues/41/ + + The following font stack makes Pure Grids work on all known environments. + + * FreeSans: Ships with many Linux distros, including Ubuntu + + * Arimo: Ships with Chrome OS. Arimo has to be defined before Helvetica and + Arial to get picked up by the browser, even though neither is available + in Chrome OS. + + * Droid Sans: Ships with all versions of Android. + + * Helvetica, Arial, sans-serif: Common font stack on OS X and Windows. + */ + font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif; + + /* + Use flexbox when possible to avoid `letter-spacing` side-effects. + + NOTE: Firefox (as of 25) does not currently support flex-wrap, so the + `-moz-` prefix version is omitted. + */ + + display: -webkit-flex; + -webkit-flex-flow: row wrap; + + /* IE10 uses display: flexbox */ + display: -ms-flexbox; + -ms-flex-flow: row wrap; + + /* Prevents distributing space between rows */ + -ms-align-content: flex-start; + -webkit-align-content: flex-start; + align-content: flex-start; +} + +/* Opera as of 12 on Windows needs word-spacing. + The ".opera-only" selector is used to prevent actual prefocus styling + and is not required in markup. +*/ +.opera-only :-o-prefocus, +.pure-g { + word-spacing: -0.43em; +} + +.pure-u { + display: inline-block; + *display: inline; /* IE < 8: fake inline-block */ + zoom: 1; + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto; +} + +/* +Resets the font family back to the OS/browser's default sans-serif font, +this the same font stack that Normalize.css sets for the `body`. +*/ +.pure-g [class *= "pure-u"] { + font-family: sans-serif; +} + +/* Pure 4 column grid */ + +.pure-u-1, +.pure-u-1-1, +.pure-u-1-2, +.pure-u-1-4, +.pure-u-2-4, +.pure-u-3-4, +.pure-u-4-4 { + display: inline-block; + *display: inline; + zoom: 1; + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto; +} + +.pure-u-1-4 { + width: 25%; + *width: 24.9690%; +} + +.pure-u-1-2, +.pure-u-2-4 { + width: 50%; + *width: 49.9690%; +} + +.pure-u-3-4 { + width: 75%; + *width: 74.9690%; +} + +.pure-u-1, +.pure-u-1-1, +.pure-u-4-4 { + width: 100%; +} + +/* responsive grid */ + +@media screen and (min-width: 48em) { + .pure-u-md-1, + .pure-u-md-1-1, + .pure-u-md-1-2, + .pure-u-md-1-4, + .pure-u-md-2-4, + .pure-u-md-3-4, + .pure-u-md-4-4 { + display: inline-block; + *display: inline; + zoom: 1; + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto; + } + + .pure-u-md-1-4 { + width: 25%; + *width: 24.9690%; + } + + .pure-u-md-1-2, + .pure-u-md-2-4 { + width: 50%; + *width: 49.9690%; + } + + .pure-u-md-3-4 { + width: 75%; + *width: 74.9690%; + } + + .pure-u-md-1, + .pure-u-md-1-1, + .pure-u-md-4-4 { + width: 100%; + } +} + +@media screen and (min-width: 64em) { + .pure-u-lg-1, + .pure-u-lg-1-1, + .pure-u-lg-1-2, + .pure-u-lg-1-4, + .pure-u-lg-2-4, + .pure-u-lg-3-4, + .pure-u-lg-4-4 { + display: inline-block; + *display: inline; + zoom: 1; + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto; + } + + .pure-u-lg-1-4 { + width: 25%; + *width: 24.9690%; + } + + .pure-u-lg-1-2, + .pure-u-lg-2-4 { + width: 50%; + *width: 49.9690%; + } + + .pure-u-lg-3-4 { + width: 75%; + *width: 74.9690%; + } + + .pure-u-lg-1, + .pure-u-lg-1-1, + .pure-u-lg-4-4 { + width: 100%; + } +} + +/* Pure buttons */ + +.pure-button { + /* Structure */ + display: inline-block; + zoom: 1; + line-height: normal; + white-space: nowrap; + vertical-align: middle; + text-align: center; + cursor: pointer; + -webkit-user-drag: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* Firefox: Get rid of the inner focus border */ +.pure-button::-moz-focus-inner { + padding: 0; + border: 0; +} + +/*csslint outline-none:false*/ + +.pure-button { + font-family: inherit; + font-size: 100%; + padding: 0.5em 1em; + color: #444; /* rgba not supported (IE 8) */ + color: rgba(0, 0, 0, 0.80); /* rgba supported */ + border: 1px solid #999; /*IE 6/7/8*/ + border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/ + background-color: #E6E6E6; + text-decoration: none; + border-radius: 2px; +} + +.pure-button-hover, +.pure-button:hover, +.pure-button:focus { + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000',GradientType=0); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(40%, rgba(0,0,0, 0.05)), to(rgba(0,0,0, 0.10))); + background-image: -webkit-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); + background-image: -moz-linear-gradient(top, rgba(0,0,0, 0.05) 0%, rgba(0,0,0, 0.10)); + background-image: -o-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); + background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); +} +.pure-button:focus { + outline: 0; +} +.pure-button-active, +.pure-button:active { + box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; + border-color: #000\9; +} + +.pure-button[disabled], +.pure-button-disabled, +.pure-button-disabled:hover, +.pure-button-disabled:focus, +.pure-button-disabled:active { + border: none; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + filter: alpha(opacity=40); + -khtml-opacity: 0.40; + -moz-opacity: 0.40; + opacity: 0.40; + cursor: not-allowed; + box-shadow: none; +} + +.pure-button-hidden { + display: none; +} + +/* Firefox: Get rid of the inner focus border */ +.pure-button::-moz-focus-inner{ + padding: 0; + border: 0; +} + +.pure-button-primary, +.pure-button-selected, +a.pure-button-primary, +a.pure-button-selected { + background-color: rgb(0, 120, 231); + color: #fff; +} + +/*csslint box-model:false*/ +/* +Box-model set to false because we're setting a height on select elements, which +also have border and padding. This is done because some browsers don't render +the padding. We explicitly set the box-model for select elements to border-box, +so we can ignore the csslint warning. +*/ + +.pure-form input[type="text"], +.pure-form input[type="password"], +.pure-form input[type="email"], +.pure-form input[type="url"], +.pure-form input[type="date"], +.pure-form input[type="month"], +.pure-form input[type="time"], +.pure-form input[type="datetime"], +.pure-form input[type="datetime-local"], +.pure-form input[type="week"], +.pure-form input[type="number"], +.pure-form input[type="search"], +.pure-form input[type="tel"], +.pure-form input[type="color"], +.pure-form select, +.pure-form textarea { + padding: 0.5em 0.6em; + display: inline-block; + border: 1px solid #ccc; + box-shadow: inset 0 1px 3px #ddd; + border-radius: 4px; + vertical-align: middle; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* +Need to separate out the :not() selector from the rest of the CSS 2.1 selectors +since IE8 won't execute CSS that contains a CSS3 selector. +*/ +.pure-form input:not([type]) { + padding: 0.5em 0.6em; + display: inline-block; + border: 1px solid #ccc; + box-shadow: inset 0 1px 3px #ddd; + border-radius: 4px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + + +/* Chrome (as of v.32/34 on OS X) needs additional room for color to display. */ +/* May be able to remove this tweak as color inputs become more standardized across browsers. */ +.pure-form input[type="color"] { + padding: 0.2em 0.5em; +} + + +.pure-form input[type="text"]:focus, +.pure-form input[type="password"]:focus, +.pure-form input[type="email"]:focus, +.pure-form input[type="url"]:focus, +.pure-form input[type="date"]:focus, +.pure-form input[type="month"]:focus, +.pure-form input[type="time"]:focus, +.pure-form input[type="datetime"]:focus, +.pure-form input[type="datetime-local"]:focus, +.pure-form input[type="week"]:focus, +.pure-form input[type="number"]:focus, +.pure-form input[type="search"]:focus, +.pure-form input[type="tel"]:focus, +.pure-form input[type="color"]:focus, +.pure-form select:focus, +.pure-form textarea:focus { + outline: 0; + border-color: #129FEA; +} + +/* +Need to separate out the :not() selector from the rest of the CSS 2.1 selectors +since IE8 won't execute CSS that contains a CSS3 selector. +*/ +.pure-form input:not([type]):focus { + outline: 0; + border-color: #129FEA; +} + +.pure-form input[type="file"]:focus, +.pure-form input[type="radio"]:focus, +.pure-form input[type="checkbox"]:focus { + outline: thin solid #129FEA; + outline: 1px auto #129FEA; +} +.pure-form .pure-checkbox, +.pure-form .pure-radio { + margin: 0.5em 0; + display: block; +} + +.pure-form input[type="text"][disabled], +.pure-form input[type="password"][disabled], +.pure-form input[type="email"][disabled], +.pure-form input[type="url"][disabled], +.pure-form input[type="date"][disabled], +.pure-form input[type="month"][disabled], +.pure-form input[type="time"][disabled], +.pure-form input[type="datetime"][disabled], +.pure-form input[type="datetime-local"][disabled], +.pure-form input[type="week"][disabled], +.pure-form input[type="number"][disabled], +.pure-form input[type="search"][disabled], +.pure-form input[type="tel"][disabled], +.pure-form input[type="color"][disabled], +.pure-form select[disabled], +.pure-form textarea[disabled] { + cursor: not-allowed; + background-color: #eaeded; + color: #cad2d3; +} + +/* +Need to separate out the :not() selector from the rest of the CSS 2.1 selectors +since IE8 won't execute CSS that contains a CSS3 selector. +*/ +.pure-form input:not([type])[disabled] { + cursor: not-allowed; + background-color: #eaeded; + color: #cad2d3; +} +.pure-form input[readonly], +.pure-form select[readonly], +.pure-form textarea[readonly] { + background-color: #eee; /* menu hover bg color */ + color: #777; /* menu text color */ + border-color: #ccc; +} + +.pure-form input:focus:invalid, +.pure-form textarea:focus:invalid, +.pure-form select:focus:invalid { + color: #b94a48; + border-color: #e9322d; +} +.pure-form input[type="file"]:focus:invalid:focus, +.pure-form input[type="radio"]:focus:invalid:focus, +.pure-form input[type="checkbox"]:focus:invalid:focus { + outline-color: #e9322d; +} +.pure-form select { + /* Normalizes the height; padding is not sufficient. */ + height: 2.25em; + border: 1px solid #ccc; + background-color: white; +} +.pure-form select[multiple] { + height: auto; +} +.pure-form label { + margin: 0.5em 0 0.2em; +} +.pure-form fieldset { + margin: 0; + padding: 0.35em 0 0.75em; + border: 0; +} +.pure-form legend { + display: block; + width: 100%; + padding: 0.3em 0; + margin-bottom: 0.3em; + color: #333; + border-bottom: 1px solid #e5e5e5; +} + +.pure-form-stacked input[type="text"], +.pure-form-stacked input[type="password"], +.pure-form-stacked input[type="email"], +.pure-form-stacked input[type="url"], +.pure-form-stacked input[type="date"], +.pure-form-stacked input[type="month"], +.pure-form-stacked input[type="time"], +.pure-form-stacked input[type="datetime"], +.pure-form-stacked input[type="datetime-local"], +.pure-form-stacked input[type="week"], +.pure-form-stacked input[type="number"], +.pure-form-stacked input[type="search"], +.pure-form-stacked input[type="tel"], +.pure-form-stacked input[type="color"], +.pure-form-stacked input[type="file"], +.pure-form-stacked select, +.pure-form-stacked label, +.pure-form-stacked textarea { + display: block; + margin: 0.25em 0; +} + +/* +Need to separate out the :not() selector from the rest of the CSS 2.1 selectors +since IE8 won't execute CSS that contains a CSS3 selector. +*/ +.pure-form-stacked input:not([type]) { + display: block; + margin: 0.25em 0; +} +.pure-form-aligned input, +.pure-form-aligned textarea, +.pure-form-aligned select, +/* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */ +.pure-form-aligned .pure-help-inline, +.pure-form-message-inline { + display: inline-block; + *display: inline; + *zoom: 1; + vertical-align: middle; +} +.pure-form-aligned textarea { + vertical-align: top; +} + +/* Aligned Forms */ +.pure-form-aligned .pure-control-group { + margin-bottom: 0.5em; +} +.pure-form-aligned .pure-control-group label { + text-align: right; + display: inline-block; + vertical-align: middle; + width: 10em; + margin: 0 1em 0 0; +} +.pure-form-aligned .pure-controls { + margin: 1.5em 0 0 11em; +} + +/* Rounded Inputs */ +.pure-form input.pure-input-rounded, +.pure-form .pure-input-rounded { + border-radius: 2em; + padding: 0.5em 1em; +} + +/* Grouped Inputs */ +.pure-form .pure-group fieldset { + margin-bottom: 10px; +} +.pure-form .pure-group input, +.pure-form .pure-group textarea { + display: block; + padding: 10px; + margin: 0 0 -1px; + border-radius: 0; + position: relative; + top: -1px; +} +.pure-form .pure-group input:focus, +.pure-form .pure-group textarea:focus { + z-index: 3; +} +.pure-form .pure-group input:first-child, +.pure-form .pure-group textarea:first-child { + top: 1px; + border-radius: 4px 4px 0 0; + margin: 0; +} +.pure-form .pure-group input:first-child:last-child, +.pure-form .pure-group textarea:first-child:last-child { + top: 1px; + border-radius: 4px; + margin: 0; +} +.pure-form .pure-group input:last-child, +.pure-form .pure-group textarea:last-child { + top: -2px; + border-radius: 0 0 4px 4px; + margin: 0; +} +.pure-form .pure-group button { + margin: 0.35em 0; +} + +.pure-form .pure-input-1 { + width: 100%; +} +.pure-form .pure-input-2-3 { + width: 66%; +} +.pure-form .pure-input-1-2 { + width: 50%; +} +.pure-form .pure-input-1-3 { + width: 33%; +} +.pure-form .pure-input-1-4 { + width: 25%; +} + +/* Inline help for forms */ +/* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */ +.pure-form .pure-help-inline, +.pure-form-message-inline { + display: inline-block; + padding-left: 0.3em; + color: #666; + vertical-align: middle; + font-size: 0.875em; +} + +/* Block help for forms */ +.pure-form-message { + display: block; + color: #666; + font-size: 0.875em; +} + +@media only screen and (max-width : 480px) { + .pure-form button[type="submit"] { + margin: 0.7em 0 0; + } + + .pure-form input:not([type]), + .pure-form input[type="text"], + .pure-form input[type="password"], + .pure-form input[type="email"], + .pure-form input[type="url"], + .pure-form input[type="date"], + .pure-form input[type="month"], + .pure-form input[type="time"], + .pure-form input[type="datetime"], + .pure-form input[type="datetime-local"], + .pure-form input[type="week"], + .pure-form input[type="number"], + .pure-form input[type="search"], + .pure-form input[type="tel"], + .pure-form input[type="color"], + .pure-form label { + margin-bottom: 0.3em; + display: block; + } + + .pure-group input:not([type]), + .pure-group input[type="text"], + .pure-group input[type="password"], + .pure-group input[type="email"], + .pure-group input[type="url"], + .pure-group input[type="date"], + .pure-group input[type="month"], + .pure-group input[type="time"], + .pure-group input[type="datetime"], + .pure-group input[type="datetime-local"], + .pure-group input[type="week"], + .pure-group input[type="number"], + .pure-group input[type="search"], + .pure-group input[type="tel"], + .pure-group input[type="color"] { + margin-bottom: 0; + } + + .pure-form-aligned .pure-control-group label { + margin-bottom: 0.3em; + text-align: left; + display: block; + width: 100%; + } + + .pure-form-aligned .pure-controls { + margin: 1.5em 0 0 0; + } + + /* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */ + .pure-form .pure-help-inline, + .pure-form-message-inline, + .pure-form-message { + display: block; + font-size: 0.75em; + /* Increased bottom padding to make it group with its related input element. */ + padding: 0.2em 0 0.8em; + } +} + +/*csslint adjoining-classes: false, box-model:false*/ +.pure-menu { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.pure-menu-fixed { + position: fixed; + left: 0; + top: 0; + z-index: 3; +} + +.pure-menu-list, +.pure-menu-item { + position: relative; +} + +.pure-menu-list { + list-style: none; + margin: 0; + padding: 0; +} + +.pure-menu-item { + padding: 0; + margin: 0; + height: 100%; +} + +.pure-menu-link, +.pure-menu-heading { + display: block; + text-decoration: none; + white-space: nowrap; +} + +/* HORIZONTAL MENU */ +.pure-menu-horizontal { + width: 100%; + white-space: nowrap; +} + +.pure-menu-horizontal .pure-menu-list { + display: inline-block; +} + +/* Initial menus should be inline-block so that they are horizontal */ +.pure-menu-horizontal .pure-menu-item, +.pure-menu-horizontal .pure-menu-heading, +.pure-menu-horizontal .pure-menu-separator { + display: inline-block; + *display: inline; + zoom: 1; + vertical-align: middle; +} + +/* Submenus should still be display: block; */ +.pure-menu-item .pure-menu-item { + display: block; +} + +.pure-menu-children { + display: none; + position: absolute; + left: 100%; + top: 0; + margin: 0; + padding: 0; + z-index: 3; +} + +.pure-menu-horizontal .pure-menu-children { + left: 0; + top: auto; + width: inherit; +} + +.pure-menu-allow-hover:hover > .pure-menu-children, +.pure-menu-active > .pure-menu-children { + display: block; + position: absolute; +} + +/* Vertical Menus - show the dropdown arrow */ +.pure-menu-has-children > .pure-menu-link:after { + padding-left: 0.5em; + content: "\25B8"; + font-size: small; +} + +/* Horizontal Menus - show the dropdown arrow */ +.pure-menu-horizontal .pure-menu-has-children > .pure-menu-link:after { + content: "\25BE"; +} + +/* scrollable menus */ +.pure-menu-scrollable { + overflow-y: scroll; + overflow-x: hidden; +} + +.pure-menu-scrollable .pure-menu-list { + display: block; +} + +.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list { + display: inline-block; +} + +.pure-menu-horizontal.pure-menu-scrollable { + white-space: nowrap; + overflow-y: hidden; + overflow-x: auto; + -ms-overflow-style: none; + -webkit-overflow-scrolling: touch; + /* a little extra padding for this style to allow for scrollbars */ + padding: .5em 0; +} + +.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar { + display: none; +} + +/* misc default styling */ + +.pure-menu-separator { + background-color: #ccc; + height: 1px; + margin: .3em 0; +} + +.pure-menu-horizontal .pure-menu-separator { + width: 1px; + height: 1.3em; + margin: 0 .3em ; +} + +.pure-menu-heading { + text-transform: uppercase; + color: #565d64; +} + +.pure-menu-link { + color: #777; +} + +.pure-menu-children { + background-color: #fff; +} + +.pure-menu-link, +.pure-menu-disabled, +.pure-menu-heading { + padding: .5em 1em; +} + +.pure-menu-disabled { + opacity: .5; +} + +.pure-menu-disabled .pure-menu-link:hover { + background-color: transparent; +} + +.pure-menu-active > .pure-menu-link, +.pure-menu-link:hover, +.pure-menu-link:focus { + background-color: #eee; +} + +.pure-menu-selected .pure-menu-link, +.pure-menu-selected .pure-menu-link:visited { + color: #000; +} + +.pure-table { + /* Remove spacing between table cells (from Normalize.css) */ + border-collapse: collapse; + border-spacing: 0; + empty-cells: show; +} + +.pure-table caption { + color: #000; + font: italic 85%/1 arial, sans-serif; + padding: 1em 0; + text-align: center; +} + +.pure-table td, +.pure-table th { + font-size: inherit; + margin: 0; + overflow: visible; /*to make ths where the title is really long work*/ + padding: 0.5em 1em; /* cell padding */ +} + +/* Consider removing this next declaration block, as it causes problems when +there's a rowspan on the first cell. Case added to the tests. issue#432 */ +.pure-table td:first-child, +.pure-table th:first-child { + border-left-width: 0; +} + +.pure-table thead { + background-color: #e0e0e0; + color: #000; + text-align: left; + vertical-align: bottom; +} + +/* +striping: + even - #fff (white) + odd - #f2f2f2 (light gray) +*/ +.pure-table td { + background-color: transparent; +} +.pure-table-odd td { + background-color: #f2f2f2; +} + +/* nth-child selector for modern browsers */ +.pure-table-striped tr:nth-child(2n-1) td { + background-color: #f2f2f2; +} + +/* BORDERED TABLES */ +.pure-table-bordered td { + border-bottom: 1px solid #cbcbcb; +} +.pure-table-bordered tbody > tr:last-child > td { + border-bottom-width: 0; +} + + +/* HORIZONTAL BORDERED TABLES */ + +.pure-table-horizontal td, +.pure-table-horizontal th { +} +.pure-table-horizontal tbody > tr:last-child > td { + border-bottom-width: 0; +} diff --git a/html/style.css b/html/style.css new file mode 100644 index 0000000..a2271d8 --- /dev/null +++ b/html/style.css @@ -0,0 +1,528 @@ +/* All fonts */ +html, button, input, select, textarea, .pure-g [class *= "pure-u"] { + font-family: sans-serif; +} + +input[type="text"], input[type="password"], textarea { + width: 100%; +} + +input[type=checkbox] { + float: left; + margin: .35em 0.4em; +} + +body { + color: #000; + background-color: #AAABD3; +} +a:visited, a:link { + color: #009; +} +a:hover { + color: #00c; +} + +.card { + background-color: #a5dff9; + padding: 1em; + margin: 0.5em; + border-radius: 0.5em; + border: 0px solid #000000; +} + +/* click-to-edit fields */ +.click-to-edit { + position: relative; +} +.edit-off { + cursor: pointer; +} +.click-to-edit input, .click-to-edit textarea { + color: black; + background-color: #eee; + width: 100%; +} +.click-to-edit span, .click-to-edit div { + /*background-color: #e0e0e0;*/ + /*color: #1c0099;*/ + padding: 0.3em; + width: 100%; + color: #444; /* rgba not supported (IE 8) */ + color: rgba(0, 0, 0, 0.80); /* rgba supported */ + border: 1px solid #999; /*IE 6/7/8*/ + border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/ + background-color: #d6ecfa; + text-decoration: none; + border-radius: 2px; +} +.click-to-edit span:hover, .click-to-edit div:hover, +.click-to-edit span:focus, .click-to-edit div:focus { + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000',GradientType=0); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(40%, rgba(0,0,0, 0.05)), to(rgba(0,0,0, 0.10))); + background-image: -webkit-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); + background-image: -moz-linear-gradient(top, rgba(0,0,0, 0.05) 0%, rgba(0,0,0, 0.10)); + background-image: -o-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); + background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); +} +.click-to-edit span:focus, .click-to-edit div:focus { + outline: 0; +} +.click-to-edit span:active, .click-to-edit div:active { + box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; + border-color: #000\9; +} +/* Firefox: Get rid of the inner focus border */ +.click-to-edit span::-moz-focus-inner, +.click-to-edit div::-moz-focus-inner { + padding: 0; + border: 0; +} + +/* pop-ups */ +.popup-hidden { + display: none; +} +.popup, div.popup { + position: absolute; + /*top: 100%;*/ + bottom: 125%; + background-color: #fff0b3; + border-radius: 5px; + border: 1px solid #e6b800; + color: #682D63; + font-size: 80%; + line-height: 110%; + z-index: 100; + padding: 5px; + min-width: 20em; +} +.popup:not(.pop-left) { + left: 20px; +} +.popup.pop-left { + right: 20px; +} +.popup-target:hover .popup { + display: block; +} +.popup-target { + position: relative; +} + +/* wifi AP selection form */ +#aps label div { + display: inline-block; + margin: 0em 0.2em; +} +fieldset.radios { + border: none; + padding-left: 0px; +} +fieldset fields { + clear: both; +} +#pin-mux input { + display: block; + margin-top: 0.4em; + float: left; +} +#pin-mux label { + display: block; + margin: 0em 0.2em 0em 1em; + width: 90%; +} + +.pure-table td, .pure-table th { + padding: 0.5em 0.5em; +} + +/* Narrow left-aligned Forms */ +.pure-form-aligned.form-narrow .pure-control-group label { + text-align: left; + width: 6em; +} +.pure-form-aligned.form-narrow input[type=checkbox], +.pure-form-aligned.form-narrow input[type=radio] { + float: none; +} + +/* make images size-up */ +.xx-pure-img-responsive { + max-width: 100%; + height: auto; +} + +/* Add transition to containers so they can push in and out */ +#layout, #menu, .menu-link { + -webkit-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} + +/* This is the parent `
` that contains the menu and the content area */ +#layout { + position: relative; + padding-left: 0; +} +#layout.active #menu { + left: 150px; + width: 200px; +} + +#layout.active .menu-link { + left: 150px; +} + +div.tt { + font-family: monospace; + font-size: 120%; + color: #390; + background-color: #ddd; + padding: 2px; + margin: 2px 0; + line-height: 100%; +} + +/* The content `
` */ +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 20px; + line-height: 1.6em; + width: 100%; + box-sizing: border-box; + overflow: auto; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 5px; + margin-bottom: 10px; + background-color: #00b9f1; +} +.header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; +} +.header h1 .esp { + font-size: 1.25em; +} + +.content-subhead { + margin: 50px 0 20px 0; + font-weight: 300; + color: #888; +} + +form button { + margin-top: 0.5em; +} +.button-primary { + background-color: #99f; +} +.button-selected { + background-color: #fc6; +} +select.pure-button { + padding: 0.465em 1em; + color: #009; /* same as a:link */ +} +.button-small { + font-size: 75%; + background: #ccc; +} + +input.inline { + float:none; + margin-right: 0px; + margin-left: 0.5em; +} + +/* Text console */ +pre.console { + background-color: #663300; + border-radius: 5px; + border: 0px solid #000000; + color: #66ff66; + padding: 5px; + overflow: scroll; + margin: 0px; +} + +pre.console a { + color: #66ff66; +} + +.console-in { + background-color: #fff0b3; + border-radius: 5px; + border: 0px solid #630; + color: #0c0; + padding: 5px; + width: 100%; + height: 100%; + box-sizing: border-box; +} +.console-in option:checked { + background-image: -webkit-linear-gradient(#fc0, #fc0); + background-image: linear-gradient(#fc0, #fc0); +} + +/* console flex */ + +.flex-hbox, .flex-vbox { + display: -webkit-box; + display: flex; + display: -ms-flexbox; + display: -webkit-flex; +} +.flex-hbox { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + ms-flex-direction: row; + webkit-flex-direction: row; +} +.flex-vbox { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + ms-flex-direction: column; + webkit-flex-direction: column; + /*width: 100%; */ +} +.flex-fill { + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + ms-flex: 1 1 auto; + webkit-flex: 1 1 auto; +} + +.height100 { + height: 100%; +} + +/* log page */ +.dbg-btn, #refresh-button { + vertical-align: baseline; +} + +.lock-icon { + background-image: url("/wifi/icons.png"); + background-color: transparent; + width: 32px; + height: 32px; + display: inline-block; +} + +#menu { + margin-left: -150px; + width: 200px; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 1000; + background: #7c4dff; + overflow: visible; + -webkit-overflow-scrolling: touch; +} + +#menu a { + color: #ABD0CE; + border: none; + padding: 0.6em 0 0.6em 0.6em; +} + +#menu .pure-menu, #menu .pure-menu ul { + border: none; + background: transparent; +} + +.pure-menu-list { + margin-top: 15px; +} + +#menu .pure-menu ul, #menu .pure-menu .menu-item-divided { +} + +#menu .pure-menu li a:hover, #menu .pure-menu li a:focus { + background: #00b9f1; + color: #fff; +} + +#menu .pure-menu-selected, #menu .pure-menu-heading { + background: #7c4dff; +} + +#menu .pure-menu-selected a { + color: #fff; +} + +#menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; + text-transform: none; +} + +#menu .pure-menu-heading img { + vertical-align: middle; + top: -1px; + position: relative; +} + +#menu .pure-menu-item { + height:2em; +} + +/* -- Dynamic Button For Responsive Menu -------------------------------------*/ + +.menu-link { + position: fixed; + display: block; + top: 0; + left: 0; + background: #000; + background: rgba(0,0,0,0.7); + font-size: 10px; + z-index: 10; + width: 2em; + height: auto; + padding: 2.1em 1.6em; +} + +.menu-link:hover, .menu-link:focus { + background: #000; +} + +.menu-link span { + position: relative; + display: block; +} + +.menu-link span, .menu-link span:before, .menu-link span:after { + background-color: #fff; + width: 100%; + height: 0.2em; +} + +.menu-link span:before, .menu-link span:after { + position: absolute; + margin-top: -0.6em; + content: " "; +} + +.menu-link span:after { + margin-top: 0.6em; +} + +/* -- Responsive Styles (Media Queries) ------------------------------------- */ + +@media (min-width: 56em) { + .header, .content { + padding-left: 2em; + padding-right: 2em; + } + + #layout { + padding-left: 150px; + left: 0; + } + #menu { + left: 150px; + } + + .menu-link { + position: fixed; + left: 150px; + display: none; + } + + #layout.active .menu-link { + left: 150px; + } +} + +@media (max-width: 56em) { + #layout.active { + position: relative; + left: 150px; + } +} + +/*===== spinners and notification messages */ + +#messages { + position: absolute; + left: 25%; + width: 50%; + top: 10; + z-index: 200; + font-size: 110%; + text-align: center; +} +#warning { + background-color: #933; + color: #fcc; + padding: 0.1em 0.4em; +} +#notification { + background-color: #693; + color: #cfc; + padding: 0.1em 0.4em; +} + +#spinner { + position: absolute; + right: 10%; + top: 20; + z-index: 1000; +} +.spinner { + height: 50px; + width: 50px; + -webkit-animation: rotation 1s infinite linear; + animation: rotation 1s infinite linear; + border-left: 10px solid rgba(204, 51, 0, 0.15); + border-right: 10px solid rgba(204, 51, 0, 0.15); + border-bottom: 10px solid rgba(204, 51, 0, 0.15); + border-top: 10px solid rgba(204, 51, 0, 0.8); + border-radius: 100%; +} +.spinner-small { + display: inline-block; + height: 1em; + width: 1em; + border-width: 4px; +} + +@-webkit-keyframes rotation { + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(359deg); + } +} + +@keyframes rotation { + from { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + to { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} diff --git a/html/ui.js b/html/ui.js new file mode 100644 index 0000000..7aba575 --- /dev/null +++ b/html/ui.js @@ -0,0 +1,391 @@ +//===== Collection of small utilities + +/* + * Bind/Unbind events + * + * Usage: + * var el = document.getElementyById('#container'); + * bnd(el, 'click', function() { + * console.log('clicked'); + * }); + */ + +var bnd = function( + d, // a DOM element + e, // an event name such as "click" + f // a handler function +){ + d.addEventListener(e, f, false); +} + +/* + * Create DOM element + * + * Usage: + * var el = m('

Hello

'); + * document.body.appendChild(el); + * + * Copyright (C) 2011 Jed Schmidt - WTFPL + * More: https://gist.github.com/966233 + */ + +var m = function( + a, // an HTML string + b, // placeholder + c // placeholder +){ + b = document; // get the document, + c = b.createElement("p"); // create a container element, + c.innerHTML = a; // write the HTML to it, and + a = b.createDocumentFragment(); // create a fragment. + + while ( // while + b = c.firstChild // the container element has a first child + ) a.appendChild(b); // append the child to the fragment, + + return a // and then return the fragment. +} + +/* + * DOM selector + * + * Usage: + * $('div'); + * $('#name'); + * $('.name'); + * + * Copyright (C) 2011 Jed Schmidt - WTFPL + * More: https://gist.github.com/991057 + */ + +var $ = function( + a, // take a simple selector like "name", "#name", or ".name", and + b // an optional context, and +){ + a = a.match(/^(\W)?(.*)/); // split the selector into name and symbol. + return( // return an element or list, from within the scope of + b // the passed context + || document // or document, + )[ + "getElement" + ( // obtained by the appropriate method calculated by + a[1] + ? a[1] == "#" + ? "ById" // the node by ID, + : "sByClassName" // the nodes by class name, or + : "sByTagName" // the nodes by tag name, + ) + ]( + a[2] // called with the name. + ) +} + +/* + * Get cross browser xhr object + * + * Copyright (C) 2011 Jed Schmidt + * More: https://gist.github.com/993585 + */ + +var j = function( + a // cursor placeholder +){ + for( // for all a + a=0; // from 0 + a<4; // to 4, + a++ // incrementing + ) try { // try + return a // returning + ? new ActiveXObject( // a new ActiveXObject + [ // reflecting + , // (elided) + "Msxml2", // the various + "Msxml3", // working + "Microsoft" // options + ][a] + // for Microsoft implementations, and + ".XMLHTTP" // the appropriate suffix, + ) // but make sure to + : new XMLHttpRequest // try the w3c standard first, and + } + + catch(e){} // ignore when it fails. +} + +// dom element iterator: domForEach($(".some-class"), function(el) { ... }); +function domForEach(els, fun) { return Array.prototype.forEach.call(els, fun); } + +// createElement short-hand + +e = function(a) { return document.createElement(a); } + +// chain onload handlers + +function onLoad(f) { + var old = window.onload; + if (typeof old != 'function') { + window.onload = f; + } else { + window.onload = function() { + old(); + f(); + } + } +} + +//===== helpers to add/remove/toggle HTML element classes + +function addClass(el, cl) { + el.className += ' ' + cl; +} +function removeClass(el, cl) { + var cls = el.className.split(/\s+/), + l = cls.length; + for (var i=0; i= 200 && xhr.status < 300) { +// console.log("XHR done:", method, url, "->", xhr.status); + ok_cb(xhr.responseText); + } else { + console.log("XHR ERR :", method, url, "->", xhr.status, xhr.responseText, xhr); + err_cb(xhr.status, xhr.responseText); + } + } +// console.log("XHR send:", method, url); + try { + xhr.send(); + } catch(err) { + console.log("XHR EXC :", method, url, "->", err); + err_cb(599, err); + } +} + +function dispatchJson(resp, ok_cb, err_cb) { + var j; + try { j = JSON.parse(resp); } + catch(err) { + console.log("JSON parse error: " + err + ". In: " + resp); + err_cb(500, "JSON parse error: " + err); + return; + } + ok_cb(j); +} + +function ajaxJson(method, url, ok_cb, err_cb) { + ajaxReq(method, url, function(resp) { dispatchJson(resp, ok_cb, err_cb); }, err_cb); +} + +function ajaxSpin(method, url, ok_cb, err_cb) { + $("#spinner").removeAttribute('hidden'); + ajaxReq(method, url, function(resp) { + $("#spinner").setAttribute('hidden', ''); + ok_cb(resp); + }, function(status, statusText) { + $("#spinner").setAttribute('hidden', ''); + //showWarning("Error: " + statusText); + err_cb(status, statusText); + }); +} + +function ajaxJsonSpin(method, url, ok_cb, err_cb) { + ajaxSpin(method, url, function(resp) { dispatchJson(resp, ok_cb, err_cb); }, err_cb); +} + +//===== main menu, header spinner and notification boxes + +function hidePopup(el) { + addClass(el, "popup-hidden"); + addClass(el.parentNode, "popup-target"); +} + +onLoad(function() { + var l = $("#layout"); + var o = l.childNodes[0]; + // spinner + l.insertBefore(m(''), o); + // notification boxes + l.insertBefore(m( + '
'), o); + // menu hamburger button + l.insertBefore(m(''), o); + // menu left-pane + var mm = m( + '\ + '); + l.insertBefore(mm, o); + + // make hamburger button pull out menu + var ml = $('#menuLink'), mm = $('#menu'); + bnd(ml, 'click', function (e) { +// console.log("hamburger time"); + var active = 'active'; + e.preventDefault(); + toggleClass(l, active); + toggleClass(mm, active); + toggleClass(ml, active); + }); + + // hide pop-ups + domForEach($(".popup"), function(el) { + hidePopup(el); + }); + + // populate menu via ajax call + var getMenu = function() { + ajaxJson("GET", "/menu", function(data) { + var html = "", path = window.location.pathname; + for (var i=0; i" + + "" + + data.menu[i] + ""); + } + $("#menu-list").innerHTML = html; + + var v = $("#version"); + if (v != null) { v.innerHTML = data.version; } + + setEditToClick("system-name", data["name"]); + }, function() { setTimeout(getMenu, 1000); }); + }; + getMenu(); +}); + +//===== Wifi info + +function showWifiInfo(data) { + Object.keys(data).forEach(function(v) { + el = $("#wifi-" + v); + if (el != null) { + if (el.nodeName === "INPUT") el.value = data[v]; + else el.innerHTML = data[v]; + } + }); + var dhcp = $('#dhcp-r'+data.dhcp); + if (dhcp) dhcp.click(); + $("#wifi-spinner").setAttribute("hidden", ""); + $("#wifi-table").removeAttribute("hidden"); + currAp = data.ssid; +} + +function getWifiInfo() { + ajaxJson('GET', "/wifi/info", showWifiInfo, + function(s, st) { window.setTimeout(getWifiInfo, 1000); }); +} + +//===== System info + +function setEditToClick(klass, value) { + domForEach($("."+klass), function(div) { + if (div.children.length > 0) { + domForEach(div.children, function(el) { + if (el.nodeName === "INPUT") el.value = value; + else if (el.nodeName !== "DIV") el.innerHTML = value; + }); + } else { + div.innerHTML = value; + } + }); +} + +function showSystemInfo(data) { + Object.keys(data).forEach(function(v) { + setEditToClick("system-"+v, data[v]); + }); + $("#system-spinner").setAttribute("hidden", ""); + $("#system-table").removeAttribute("hidden"); + currAp = data.ssid; +} + +function getSystemInfo() { + ajaxJson('GET', "/system/info", showSystemInfo, + function(s, st) { window.setTimeout(getSystemInfo, 1000); }); +} + +function makeAjaxInput(klass, field) { + domForEach($("."+klass+"-"+field), function(div) { + var eon = $(".edit-on", div); + var eoff = $(".edit-off", div)[0]; + var url = "/"+klass+"/update?"+field; + + if (eoff === undefined || eon == undefined) return; + + var enableEditToClick = function() { + eoff.setAttribute('hidden',''); + domForEach(eon, function(el){ el.removeAttribute('hidden'); }); + eon[0].select(); + return false; + } + + var submitEditToClick = function(v) { +// console.log("Submit POST "+url+"="+v); + ajaxSpin("POST", url+"="+v, function() { + domForEach(eon, function(el){ el.setAttribute('hidden',''); }); + eoff.removeAttribute('hidden'); + setEditToClick(klass+"-"+field, v) + showNotification(field + " changed to " + v); + }, function() { + showWarning(field + " change failed"); + }); + return false; + } + + bnd(eoff, "click", function(){return enableEditToClick();}); + bnd(eon[0], "blur", function(){return submitEditToClick(eon[0].value);}); + bnd(eon[0], "keyup", function(ev){ + if ((ev||window.event).keyCode==13) return submitEditToClick(eon[0].value); + }); + }); +} + + +//===== Notifications + +function showWarning(text) { + var el = $("#warning"); + el.innerHTML = text; + el.removeAttribute('hidden'); + window.scrollTo(0, 0); +} +function hideWarning() { + el = $("#warning").setAttribute('hidden', ''); +} +var notifTimeout = null; +function showNotification(text) { + var el = $("#notification"); + el.innerHTML = text; + el.removeAttribute('hidden'); + if (notifTimeout != null) clearTimeout(notifTimeout); + notifTimout = setTimeout(function() { + el.setAttribute('hidden', ''); + notifTimout = null; + }, 4000); +} + diff --git a/html/wifi/icons.png b/html/wifi/icons.png new file mode 100644 index 0000000..08c933f Binary files /dev/null and b/html/wifi/icons.png differ diff --git a/html/wifi/wifiAp.html b/html/wifi/wifiAp.html new file mode 100644 index 0000000..e93e54d --- /dev/null +++ b/html/wifi/wifiAp.html @@ -0,0 +1,124 @@ +
+
+

WiFi Soft-AP Configuration

+
+ +
+
+
+
+

Soft-AP State

+
+
+ + + + + + + + + + + + +
+
+
+ +
+

Soft-AP Settings

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

WiFi Station Configuration

+
+ +
+
+
+

WiFi State

+
+ + + + + + + + + + + +
+ +
+
+

WiFi Association

+ +
+ To connect to a WiFi network, please select one of the detected networks, + enter the password, and hit the connect button... + +
Scanning...
+ + + + +
+
+
+
+
+

Special Settings

+
+ Special settings, use with care! +
+ + +
+
+
+ + + + + + +
+ +
+
+
+
+
+
+ + + + + diff --git a/html/wifi/wifiSta.js b/html/wifi/wifiSta.js new file mode 100644 index 0000000..8af3ee0 --- /dev/null +++ b/html/wifi/wifiSta.js @@ -0,0 +1,203 @@ +var currAp = ""; +var blockScan = 0; + +function createInputForAp(ap) { + if (ap.essid=="" && ap.rssi==0) return; + + var input = e("input"); + input.type = "radio"; + input.name = "essid"; + input.value=ap.essid; + input.id = "opt-" + ap.essid; + if (currAp == ap.essid) input.checked = "1"; + + var bars = e("div"); + var rssiVal = -Math.floor(ap.rssi/51)*32; + bars.className = "lock-icon"; + bars.style.backgroundPosition = "0px "+rssiVal+"px"; + + var rssi = e("div"); + rssi.innerHTML = "" + ap.rssi +"dB"; + + var encrypt = e("div"); + var encVal = "-64"; //assume wpa/wpa2 + if (ap.enc == "0") encVal = "0"; //open + if (ap.enc == "1") encVal = "-32"; //wep + encrypt.className = "lock-icon"; + encrypt.style.backgroundPosition = "-32px "+encVal+"px"; + + var label = e("div"); + label.innerHTML = ap.essid; + + var div = m('').childNodes[0]; + div.appendChild(input); + div.appendChild(encrypt); + div.appendChild(bars); + div.appendChild(rssi); + div.appendChild(label); + return div; +} + +function getSelectedEssid() { + var e = document.forms.wifiform.elements; + for (var i=0; i 60) { + return scanAPs(); + } + scanReqCnt += 1; + ajaxJson('GET', "scan", function(data) { + currAp = getSelectedEssid(); + if (data.result.inProgress == "0" && data.result.APs.length > 0) { + $("#aps").innerHTML = ""; + var n = 0; + for (var i=0; i"+data.ip+", else connect to network "+data.ssid+" first."; + } else { + blockScan = 0; + showWarning("Connection failed: " + data.status + ", " + data.reason); + $("#aps").innerHTML = + "Check password and selected AP. Go Back"; + } + }, function(s, st) { + //showWarning("Can't get status: " + st); + window.setTimeout(getStatus, 2000); + }); +} + +function changeWifiMode(m) { + blockScan = 1; + hideWarning(); + ajaxSpin("POST", "setmode?mode=" + m, function(resp) { + showNotification("Mode changed"); + window.setTimeout(getWifiInfo, 100); + blockScan = 0; + }, function(s, st) { + showWarning("Error changing mode: " + st); + window.setTimeout(getWifiInfo, 100); + blockScan = 0; + }); +} + +function changeWifiAp(e) { + e.preventDefault(); + var passwd = $("#wifi-passwd").value; + var essid = getSelectedEssid(); + showNotification("Connecting to " + essid); + var url = "connect?essid="+encodeURIComponent(essid)+"&passwd="+encodeURIComponent(passwd); + + hideWarning(); + $("#reconnect").setAttribute("hidden", ""); + $("#wifi-passwd").value = ""; + var cb = $("#connect-button"); + var cn = cb.className; + cb.className += ' pure-button-disabled'; + blockScan = 1; + ajaxSpin("POST", url, function(resp) { + $("#spinner").removeAttribute('hidden'); // hack + showNotification("Waiting for network change..."); + window.scrollTo(0, 0); + window.setTimeout(getStatus, 2000); + }, function(s, st) { + showWarning("Error switching network: "+st); + cb.className = cn; + window.setTimeout(scanAPs, 1000); + }); +} + +function changeSpecial(e) { + e.preventDefault(); + var url = "special"; + url += "?dhcp=" + document.querySelector('input[name="dhcp"]:checked').value; + url += "&staticip=" + encodeURIComponent($("#wifi-staticip").value); + url += "&netmask=" + encodeURIComponent($("#wifi-netmask").value); + url += "&gateway=" + encodeURIComponent($("#wifi-gateway").value); + + hideWarning(); + var cb = $("#special-button"); + addClass(cb, 'pure-button-disabled'); + ajaxSpin("POST", url, function(resp) { + removeClass(cb, 'pure-button-disabled'); + //getWifiInfo(); // it takes 1 second for new settings to be applied + }, function(s, st) { + showWarning("Error: "+st); + removeClass(cb, 'pure-button-disabled'); + getWifiInfo(); + }); +} + +function doDhcp() { + $('#dhcp-on').removeAttribute('hidden'); + $('#dhcp-off').setAttribute('hidden', ''); +} + +function doStatic() { + $('#dhcp-off').removeAttribute('hidden'); + $('#dhcp-on').setAttribute('hidden', ''); +} diff --git a/httpd/auth.c b/httpd/auth.c new file mode 100644 index 0000000..df14175 --- /dev/null +++ b/httpd/auth.c @@ -0,0 +1,61 @@ +/* +HTTP auth implementation. Only does basic authentication for now. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + + +#include +#include "auth.h" +#include "base64.h" + +int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData) { + const char *forbidden="401 Forbidden."; + int no=0; + int r; + char hdr[(AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2)*10]; + char userpass[AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2]; + char user[AUTH_MAX_USER_LEN]; + char pass[AUTH_MAX_PASS_LEN]; + if (connData->conn==NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + r=httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr)); + if (r && strncmp(hdr, "Basic", 5)==0) { + r=base64_decode(strlen(hdr)-6, hdr+6, sizeof(userpass), (unsigned char *)userpass); + if (r<0) r=0; //just clean out string on decode error + userpass[r]=0; //zero-terminate user:pass string +// os_printf("Auth: %s\n", userpass); + while (((AuthGetUserPw)(connData->cgiArg))(connData, no, + user, AUTH_MAX_USER_LEN, pass, AUTH_MAX_PASS_LEN)) { + //Check user/pass against auth header + if (strlen(userpass)==strlen(user)+strlen(pass)+1 && + os_strncmp(userpass, user, strlen(user))==0 && + userpass[strlen(user)]==':' && + os_strcmp(userpass+strlen(user)+1, pass)==0) { + //Authenticated. Yay! + return HTTPD_CGI_AUTHENTICATED; + } + no++; //Not authenticated with this user/pass. Check next user/pass combo. + } + } + + //Not authenticated. Go bug user with login screen. + httpdStartResponse(connData, 401); + httpdHeader(connData, "Content-Type", "text/plain"); + httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\""); + httpdEndHeaders(connData); + httpdSend(connData, forbidden, -1); + //Okay, all done. + return HTTPD_CGI_DONE; +} + diff --git a/httpd/auth.h b/httpd/auth.h new file mode 100644 index 0000000..1ef0f61 --- /dev/null +++ b/httpd/auth.h @@ -0,0 +1,22 @@ +#ifndef AUTH_H +#define AUTH_H + +#include "httpd.h" + +#ifndef HTTP_AUTH_REALM +#define HTTP_AUTH_REALM "Protected" +#endif + +#define HTTPD_AUTH_SINGLE 0 +#define HTTPD_AUTH_CALLBACK 1 + +#define AUTH_MAX_USER_LEN 32 +#define AUTH_MAX_PASS_LEN 32 + +//Parameter given to authWhatever functions. This callback returns the usernames/passwords the device +//has. +typedef int (* AuthGetUserPw)(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen); + +int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData); + +#endif \ No newline at end of file diff --git a/httpd/base64.c b/httpd/base64.c new file mode 100644 index 0000000..9d97a26 --- /dev/null +++ b/httpd/base64.c @@ -0,0 +1,112 @@ +/* base64.c : base-64 / MIME encode/decode */ +/* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */ +#include +#include "base64.h" + +static const uint8_t base64dec_tab[256]= { + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +}; + +#if 0 +static int ICACHE_FLASH_ATTR base64decode(const char in[4], char out[3]) { + uint8_t v[4]; + + v[0]=base64dec_tab[(unsigned)in[0]]; + v[1]=base64dec_tab[(unsigned)in[1]]; + v[2]=base64dec_tab[(unsigned)in[2]]; + v[3]=base64dec_tab[(unsigned)in[3]]; + + out[0]=(v[0]<<2)|(v[1]>>4); + out[1]=(v[1]<<4)|(v[2]>>2); + out[2]=(v[2]<<6)|(v[3]); + return (v[0]|v[1]|v[2]|v[3])!=255 ? in[3]=='=' ? in[2]=='=' ? 1 : 2 : 3 : 0; +} +#endif + +/* decode a base64 string in one shot */ +int ICACHE_FLASH_ATTR base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out) { + unsigned int ii, io; + uint32_t v; + unsigned int rem; + + for(io=0,ii=0,v=0,rem=0;ii=8) { + rem-=8; + if(io>=out_len) return -1; /* truncation is failure */ + out[io++]=(v>>rem)&255; + } + } + if(rem>=8) { + rem-=8; + if(io>=out_len) return -1; /* truncation is failure */ + out[io++]=(v>>rem)&255; + } + return io; +} + +//Only need decode functions for now. +#if 0 + +static const uint8_t base64enc_tab[64]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void base64encode(const unsigned char in[3], unsigned char out[4], int count) { + out[0]=base64enc_tab[(in[0]>>2)]; + out[1]=base64enc_tab[((in[0]&3)<<4)|(in[1]>>4)]; + out[2]=count<2 ? '=' : base64enc_tab[((in[1]&15)<<2)|(in[2]>>6)]; + out[3]=count<3 ? '=' : base64enc_tab[(in[2]&63)]; +} + + +int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) { + unsigned ii, io; + uint_least32_t v; + unsigned rem; + + for(io=0,ii=0,v=0,rem=0;ii=6) { + rem-=6; + if(io>=out_len) return -1; /* truncation is failure */ + out[io++]=base64enc_tab[(v>>rem)&63]; + } + } + if(rem) { + v<<=(6-rem); + if(io>=out_len) return -1; /* truncation is failure */ + out[io++]=base64enc_tab[v&63]; + } + while(io&3) { + if(io>=out_len) return -1; /* truncation is failure */ + out[io++]='='; + } + if(io>=out_len) return -1; /* no room for null terminator */ + out[io]=0; + return io; +} + +#endif \ No newline at end of file diff --git a/httpd/base64.h b/httpd/base64.h new file mode 100644 index 0000000..4dc8621 --- /dev/null +++ b/httpd/base64.h @@ -0,0 +1,6 @@ +#ifndef BASE64_H +#define BASE64_H + +int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out); + +#endif \ No newline at end of file diff --git a/httpd/httpd.c b/httpd/httpd.c new file mode 100644 index 0000000..53f03cd --- /dev/null +++ b/httpd/httpd.c @@ -0,0 +1,618 @@ +/* +Esp8266 http server - core routines +*/ + +/* +* ---------------------------------------------------------------------------- +* "THE BEER-WARE LICENSE" (Revision 42): +* Jeroen Domburg wrote this file. As long as you retain +* this notice you can do whatever you want with this stuff. If we meet some day, +* and you think this stuff is worth it, you can buy me a beer in return. +* ---------------------------------------------------------------------------- +* Modified and enhanced by Thorsten von Eicken in 2015 +* ---------------------------------------------------------------------------- +*/ + + +#include +#include "httpd.h" + +#ifdef HTTPD_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + + +//Max length of request head +#define MAX_HEAD_LEN 1024 +//Max amount of connections +#define MAX_CONN 6 +//Max post buffer len +#define MAX_POST 1024 +//Max send buffer len +#define MAX_SENDBUFF_LEN 2600 + + +//This gets set at init time. +static HttpdBuiltInUrl *builtInUrls; + +//Private data for http connection +struct HttpdPriv { + char head[MAX_HEAD_LEN]; // buffer to accumulate header + char from[24]; // source ip&port + char *sendBuff; // output buffer + short headPos; // offset into header + short sendBuffLen; // offset into output buffer + short code; // http response code (only for logging) +}; + +//Connection pool +static HttpdPriv connPrivData[MAX_CONN]; +static HttpdConnData connData[MAX_CONN]; +static HttpdPostData connPostData[MAX_CONN]; + +//Listening connection data +static struct espconn httpdConn; +static esp_tcp httpdTcp; + +//Struct to keep extension->mime data in +typedef struct { + const char *ext; + const char *mimetype; +} MimeMap; + +//The mappings from file extensions to mime types. If you need an extra mime type, +//add it here. +static const MimeMap mimeTypes[] = { + { "htm", "text/htm" }, + { "html", "text/html; charset=UTF-8" }, + { "css", "text/css" }, + { "js", "text/javascript" }, + { "txt", "text/plain" }, + { "jpg", "image/jpeg" }, + { "jpeg", "image/jpeg" }, + { "png", "image/png" }, + { "svg", "image/svg+xml" }, + { "tpl", "text/html; charset=UTF-8" }, + { NULL, "text/html" }, //default value +}; + +//Returns a static char* to a mime type for a given url to a file. +const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { + int i = 0; + //Go find the extension + char *ext = url + (strlen(url) - 1); + while (ext != url && *ext != '.') ext--; + if (*ext == '.') ext++; + + //ToDo: os_strcmp is case sensitive; we may want to do case-intensive matching here... + while (mimeTypes[i].ext != NULL && os_strcmp(ext, mimeTypes[i].ext) != 0) i++; + return mimeTypes[i].mimetype; +} + +// debug string to identify connection (ip address & port) +// a static string works because callbacks don't get interrupted... +static char connStr[24]; + +static void debugConn(void *arg, char *what) { +#if 0 + struct espconn *espconn = arg; + esp_tcp *tcp = espconn->proto.tcp; + os_sprintf(connStr, "%d.%d.%d.%d:%d ", + tcp->remote_ip[0], tcp->remote_ip[1], tcp->remote_ip[2], tcp->remote_ip[3], + tcp->remote_port); + DBG("%s %s\n", connStr, what); +#else + connStr[0] = 0; +#endif +} + +// Retires a connection for re-use +static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) { + if (conn->conn && conn->conn->reverse == conn) + conn->conn->reverse = NULL; // break reverse link + + // log information about the request we handled + uint32 dt = conn->startTime; + if (dt > 0) dt = (system_get_time() - dt) / 1000; + if (conn->conn && conn->url) +#if 0 + DBG("HTTP %s %s from %s -> %d in %ums, heap=%ld\n", + conn->requestType == HTTPD_METHOD_GET ? "GET" : "POST", conn->url, conn->priv->from, + conn->priv->code, dt, (unsigned long)system_get_free_heap_size()); +#else + DBG("HTTP %s %s: %d, %ums, h=%ld\n", + conn->requestType == HTTPD_METHOD_GET ? "GET" : "POST", conn->url, + conn->priv->code, dt, (unsigned long)system_get_free_heap_size()); +#endif + + conn->conn = NULL; // don't try to send anything, the SDK crashes... + if (conn->cgi != NULL) conn->cgi(conn); // free cgi data + if (conn->post->buff != NULL) os_free(conn->post->buff); + conn->cgi = NULL; + conn->post->buff = NULL; +} + +//Stupid li'l helper function that returns the value of a hex char. +static int httpdHexVal(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + return 0; +} + +//Decode a percent-encoded value. +//Takes the valLen bytes stored in val, and converts it into at most retLen bytes that +//are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also +//zero-terminates the ret buffer. +int httpdUrlDecode(char *val, int valLen, char *ret, int retLen) { + int s = 0, d = 0; + int esced = 0, escVal = 0; + while (s1) { + *ret++ = *p++; + retLen--; + } + //Zero-terminate string + *ret = 0; + //All done :) + return 1; + } + p += strlen(p) + 1; //Skip past end of string and \0 terminator + } + return 0; +} + +//Start the response headers. +void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code) { + char buff[128]; + int l; + conn->priv->code = code; + char *status = code < 400 ? "OK" : "ERROR"; + l = os_sprintf(buff, "HTTP/1.0 %d %s\r\nServer: esp-link\r\nConnection: close\r\n", code, status); + httpdSend(conn, buff, l); +} + +//Send a http header. +void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val) { + char buff[256]; + int l; + + l = os_sprintf(buff, "%s: %s\r\n", field, val); + httpdSend(conn, buff, l); +} + +//Finish the headers. +void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn) { + httpdSend(conn, "\r\n", -1); +} + +//ToDo: sprintf->snprintf everywhere... esp doesn't have snprintf tho' :/ +//Redirect to the given URL. +void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl) { + char buff[1024]; + int l; + conn->priv->code = 302; + l = os_sprintf(buff, "HTTP/1.0 302 Found\r\nServer: esp8266-link\r\nConnection: close\r\n" + "Location: %s\r\n\r\nRedirecting to %s\r\n", newUrl, newUrl); + httpdSend(conn, buff, l); +} + +//Use this as a cgi function to redirect one url to another. +int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) { + if (connData->conn == NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + httpdRedirect(connData, (char*)connData->cgiArg); + return HTTPD_CGI_DONE; +} + + +//Add data to the send buffer. len is the length of the data. If len is -1 +//the data is seen as a C-string. +//Returns 1 for success, 0 for out-of-memory. +int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len) { + if (len<0) len = strlen(data); + if (conn->priv->sendBuffLen + len>MAX_SENDBUFF_LEN) { + DBG("%sERROR! httpdSend full (%d of %d)\n", + connStr, conn->priv->sendBuffLen, MAX_SENDBUFF_LEN); + return 0; + } + os_memcpy(conn->priv->sendBuff + conn->priv->sendBuffLen, data, len); + conn->priv->sendBuffLen += len; + return 1; +} + +//Helper function to send any data in conn->priv->sendBuff +static void ICACHE_FLASH_ATTR xmitSendBuff(HttpdConnData *conn) { + if (conn->priv->sendBuffLen != 0) { + sint8 status = espconn_sent(conn->conn, (uint8_t*)conn->priv->sendBuff, conn->priv->sendBuffLen); + if (status != 0) { + DBG("%sERROR! espconn_sent returned %d, trying to send %d to %s\n", + connStr, status, conn->priv->sendBuffLen, conn->url); + } + conn->priv->sendBuffLen = 0; + } +} + +//Callback called when the data on a socket has been successfully sent. +static void ICACHE_FLASH_ATTR httpdSentCb(void *arg) { + debugConn(arg, "httpdSentCb"); + struct espconn* pCon = (struct espconn *)arg; + HttpdConnData *conn = (HttpdConnData *)pCon->reverse; + if (conn == NULL) return; // aborted connection + + char sendBuff[MAX_SENDBUFF_LEN]; + conn->priv->sendBuff = sendBuff; + conn->priv->sendBuffLen = 0; + + if (conn->cgi == NULL) { //Marked for destruction? + //os_printf("Closing 0x%p/0x%p->0x%p\n", arg, conn->conn, conn); + espconn_disconnect(conn->conn); // we will get a disconnect callback + return; //No need to call xmitSendBuff. + } + + int r = conn->cgi(conn); //Execute cgi fn. + if (r == HTTPD_CGI_DONE) { + conn->cgi = NULL; //mark for destruction. + } + if (r == HTTPD_CGI_NOTFOUND || r == HTTPD_CGI_AUTHENTICATED) { + DBG("%sERROR! Bad CGI code %d\n", connStr, r); + conn->cgi = NULL; //mark for destruction. + } + xmitSendBuff(conn); +} + +static const char *httpNotFoundHeader = "HTTP/1.0 404 Not Found\r\nConnection: close\r\n" + "Content-Type: text/plain\r\nContent-Length: 12\r\n\r\nNot Found.\r\n"; + +//This is called when the headers have been received and the connection is ready to send +//the result headers and data. +//We need to find the CGI function to call, call it, and dependent on what it returns either +//find the next cgi function, wait till the cgi data is sent or close up the connection. +static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) { + int r; + int i = 0; + if (conn->url == NULL) { + DBG("%sWtF? url = NULL\n", connStr); + return; //Shouldn't happen + } + //See if we can find a CGI that's happy to handle the request. + while (1) { + //Look up URL in the built-in URL table. + if (conn->cgi == NULL) { + while (builtInUrls[i].url != NULL) { + int match = 0; + //See if there's a literal match + if (os_strcmp(builtInUrls[i].url, conn->url) == 0) match = 1; + //See if there's a wildcard match + if (builtInUrls[i].url[os_strlen(builtInUrls[i].url) - 1] == '*' && + os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url) - 1) == 0) match = 1; + if (match) { + //os_printf("Is url index %d\n", i); + conn->cgiData = NULL; + conn->cgi = builtInUrls[i].cgiCb; + conn->cgiArg = builtInUrls[i].cgiArg; + break; + } + i++; + } + if (builtInUrls[i].url == NULL) { + //Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just + //generate a built-in 404 to handle this. + DBG("%s%s not found. 404!\n", connStr, conn->url); + httpdSend(conn, httpNotFoundHeader, -1); + xmitSendBuff(conn); + conn->cgi = NULL; //mark for destruction. + if (conn->post) conn->post->len = 0; // skip any remaining receives + return; + } + } + + //Okay, we have a CGI function that matches the URL. See if it wants to handle the + //particular URL we're supposed to handle. + r = conn->cgi(conn); + if (r == HTTPD_CGI_MORE) { + //Yep, it's happy to do so and has more data to send. + xmitSendBuff(conn); + return; + } + else if (r == HTTPD_CGI_DONE) { + //Yep, it's happy to do so and already is done sending data. + xmitSendBuff(conn); + conn->cgi = NULL; //mark for destruction. + if (conn->post) conn->post->len = 0; // skip any remaining receives + return; + } + else { + if (!(r == HTTPD_CGI_NOTFOUND || r == HTTPD_CGI_AUTHENTICATED)) { + os_printf("%shandler for %s returned invalid result %d\n", connStr, conn->url, r); + } + //URL doesn't want to handle the request: either the data isn't found or there's no + //need to generate a login screen. + conn->cgi = NULL; // force lookup again + i++; //look at next url the next iteration of the loop. + } + } +} + +//Parse a line of header data and modify the connection data accordingly. +static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) { + int i; + char first_line = false; + + if (os_strncmp(h, "GET ", 4) == 0) { + conn->requestType = HTTPD_METHOD_GET; + first_line = true; + } + else if (os_strncmp(h, "POST ", 5) == 0) { + conn->requestType = HTTPD_METHOD_POST; + first_line = true; + } + + if (first_line) { + char *e; + + //Skip past the space after POST/GET + i = 0; + while (h[i] != ' ') i++; + conn->url = h + i + 1; + + //Figure out end of url. + e = (char*)os_strstr(conn->url, " "); + if (e == NULL) return; //wtf? + *e = 0; //terminate url part + + // Count number of open connections + //esp_tcp *tcp = conn->conn->proto.tcp; + //DBG("%sHTTP %s %s from %s\n", connStr, + // conn->requestType == HTTPD_METHOD_GET ? "GET" : "POST", conn->url, conn->priv->from); + //Parse out the URL part before the GET parameters. + conn->getArgs = (char*)os_strstr(conn->url, "?"); + if (conn->getArgs != 0) { + *conn->getArgs = 0; + conn->getArgs++; + //DBG("%sargs = %s\n", connStr, conn->getArgs); + } + else { + conn->getArgs = NULL; + } + + } + else if (os_strncmp(h, "Content-Length:", 15) == 0) { + i = 15; + //Skip trailing spaces + while (h[i] == ' ') i++; + //Get POST data length + conn->post->len = atoi(h + i); + + // Allocate the buffer + if (conn->post->len > MAX_POST) { + // we'll stream this in in chunks + conn->post->buffSize = MAX_POST; + } + else { + conn->post->buffSize = conn->post->len; + } + //DBG("Mallocced buffer for %d + 1 bytes of post data.\n", conn->post->buffSize); + conn->post->buff = (char*)os_malloc(conn->post->buffSize + 1); + conn->post->buffLen = 0; + } + else if (os_strncmp(h, "Content-Type: ", 14) == 0) { + if (os_strstr(h, "multipart/form-data")) { + // It's multipart form data so let's pull out the boundary for future use + char *b; + if ((b = os_strstr(h, "boundary=")) != NULL) { + conn->post->multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes + conn->post->multipartBoundary[0] = '-'; + conn->post->multipartBoundary[1] = '-'; + //DBG("boundary = %s\n", conn->post->multipartBoundary); + } + } + } +} + + +//Callback called when there's data available on a socket. +static void ICACHE_FLASH_ATTR httpdRecvCb(void *arg, char *data, unsigned short len) { + debugConn(arg, "httpdRecvCb"); + struct espconn* pCon = (struct espconn *)arg; + HttpdConnData *conn = (HttpdConnData *)pCon->reverse; + if (conn == NULL) return; // aborted connection + + char sendBuff[MAX_SENDBUFF_LEN]; + conn->priv->sendBuff = sendBuff; + conn->priv->sendBuffLen = 0; + + //This is slightly evil/dirty: we abuse conn->post->len as a state variable for where in the http communications we are: + //<0 (-1): Post len unknown because we're still receiving headers + //==0: No post data + //>0: Need to receive post data + //ToDo: See if we can use something more elegant for this. + + for (int x = 0; xpost->len<0) { + //This byte is a header byte. + if (conn->priv->headPos != MAX_HEAD_LEN) conn->priv->head[conn->priv->headPos++] = data[x]; + conn->priv->head[conn->priv->headPos] = 0; + //Scan for /r/n/r/n. Receiving this indicate the headers end. + if (data[x] == '\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n") != NULL) { + //Indicate we're done with the headers. + conn->post->len = 0; + //Reset url data + conn->url = NULL; + //Iterate over all received headers and parse them. + char *p = conn->priv->head; + while (p<(&conn->priv->head[conn->priv->headPos - 4])) { + char *e = (char *)os_strstr(p, "\r\n"); //Find end of header line + if (e == NULL) break; //Shouldn't happen. + e[0] = 0; //Zero-terminate header + httpdParseHeader(p, conn); //and parse it. + p = e + 2; //Skip /r/n (now /0/n) + } + //If we don't need to receive post data, we can send the response now. + if (conn->post->len == 0) { + httpdProcessRequest(conn); + } + } + } + else if (conn->post->len != 0) { + //This byte is a POST byte. + conn->post->buff[conn->post->buffLen++] = data[x]; + conn->post->received++; + if (conn->post->buffLen >= conn->post->buffSize || conn->post->received == conn->post->len) { + //Received a chunk of post data + conn->post->buff[conn->post->buffLen] = 0; //zero-terminate, in case the cgi handler knows it can use strings + //Send the response. + httpdProcessRequest(conn); + conn->post->buffLen = 0; + } + } + } +} + +static void ICACHE_FLASH_ATTR httpdDisconCb(void *arg) { + debugConn(arg, "httpdDisconCb"); + struct espconn* pCon = (struct espconn *)arg; + HttpdConnData *conn = (HttpdConnData *)pCon->reverse; + if (conn == NULL) return; // aborted connection + httpdRetireConn(conn); +} + +// Callback indicating a failure in the connection. "Recon" is probably intended in the sense +// of "you need to reconnect". Sigh... Note that there is no DisconCb after ReconCb +static void ICACHE_FLASH_ATTR httpdReconCb(void *arg, sint8 err) { + debugConn(arg, "httpdReconCb"); + struct espconn* pCon = (struct espconn *)arg; + HttpdConnData *conn = (HttpdConnData *)pCon->reverse; + if (conn == NULL) return; // aborted connection + DBG("%s***** reset, err=%d\n", connStr, err); + httpdRetireConn(conn); +} + + +static void ICACHE_FLASH_ATTR httpdConnectCb(void *arg) { + debugConn(arg, "httpdConnectCb"); + struct espconn *conn = arg; + + // Find empty conndata in pool + int i; + for (i = 0; ireverse = connData+i; + connData[i].priv->headPos = 0; + + esp_tcp *tcp = conn->proto.tcp; + os_sprintf(connData[i].priv->from, "%d.%d.%d.%d:%d", tcp->remote_ip[0], tcp->remote_ip[1], + tcp->remote_ip[2], tcp->remote_ip[3], tcp->remote_port); + connData[i].post = &connPostData[i]; + connData[i].post->buff = NULL; + connData[i].post->buffLen = 0; + connData[i].post->received = 0; + connData[i].post->len = -1; + connData[i].startTime = system_get_time(); + + espconn_regist_recvcb(conn, httpdRecvCb); + espconn_regist_reconcb(conn, httpdReconCb); + espconn_regist_disconcb(conn, httpdDisconCb); + espconn_regist_sentcb(conn, httpdSentCb); + + espconn_set_opt(conn, ESPCONN_REUSEADDR | ESPCONN_NODELAY); +} + +//Httpd initialization routine. Call this to kick off webserver functionality. +void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) { + int i; + + for (i = 0; i wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + * Modified and enhanced by Thorsten von Eicken in 2015 + * ---------------------------------------------------------------------------- + */ +#include "httpdespfs.h" + +// The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression. +// If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.) +static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n"; + + +//This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding +//path in the filesystem and if it exists, passes the file through. This simulates what a normal +//webserver would do with static files. +int ICACHE_FLASH_ATTR +cgiEspFsHook(HttpdConnData *connData) { + EspFsFile *file=connData->cgiData; + int len; + char buff[1024]; + char acceptEncodingBuffer[64]; + int isGzip; + + //os_printf("cgiEspFsHook conn=%p conn->conn=%p file=%p\n", connData, connData->conn, file); + + if (connData->conn==NULL) { + //Connection aborted. Clean up. + espFsClose(file); + return HTTPD_CGI_DONE; + } + + if (file==NULL) { + //First call to this cgi. Open the file so we can read it. + file=espFsOpen(connData->url); + if (file==NULL) { + return HTTPD_CGI_NOTFOUND; + } + + // The gzip checking code is intentionally without #ifdefs because checking + // for FLAG_GZIP (which indicates gzip compressed file) is very easy, doesn't + // mean additional overhead and is actually safer to be on at all times. + // If there are no gzipped files in the image, the code bellow will not cause any harm. + + // Check if requested file was GZIP compressed + isGzip = espFsFlags(file) & FLAG_GZIP; + if (isGzip) { + // Check the browser's "Accept-Encoding" header. If the client does not + // advertise that he accepts GZIP send a warning message (telnet users for e.g.) + httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64); + if (os_strstr(acceptEncodingBuffer, "gzip") == NULL) { + //No Accept-Encoding: gzip header present + httpdSend(connData, gzipNonSupportedMessage, -1); + espFsClose(file); + return HTTPD_CGI_DONE; + } + } + + connData->cgiData=file; + httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); + if (isGzip) { + httpdHeader(connData, "Content-Encoding", "gzip"); + } + httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); + httpdEndHeaders(connData); + return HTTPD_CGI_MORE; + } + + len=espFsRead(file, buff, 1024); + if (len>0) espconn_sent(connData->conn, (uint8 *)buff, len); + if (len!=1024) { + //We're done. + espFsClose(file); + return HTTPD_CGI_DONE; + } else { + //Ok, till next time. + return HTTPD_CGI_MORE; + } +} + +#if 0 +//cgiEspFsHtml is a simple HTML file that gets prefixed by head.tpl +int ICACHE_FLASH_ATTR +cgiEspFsHtml(HttpdConnData *connData) { + EspFsFile *file = connData->cgiData; + char buff[2048]; + + if (connData->conn==NULL) { + // Connection aborted. Clean up. + if (file != NULL) espFsClose(file); + return HTTPD_CGI_DONE; + } + + // The first time around we send the head template in one go and we open the file + if (file == NULL) { + int status = 200; + // open file, return error on failure + file = espFsOpen("/head.tpl"); + if (file == NULL) { + os_strcpy(buff, "Header file 'head.tpl' not found\n"); + os_printf(buff); + status = 500; + goto error; + } + + // read file and return it + int len = espFsRead(file, buff, sizeof(buff)); + espFsClose(file); + if (len == sizeof(buff)) { + os_sprintf(buff, "Header file 'head.tpl' too large (%d>%d)!\n", len, sizeof(buff)); + os_printf(buff); + status = 500; + goto error; + } + + // before returning, open the real file for next time around + file = espFsOpen(connData->url); + if (file == NULL) { + os_strcpy(buff, connData->url); + os_strcat(buff, " not found\n"); + os_printf(buff); + status = 404; + goto error; + } + + connData->cgiData = file; + httpdStartResponse(connData, status); + httpdHeader(connData, "Content-Type", "text/html; charset=UTF-8"); + httpdEndHeaders(connData); + httpdSend(connData, buff, len); + printGlobalJSON(connData); + return HTTPD_CGI_MORE; + +error: // error response + httpdStartResponse(connData, status); + httpdHeader(connData, "Content-Type", "text/html; charset=UTF-8"); + httpdEndHeaders(connData); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; + } + + // The second time around send actual file + int len = espFsRead(file, buff, sizeof(buff)); + httpdSend(connData, buff, len); + if (len == sizeof(buff)) { + return HTTPD_CGI_MORE; + } else { + connData->cgiData = NULL; + espFsClose(file); + return HTTPD_CGI_DONE; + } +} +#endif + +#if 0 +//cgiEspFsTemplate can be used as a template. + +typedef struct { + EspFsFile *file; + void *tplArg; + char token[64]; + int tokenPos; +} TplData; + +typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg); + +int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) { + TplData *tpd=connData->cgiData; + int len; + int x, sp=0; + char *e=NULL; + char buff[1025]; + + if (connData->conn==NULL) { + //Connection aborted. Clean up. + ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); + espFsClose(tpd->file); + os_free(tpd); + return HTTPD_CGI_DONE; + } + + if (tpd==NULL) { + //First call to this cgi. Open the file so we can read it. + tpd=(TplData *)os_malloc(sizeof(TplData)); + tpd->file=espFsOpen(connData->url); + tpd->tplArg=NULL; + tpd->tokenPos=-1; + if (tpd->file==NULL) { + espFsClose(tpd->file); + os_free(tpd); + return HTTPD_CGI_NOTFOUND; + } + if (espFsFlags(tpd->file) & FLAG_GZIP) { + os_printf("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!\n", connData->url); + espFsClose(tpd->file); + os_free(tpd); + return HTTPD_CGI_NOTFOUND; + } + connData->cgiData=tpd; + httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); + httpdEndHeaders(connData); + return HTTPD_CGI_MORE; + } + + len=espFsRead(tpd->file, buff, 1024); + if (len>0) { + sp=0; + e=buff; + for (x=0; xtokenPos==-1) { + //Inside ordinary text. + if (buff[x]=='%') { + //Send raw data up to now + if (sp!=0) httpdSend(connData, e, sp); + sp=0; + //Go collect token chars. + tpd->tokenPos=0; + } else { + sp++; + } + } else { + if (buff[x]=='%') { + if (tpd->tokenPos==0) { + //This is the second % of a %% escape string. + //Send a single % and resume with the normal program flow. + httpdSend(connData, "%", 1); + } else { + //This is an actual token. + tpd->token[tpd->tokenPos++]=0; //zero-terminate token + ((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg); + } + //Go collect normal chars again. + e=&buff[x+1]; + tpd->tokenPos=-1; + } else { + if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x]; + } + } + } + } + //Send remaining bit. + if (sp!=0) httpdSend(connData, e, sp); + if (len!=1024) { + //We're done. + ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); + espFsClose(tpd->file); + os_free(tpd); + return HTTPD_CGI_DONE; + } else { + //Ok, till next time. + return HTTPD_CGI_MORE; + } +} +#endif + diff --git a/httpd/httpdespfs.h b/httpd/httpdespfs.h new file mode 100644 index 0000000..847a8b6 --- /dev/null +++ b/httpd/httpdespfs.h @@ -0,0 +1,14 @@ +#ifndef HTTPDESPFS_H +#define HTTPDESPFS_H + +#include +#include "espfs.h" +#include "espfsformat.h" +#include "cgi.h" +#include "httpd.h" + +int cgiEspFsHook(HttpdConnData *connData); +//int cgiEspFsTemplate(HttpdConnData *connData); +//int ICACHE_FLASH_ATTR cgiEspFsHtml(HttpdConnData *connData); + +#endif diff --git a/include/esp8266.h b/include/esp8266.h new file mode 100644 index 0000000..3e7eeb9 --- /dev/null +++ b/include/esp8266.h @@ -0,0 +1,27 @@ +// Combined include file for esp8266 +#ifndef _ESP8266_H_ +#define _ESP8266_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "espmissingincludes.h" +#include "uart_hw.h" + +#ifdef __WIN32__ +#include <_mingw.h> +#endif + +#endif // _ESP8266_H_ \ No newline at end of file diff --git a/include/espmissingincludes.h b/include/espmissingincludes.h new file mode 100644 index 0000000..c83b35a --- /dev/null +++ b/include/espmissingincludes.h @@ -0,0 +1,99 @@ +#ifndef ESPMISSINGINCLUDES_H +#define ESPMISSINGINCLUDES_H + +#include +#include +#include +#include +#include +#include + +//Missing function prototypes in include folders. Gcc will warn on these if we don't define 'em anywhere. +//MOST OF THESE ARE GUESSED! but they seem to work and shut up the compiler. +typedef struct espconn espconn; + +bool wifi_station_set_hostname(char *); +char *wifi_station_get_hostname(void); + +int atoi(const char *nptr); + +void ets_install_putc1(void *routine); // necessary for #define os_xxx -> ets_xxx +void ets_isr_attach(int intr, void *handler, void *arg); +void ets_isr_mask(unsigned intr); +void ets_isr_unmask(unsigned intr); + +int ets_memcmp(const void *s1, const void *s2, size_t n); +void *ets_memcpy(void *dest, const void *src, size_t n); +void *ets_memmove(void *dest, const void *src, size_t n); +void *ets_memset(void *s, int c, size_t n); +int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3))); +int ets_str2macaddr(void *, void *); +int ets_strcmp(const char *s1, const char *s2); +char *ets_strcpy(char *dest, const char *src); +size_t ets_strlen(const char *s); +int ets_strncmp(const char *s1, const char *s2, int len); +char *ets_strncpy(char *dest, const char *src, size_t n); +char *ets_strstr(const char *haystack, const char *needle); + +void ets_timer_arm_new(ETSTimer *a, int b, int c, int isMstimer); +void ets_timer_disarm(ETSTimer *a); +void ets_timer_setfn(ETSTimer *t, ETSTimerFunc *fn, void *parg); + +void ets_update_cpu_frequency(int freqmhz); + +#ifdef SDK_DBG +#define DEBUG_SDK true +#else +#define DEBUG_SDK false +#endif + +int ets_vsprintf(char *str, const char *format, va_list argptr); +int ets_vsnprintf(char *buffer, size_t sizeOfBuffer, const char *format, va_list argptr); +int os_snprintf(char *str, size_t size, const char *format, ...) __attribute__((format(printf, 3, 4))); +int os_printf_plus(const char *format, ...) __attribute__((format(printf, 1, 2))); + +/* +#undef os_printf +#define os_printf(format, ...) do { \ + system_set_os_print(true); \ + os_printf_plus(format, ## __VA_ARGS__); \ + system_set_os_print(DEBUG_SDK); \ + } while (0) +*/ + +// memory allocation functions are "different" due to memory debugging functionality +// added in SDK 1.4.0 +void vPortFree(void *ptr, char * file, int line); +void *pvPortMalloc(size_t xWantedSize, char * file, int line); +void *pvPortZalloc(size_t, char * file, int line); +void *vPortMalloc(size_t xWantedSize); +void pvPortFree(void *ptr); + +void uart_div_modify(int no, unsigned int freq); +uint32 system_get_time(); +int rand(void); +void ets_bzero(void *s, size_t n); +void ets_delay_us(int ms); + +// disappeared in SDK 1.1.0: +#define os_timer_done ets_timer_done +#define os_timer_handler_isr ets_timer_handler_isr +#define os_timer_init ets_timer_init + +// This is not missing in SDK 1.1.0 but causes a parens error +#undef PIN_FUNC_SELECT +#define PIN_FUNC_SELECT(PIN_NAME, FUNC) do { \ + WRITE_PERI_REG(PIN_NAME, \ + (READ_PERI_REG(PIN_NAME) & ~(PERIPHS_IO_MUX_FUNC< + * + * Copyright 1998, 2000 Aaron D. Gifford. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef HEADER_HMAC_SHA1_H +#define HEADER_HMAC_SHA1_H + +/* + * Include SHA-1 stuff - CHOOSE WHICH SOURCE to use for the SHA1 functions + * + * Use the below include if your system has a library with SHA1 and be sure + * to link to the library: + */ + +/* #include */ + +/* + * Or you can use Steve Reid's public domain SHA1 implementation: + */ + +#include "sha.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HMAC_SHA1_DIGEST_LENGTH 20 +#define HMAC_SHA1_BLOCK_LENGTH 64 + +/* The HMAC_SHA1 structure: */ +typedef struct _HMAC_SHA1_CTX { + unsigned char ipad[HMAC_SHA1_BLOCK_LENGTH]; + unsigned char opad[HMAC_SHA1_BLOCK_LENGTH]; + SHA_CTX shactx; + unsigned char key[HMAC_SHA1_BLOCK_LENGTH]; + unsigned int keylen; + unsigned int hashkey; +} HMAC_SHA1_CTX; + +#ifndef NOPROTO +void HMAC_SHA1_Init(HMAC_SHA1_CTX *ctx); +void HMAC_SHA1_UpdateKey(HMAC_SHA1_CTX *ctx, unsigned char *key, unsigned int keylen); +void HMAC_SHA1_EndKey(HMAC_SHA1_CTX *ctx); +void HMAC_SHA1_StartMessage(HMAC_SHA1_CTX *ctx); +void HMAC_SHA1_UpdateMessage(HMAC_SHA1_CTX *ctx, unsigned char *data, unsigned int datalen); +void HMAC_SHA1_EndMessage(unsigned char *out, HMAC_SHA1_CTX *ctx); +void HMAC_SHA1_Done(HMAC_SHA1_CTX *ctx); +#else +void HMAC_SHA1_Init(); +void HMAC_SHA1_UpdateKey(); +void HMAC_SHA1_EndKey(); +void HMAC_SHA1_StartMessage(); +void HMAC_SHA1_UpdateMessage(); +void HMAC_SHA1_EndMessage(); +void HMAC_SHA1_Done(); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/httpclient.h b/include/httpclient.h new file mode 100644 index 0000000..d251bd8 --- /dev/null +++ b/include/httpclient.h @@ -0,0 +1,40 @@ +#ifndef HTTPCLIENT_H +#define HTTPCLIENT_H + +#define BUFFER_SIZE_MAX 5000 + +/* + * "full_response" is a string containing all response headers and the response body. + * "response_body and "http_status" are extracted from "full_response" for convenience. + * + * A successful request corresponds to an HTTP status code of 200 (OK). + * More info at http://en.wikipedia.org/wiki/List_of_HTTP_status_codes + */ +typedef void (* http_callback)(char * response_body, int http_status, char * full_response); + +/* + * Download a web page from its URL. + * Try: + * http_get("http://wtfismyip.com/text", http_callback_example); + */ +void ICACHE_FLASH_ATTR http_get(const char * url, http_callback user_callback); + +/* + * Post data to a web form. + * The data should be encoded as application/x-www-form-urlencoded. + * Try: + * http_post("http://httpbin.org/post", "first_word=hello&second_word=world", http_callback_example); + */ +void ICACHE_FLASH_ATTR http_post(const char * url, const char * post_data, http_callback user_callback); + +/* + * Call this function to skip URL parsing if the arguments are already in separate variables. + */ +void ICACHE_FLASH_ATTR http_raw_request(const char * hostname, int port, const char * path, const char * post_data, http_callback user_callback); + +/* + * Output on the UART. + */ +void http_callback_example(char * response, int http_status, char * full_response); + +#endif diff --git a/include/jsmn.h b/include/jsmn.h new file mode 100644 index 0000000..95fb2ca --- /dev/null +++ b/include/jsmn.h @@ -0,0 +1,75 @@ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 +} jsmntype_t; + +typedef enum { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +} jsmnerr_t; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_H_ */ diff --git a/include/mfrc522.h b/include/mfrc522.h new file mode 100644 index 0000000..fc73ebb --- /dev/null +++ b/include/mfrc522.h @@ -0,0 +1,720 @@ +/** + * MFRC522.h - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT. + * Based on code Dr.Leong ( WWW.B2CQSHOP.COM ) + * Created by Miguel Balboa (circuitito.com), Jan, 2012. + * Rewritten by Soren Thing Andersen (access.thing.dk), fall of 2013 (Translation to English, refactored, comments, anti collision, cascade levels.) + * Ported to mbed by Martin Olejar, Dec, 2013 + * + * Please read this file for an overview and then MFRC522.cpp for comments on the specific functions. + * Search for "mf-rc522" on ebay.com to purchase the MF-RC522 board. + * + * There are three hardware components involved: + * 1) The micro controller: An Arduino + * 2) The PCD (short for Proximity Coupling Device): NXP MFRC522 Contactless Reader IC + * 3) The PICC (short for Proximity Integrated Circuit Card): A card or tag using the ISO 14443A interface, eg Mifare or NTAG203. + * + * The microcontroller and card reader uses SPI for communication. + * The protocol is described in the MFRC522 datasheet: http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * + * The card reader and the tags communicate using a 13.56MHz electromagnetic field. + * The protocol is defined in ISO/IEC 14443-3 Identification cards -- Contactless integrated circuit cards -- Proximity cards -- Part 3: Initialization and anticollision". + * A free version of the final draft can be found at http://wg8.de/wg8n1496_17n3613_Ballot_FCD14443-3.pdf + * Details are found in chapter 6, Type A: Initialization and anticollision. + * + * If only the PICC UID is wanted, the above documents has all the needed information. + * To read and write from MIFARE PICCs, the MIFARE protocol is used after the PICC has been selected. + * The MIFARE Classic chips and protocol is described in the datasheets: + * 1K: http://www.nxp.com/documents/data_sheet/MF1S503x.pdf + * 4K: http://www.nxp.com/documents/data_sheet/MF1S703x.pdf + * Mini: http://www.idcardmarket.com/download/mifare_S20_datasheet.pdf + * The MIFARE Ultralight chip and protocol is described in the datasheets: + * Ultralight: http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf + * Ultralight C: http://www.nxp.com/documents/short_data_sheet/MF0ICU2_SDS.pdf + * + * MIFARE Classic 1K (MF1S503x): + * Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. + * The blocks are numbered 0-63. + * Block 3 in each sector is the Sector Trailer. See http://www.nxp.com/documents/data_sheet/MF1S503x.pdf sections 8.6 and 8.7: + * Bytes 0-5: Key A + * Bytes 6-8: Access Bits + * Bytes 9: User data + * Bytes 10-15: Key B (or user data) + * Block 0 is read only manufacturer data. + * To access a block, an authentication using a key from the block's sector must be performed first. + * Example: To read from block 10, first authenticate using a key from sector 3 (blocks 8-11). + * All keys are set to FFFFFFFFFFFFh at chip delivery. + * Warning: Please read section 8.7 "Memory Access". It includes this text: if the PICC detects a format violation the whole sector is irreversibly blocked. + * To use a block in "value block" mode (for Increment/Decrement operations) you need to change the sector trailer. Use PICC_SetAccessBits() to calculate the bit patterns. + * MIFARE Classic 4K (MF1S703x): + * Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes. + * The blocks are numbered 0-255. + * The last block in each sector is the Sector Trailer like above. + * MIFARE Classic Mini (MF1 IC S20): + * Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. + * The blocks are numbered 0-19. + * The last block in each sector is the Sector Trailer like above. + * + * MIFARE Ultralight (MF0ICU1): + * Has 16 pages of 4 bytes = 64 bytes. + * Pages 0 + 1 is used for the 7-byte UID. + * Page 2 contains the last chech digit for the UID, one byte manufacturer internal data, and the lock bytes (see http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2) + * Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0. + * Pages 4-15 are read/write unless blocked by the lock bytes in page 2. + * MIFARE Ultralight C (MF0ICU2): + * Has 48 pages of 4 bytes = 64 bytes. + * Pages 0 + 1 is used for the 7-byte UID. + * Page 2 contains the last chech digit for the UID, one byte manufacturer internal data, and the lock bytes (see http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2) + * Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0. + * Pages 4-39 are read/write unless blocked by the lock bytes in page 2. + * Page 40 Lock bytes + * Page 41 16 bit one way counter + * Pages 42-43 Authentication configuration + * Pages 44-47 Authentication key + */ +#ifndef MFRC522_h +#define MFRC522_h + +#include "espmissingincludes.h" +#include "c_types.h" +#include "osapi.h" + +#include +#include "gpio.h" +#include "spi.h" +#include "gpio16.h" + + + /** + * MFRC522 registers (described in chapter 9 of the datasheet). + * When using SPI all addresses are shifted one bit left in the "SPI address byte" (section 8.1.2.3) + */ + enum PCD_Register { + // Page 0: Command and status + // 0x00 // reserved for future use + CommandReg = 0x01 << 1, // starts and stops command execution + ComIEnReg = 0x02 << 1, // enable and disable interrupt request control bits + DivIEnReg = 0x03 << 1, // enable and disable interrupt request control bits + ComIrqReg = 0x04 << 1, // interrupt request bits + DivIrqReg = 0x05 << 1, // interrupt request bits + ErrorReg = 0x06 << 1, // error bits showing the error status of the last command executed + Status1Reg = 0x07 << 1, // communication status bits + Status2Reg = 0x08 << 1, // receiver and transmitter status bits + FIFODataReg = 0x09 << 1, // input and output of 64 byte FIFO buffer + FIFOLevelReg = 0x0A << 1, // number of bytes stored in the FIFO buffer + WaterLevelReg = 0x0B << 1, // level for FIFO underflow and overflow warning + ControlReg = 0x0C << 1, // miscellaneous control registers + BitFramingReg = 0x0D << 1, // adjustments for bit-oriented frames + CollReg = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface + // 0x0F // reserved for future use + + // Page 1:Command + // 0x10 // reserved for future use + ModeReg = 0x11 << 1, // defines general modes for transmitting and receiving + TxModeReg = 0x12 << 1, // defines transmission data rate and framing + RxModeReg = 0x13 << 1, // defines reception data rate and framing + TxControlReg = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2 + TxASKReg = 0x15 << 1, // controls the setting of the transmission modulation + TxSelReg = 0x16 << 1, // selects the internal sources for the antenna driver + RxSelReg = 0x17 << 1, // selects internal receiver settings + RxThresholdReg = 0x18 << 1, // selects thresholds for the bit decoder + DemodReg = 0x19 << 1, // defines demodulator settings + // 0x1A // reserved for future use + // 0x1B // reserved for future use + MfTxReg = 0x1C << 1, // controls some MIFARE communication transmit parameters + MfRxReg = 0x1D << 1, // controls some MIFARE communication receive parameters + // 0x1E // reserved for future use + SerialSpeedReg = 0x1F << 1, // selects the speed of the serial UART interface + + // Page 2: Configuration + // 0x20 // reserved for future use + CRCResultRegH = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation + CRCResultRegL = 0x22 << 1, + // 0x23 // reserved for future use + ModWidthReg = 0x24 << 1, // controls the ModWidth setting? + // 0x25 // reserved for future use + RFCfgReg = 0x26 << 1, // configures the receiver gain + GsNReg = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation + CWGsPReg = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation + ModGsPReg = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation + TModeReg = 0x2A << 1, // defines settings for the internal timer + TPrescalerReg = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. + TReloadRegH = 0x2C << 1, // defines the 16-bit timer reload value + TReloadRegL = 0x2D << 1, + TCntValueRegH = 0x2E << 1, // shows the 16-bit timer value + TCntValueRegL = 0x2F << 1, + + // Page 3:Test Registers + // 0x30 // reserved for future use + TestSel1Reg = 0x31 << 1, // general test signal configuration + TestSel2Reg = 0x32 << 1, // general test signal configuration + TestPinEnReg = 0x33 << 1, // enables pin output driver on pins D1 to D7 + TestPinValueReg = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus + TestBusReg = 0x35 << 1, // shows the status of the internal test bus + AutoTestReg = 0x36 << 1, // controls the digital self test + VersionReg = 0x37 << 1, // shows the software version + AnalogTestReg = 0x38 << 1, // controls the pins AUX1 and AUX2 + TestDAC1Reg = 0x39 << 1, // defines the test value for TestDAC1 + TestDAC2Reg = 0x3A << 1, // defines the test value for TestDAC2 + TestADCReg = 0x3B << 1 // shows the value of ADC I and Q channels + // 0x3C // reserved for production tests + // 0x3D // reserved for production tests + // 0x3E // reserved for production tests + // 0x3F // reserved for production tests + }; + + // MFRC522 commands Described in chapter 10 of the datasheet. + enum PCD_Command { + PCD_Idle = 0x00, // no action, cancels current command execution + PCD_Mem = 0x01, // stores 25 bytes into the internal buffer + PCD_GenerateRandomID = 0x02, // generates a 10-byte random ID number + PCD_CalcCRC = 0x03, // activates the CRC coprocessor or performs a self test + PCD_Transmit = 0x04, // transmits data from the FIFO buffer + PCD_NoCmdChange = 0x07, // no command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit + PCD_Receive = 0x08, // activates the receiver circuits + PCD_Transceive = 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission + PCD_MFAuthent = 0x0E, // performs the MIFARE standard authentication as a reader + PCD_SoftReset = 0x0F // resets the MFRC522 + }; + + // Commands sent to the PICC. + enum PICC_Command { + // The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) + PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. + PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. + PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. + PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 + PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 1 + PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 1 + PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. + + // The commands used for MIFARE Classic (from http://www.nxp.com/documents/data_sheet/MF1S503x.pdf, Section 9) + // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on the sector. + // The read/write commands can also be used for MIFARE Ultralight. + PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A + PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B + PICC_CMD_MF_READ = 0x30, // Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. + PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight. + PICC_CMD_MF_DECREMENT = 0xC0, // Decrements the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_INCREMENT = 0xC1, // Increments the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register. + PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block. + + // The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) + // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. + PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 byte page to the PICC. + }; + + // MIFARE constants that does not fit anywhere else + enum MIFARE_Misc { + MF_ACK = 0xA, // The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK. + MF_KEY_SIZE = 6 // A Mifare Crypto1 key is 6 bytes. + }; + + // PICC types we can detect. Remember to update PICC_GetTypeName() if you add more. + enum PICC_Type { + PICC_TYPE_UNKNOWN = 0, + PICC_TYPE_ISO_14443_4 = 1, // PICC compliant with ISO/IEC 14443-4 + PICC_TYPE_ISO_18092 = 2, // PICC compliant with ISO/IEC 18092 (NFC) + PICC_TYPE_MIFARE_MINI = 3, // MIFARE Classic protocol, 320 bytes + PICC_TYPE_MIFARE_1K = 4, // MIFARE Classic protocol, 1KB + PICC_TYPE_MIFARE_4K = 5, // MIFARE Classic protocol, 4KB + PICC_TYPE_MIFARE_UL = 6, // MIFARE Ultralight or Ultralight C + PICC_TYPE_MIFARE_PLUS = 7, // MIFARE Plus + PICC_TYPE_TNP3XXX = 8, // Only mentioned in NXP AN 10833 MIFARE Type Identification Procedure + PICC_TYPE_NOT_COMPLETE = 255 // SAK indicates UID is not complete. + }; + + // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. + enum StatusCode { + STATUS_OK = 1, // Success + STATUS_ERROR = 2, // Error in communication + STATUS_COLLISION = 3, // Collision detected + STATUS_TIMEOUT = 4, // Timeout in communication. + STATUS_NO_ROOM = 5, // A buffer is not big enough. + STATUS_INTERNAL_ERROR = 6, // Internal error in the code. Should not happen ;-) + STATUS_INVALID = 7, // Invalid argument. + STATUS_CRC_WRONG = 8, // The CRC_A does not match + STATUS_MIFARE_NACK = 9 // A MIFARE PICC responded with NAK. + }; + + // A struct used for passing the UID of a PICC. + typedef struct { + uint8_t size; // Number of bytes in the UID. 4, 7 or 10. + uint8_t uidByte[10]; + uint8_t sak; // The SAK (Select acknowledge) byte returned from the PICC after successful selection. + } Uid; + + // A struct used for passing a MIFARE Crypto1 key + typedef struct { + uint8_t keyByte[MF_KEY_SIZE]; + } MIFARE_Key; + + // Member variables + Uid uid; // Used by PICC_ReadCardSerial(). + + // Size of the MFRC522 FIFO + static const uint8_t FIFO_SIZE = 64; // The FIFO is 64 bytes. + + /** + * MFRC522 constructor + * + * @param mosi SPI MOSI pin + * @param miso SPI MISO pin + * @param sclk SPI SCLK pin + * @param cs SPI CS pin + * @param reset Reset pin + */ + //MFRC522(PinName mosi, PinName miso, PinName sclk, PinName cs, PinName reset); + + /** + * MFRC522 destructor + */ + //~MFRC522(); + + void wait_ms (int ms); + + // ************************************************************************************ + //! @name Functions for manipulating the MFRC522 + // ************************************************************************************ + //@{ + + /** + * Initializes the MFRC522 chip. + */ + void PCD_Init (void); + + /** + * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. + */ + void PCD_Reset (void); + + /** + * Turns the antenna on by enabling pins TX1 and TX2. + * After a reset these pins disabled. + */ + void PCD_AntennaOn (void); + + /** + * Writes a byte to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + * + * @param reg The register to write to. One of the PCD_Register enums. + * @param value The value to write. + */ + void PCD_WriteRegister (uint8_t reg, uint8_t value); + + /** + * Writes a number of bytes to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + * + * @param reg The register to write to. One of the PCD_Register enums. + * @param count The number of bytes to write to the register + * @param values The values to write. Byte array. + */ + void PCD_WriteRegister2 (uint8_t reg, uint8_t count, uint8_t *values); + + /** + * Reads a byte from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + * + * @param reg The register to read from. One of the PCD_Register enums. + * @returns Register value + */ + uint8_t PCD_ReadRegister (uint8_t reg); + + /** + * Reads a number of bytes from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + * + * @param reg The register to read from. One of the PCD_Register enums. + * @param count The number of bytes to read. + * @param values Byte array to store the values in. + * @param rxAlign Only bit positions rxAlign..7 in values[0] are updated. + */ + void PCD_ReadRegister2 (uint8_t reg, uint8_t count, uint8_t *values, uint8_t rxAlign); + + /** + * Sets the bits given in mask in register reg. + * + * @param reg The register to update. One of the PCD_Register enums. + * @param mask The bits to set. + */ + void PCD_SetRegisterBits(uint8_t reg, uint8_t mask); + + /** + * Clears the bits given in mask from register reg. + * + * @param reg The register to update. One of the PCD_Register enums. + * @param mask The bits to clear. + */ + void PCD_ClrRegisterBits(uint8_t reg, uint8_t mask); + + /** + * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. + * + * @param data Pointer to the data to transfer to the FIFO for CRC calculation. + * @param length The number of bytes to transfer. + * @param result Pointer to result buffer. Result is written to result[0..1], low byte first. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PCD_CalculateCRC (uint8_t *data, uint8_t length, uint8_t *result); + + /** + * Executes the Transceive command. + * CRC validation can only be done if backData and backLen are specified. + * + * @param sendData Pointer to the data to transfer to the FIFO. + * @param sendLen Number of bytes to transfer to the FIFO. + * @param backData NULL or pointer to buffer if data should be read back after executing the command. + * @param backLen Max number of bytes to write to *backData. Out: The number of bytes returned. + * @param validBits The number of valid bits in the last byte. 0 for 8 valid bits. Default NULL. + * @param rxAlign Defines the bit position in backData[0] for the first bit received. Default 0. + * @param checkCRC True => The last two bytes of the response is assumed to be a CRC_A that must be validated. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PCD_TransceiveData (uint8_t *sendData, + uint8_t sendLen, + uint8_t *backData, + uint8_t *backLen, + uint8_t *validBits, + uint8_t rxAlign, + bool checkCRC); + + + /** + * Transfers data to the MFRC522 FIFO, executes a commend, waits for completion and transfers data back from the FIFO. + * CRC validation can only be done if backData and backLen are specified. + * + * @param command The command to execute. One of the PCD_Command enums. + * @param waitIRq The bits in the ComIrqReg register that signals successful completion of the command. + * @param sendData Pointer to the data to transfer to the FIFO. + * @param sendLen Number of bytes to transfer to the FIFO. + * @param backData NULL or pointer to buffer if data should be read back after executing the command. + * @param backLen In: Max number of bytes to write to *backData. Out: The number of bytes returned. + * @param validBits In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. + * @param rxAlign In: Defines the bit position in backData[0] for the first bit received. Default 0. + * @param checkCRC In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PCD_CommunicateWithPICC(uint8_t command, + uint8_t waitIRq, + uint8_t *sendData, + uint8_t sendLen, + uint8_t *backData, + uint8_t *backLen, + uint8_t *validBits, + uint8_t rxAlign, + bool checkCRC); + + /** + * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PICC_RequestA (uint8_t *bufferATQA, uint8_t *bufferSize); + + /** + * Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PICC_WakeupA (uint8_t *bufferATQA, uint8_t *bufferSize); + + /** + * Transmits REQA or WUPA commands. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @param command The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PICC_REQA_or_WUPA (uint8_t command, uint8_t *bufferATQA, uint8_t *bufferSize); + + /** + * Transmits SELECT/ANTICOLLISION commands to select a single PICC. + * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or PICC_WakeupA(). + * On success: + * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the ISO/IEC 14443-3 draft.) + * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. + * + * A PICC UID consists of 4, 7 or 10 bytes. + * Only 4 bytes can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: + * + * UID size Number of UID bytes Cascade levels Example of PICC + * ======== =================== ============== =============== + * single 4 1 MIFARE Classic + * double 7 2 MIFARE Ultralight + * triple 10 3 Not currently in use? + * + * + * @param uid Pointer to Uid struct. Normally output, but can also be used to supply a known UID. + * @param validBits The number of known UID bits supplied in *uid. Normally 0. If set you must also supply uid->size. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PICC_Select (Uid *uid, uint8_t validBits); + + /** + * Instructs a PICC in state ACTIVE(*) to go to state HALT. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PICC_HaltA (void); + + // ************************************************************************************ + //@} + + + // ************************************************************************************ + //! @name Functions for communicating with MIFARE PICCs + // ************************************************************************************ + //@{ + + /** + * Executes the MFRC522 MFAuthent command. + * This command manages MIFARE authentication to enable a secure communication to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. + * The authentication is described in the MFRC522 datasheet section 10.3.1.9 and http://www.nxp.com/documents/data_sheet/MF1S503x.pdf section 10.1. + * For use with MIFARE Classic PICCs. + * The PICC must be selected - ie in state ACTIVE(*) - before calling this function. + * Remember to call PCD_StopCrypto1() after communicating with the authenticated PICC - otherwise no new communications can start. + * + * All keys are set to FFFFFFFFFFFFh at chip delivery. + * + * @param command PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B + * @param blockAddr The block number. See numbering in the comments in the .h file. + * @param key Pointer to the Crypteo1 key to use (6 bytes) + * @param uid Pointer to Uid struct. The first 4 bytes of the UID is used. + * + * @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT if you supply the wrong key. + */ + uint8_t PCD_Authenticate (uint8_t command, uint8_t blockAddr, MIFARE_Key *key, Uid *uid); + + /** + * Used to exit the PCD from its authenticated state. + * Remember to call this function after communicating with an authenticated PICC - otherwise no new communications can start. + */ + void PCD_StopCrypto1 (void); + + /** + * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. + * + * For MIFARE Classic the sector containing the block must be authenticated before calling this function. + * + * For MIFARE Ultralight only addresses 00h to 0Fh are decoded. + * The MF0ICU1 returns a NAK for higher addresses. + * The MF0ICU1 responds to the READ command by sending 16 bytes starting from the page address defined by the command argument. + * For example; if blockAddr is 03h then pages 03h, 04h, 05h, 06h are returned. + * A roll-back is implemented: If blockAddr is 0Eh, then the contents of pages 0Eh, 0Fh, 00h and 01h are returned. + * + * The buffer must be at least 18 bytes because a CRC_A is also returned. + * Checks the CRC_A before returning STATUS_OK. + * + * @param blockAddr MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The first page to return data from. + * @param buffer The buffer to store the data in + * @param bufferSize Buffer size, at least 18 bytes. Also number of bytes returned if STATUS_OK. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t MIFARE_Read (uint8_t blockAddr, uint8_t *buffer, uint8_t *bufferSize); + + /** + * Writes 16 bytes to the active PICC. + * + * For MIFARE Classic the sector containing the block must be authenticated before calling this function. + * + * For MIFARE Ultralight the opretaion is called "COMPATIBILITY WRITE". + * Even though 16 bytes are transferred to the Ultralight PICC, only the least significant 4 bytes (bytes 0 to 3) + * are written to the specified address. It is recommended to set the remaining bytes 04h to 0Fh to all logic 0. + * + * @param blockAddr MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The page (2-15) to write to. + * @param buffer The 16 bytes to write to the PICC + * @param bufferSize Buffer size, must be at least 16 bytes. Exactly 16 bytes are written. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t MIFARE_Write (uint8_t blockAddr, uint8_t *buffer, uint8_t bufferSize); + + /** + * Writes a 4 byte page to the active MIFARE Ultralight PICC. + * + * @param page The page (2-15) to write to. + * @param buffer The 4 bytes to write to the PICC + * @param bufferSize Buffer size, must be at least 4 bytes. Exactly 4 bytes are written. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t MIFARE_UltralightWrite(uint8_t page, uint8_t *buffer, uint8_t bufferSize); + + /** + * MIFARE Decrement subtracts the delta from the value of the addressed block, and stores the result in a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @param blockAddr The block (0-0xff) number. + * @param delta This number is subtracted from the value of block blockAddr. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t MIFARE_Decrement (uint8_t blockAddr, uint32_t delta); + + /** + * MIFARE Increment adds the delta to the value of the addressed block, and stores the result in a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @param blockAddr The block (0-0xff) number. + * @param delta This number is added to the value of block blockAddr. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t MIFARE_Increment (uint8_t blockAddr, uint32_t delta); + + /** + * MIFARE Restore copies the value of the addressed block into a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @param blockAddr The block (0-0xff) number. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t MIFARE_Restore (uint8_t blockAddr); + + /** + * MIFARE Transfer writes the value stored in the volatile memory into one MIFARE Classic block. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * + * @param blockAddr The block (0-0xff) number. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t MIFARE_Transfer (uint8_t blockAddr); + + // ************************************************************************************ + //@} + + + // ************************************************************************************ + //! @name Support functions + // ************************************************************************************ + //@{ + + /** + * Wrapper for MIFARE protocol communication. + * Adds CRC_A, executes the Transceive command and checks that the response is MF_ACK or a timeout. + * + * @param sendData Pointer to the data to transfer to the FIFO. Do NOT include the CRC_A. + * @param sendLen Number of bytes in sendData. + * @param acceptTimeout True => A timeout is also success + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t PCD_MIFARE_Transceive(uint8_t *sendData, uint8_t sendLen, bool acceptTimeout); + + /** + * Translates the SAK (Select Acknowledge) to a PICC type. + * + * @param sak The SAK byte returned from PICC_Select(). + * + * @return PICC_Type + */ + uint8_t PICC_GetType (uint8_t sak); + + /** + * Returns a string pointer to the PICC type name. + * + * @param type One of the PICC_Type enums. + * + * @return A string pointer to the PICC type name. + */ + char* PICC_GetTypeName (uint8_t type); + + /** + * Returns a string pointer to a status code name. + * + * @param code One of the StatusCode enums. + * + * @return A string pointer to a status code name. + */ + char* GetStatusCodeName (uint8_t code); + + /** + * Calculates the bit pattern needed for the specified access bits. In the [C1 C2 C3] tupples C1 is MSB (=4) and C3 is LSB (=1). + * + * @param accessBitBuffer Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set. + * @param g0 Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) + * @param g1 Access bits [C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) + * @param g2 Access bits [C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) + * @param g3 Access bits [C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) + */ + void MIFARE_SetAccessBits (uint8_t *accessBitBuffer, + uint8_t g0, + uint8_t g1, + uint8_t g2, + uint8_t g3); + + // ************************************************************************************ + //@} + + + // ************************************************************************************ + //! @name Convenience functions - does not add extra functionality + // ************************************************************************************ + //@{ + + /** + * Returns true if a PICC responds to PICC_CMD_REQA. + * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. + * + * @return bool + */ + bool PICC_IsNewCardPresent(void); + + /** + * Simple wrapper around PICC_Select. + * Returns true if a UID could be read. + * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. + * The read UID is available in the class variable uid. + * + * @return bool + */ + bool PICC_ReadCardSerial (void); + + // ************************************************************************************ + //@} + + +//private: + //SPI m_SPI; + //DigitalOut m_CS; + //DigitalOut m_RESET; + + /** + * Helper function for the two-step MIFARE Classic protocol operations Decrement, Increment and Restore. + * + * @param command The command to use + * @param blockAddr The block (0-0xff) number. + * @param data The data to transfer in step 2 + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + uint8_t MIFARE_TwoStepHelper(uint8_t command, uint8_t blockAddr, uint32_t data); +//}; + +#endif diff --git a/include/sha.h b/include/sha.h new file mode 100644 index 0000000..f9cfbbb --- /dev/null +++ b/include/sha.h @@ -0,0 +1,74 @@ +/* + * sha.h + * + * Originally taken from the public domain SHA1 implementation + * written by by Steve Reid + * + * Modified by Aaron D. Gifford + * + * NO COPYRIGHT - THIS IS 100% IN THE PUBLIC DOMAIN + * + * The original unmodified version is available at: + * ftp://ftp.funet.fi/pub/crypt/hash/sha/sha1.c + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __SHA1_H__ +#define __SHA1_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Define this if your machine is LITTLE_ENDIAN, otherwise #undef it: */ +#define LITTLE_ENDIAN + +/* Make sure you define these types for your architecture: */ +typedef unsigned int sha1_quadbyte; /* 4 byte type */ +typedef unsigned char sha1_byte; /* single byte type */ + +/* + * Be sure to get the above definitions right. For instance, on my + * x86 based FreeBSD box, I define LITTLE_ENDIAN and use the type + * "unsigned long" for the quadbyte. On FreeBSD on the Alpha, however, + * while I still use LITTLE_ENDIAN, I must define the quadbyte type + * as "unsigned int" instead. + */ + +#define SHA1_BLOCK_LENGTH 64 +#define SHA1_DIGEST_LENGTH 20 + +/* The SHA1 structure: */ +typedef struct _SHA_CTX { + sha1_quadbyte state[5]; + sha1_quadbyte count[2]; + sha1_byte buffer[SHA1_BLOCK_LENGTH]; +} SHA_CTX; + +#ifndef NOPROTO +void SHA1_Init(SHA_CTX *context); +void SHA1_Update(SHA_CTX *context, sha1_byte *data, unsigned int len); +void SHA1_Final(sha1_byte digest[SHA1_DIGEST_LENGTH], SHA_CTX* context); +#else +void SHA1_Init(); +void SHA1_Update(); +void SHA1_Final(); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/include/spi.h b/include/spi.h new file mode 100644 index 0000000..842f2fd --- /dev/null +++ b/include/spi.h @@ -0,0 +1,21 @@ +#ifndef SPI_APP_H +#define SPI_APP_H + +#include "spi_register.h" +#include "ets_sys.h" +#include "osapi.h" +#include "os_type.h" +#include "c_types.h" +#include "user_interface.h" +#include "gpio.h" + +#define MISO 12 +#define MOSI 13 +#define CLK 14 +#define SS 15 +#define SPI_DELAY 2 + +void spi_init(); +uint8_t platform_spi_send_recv(int spi_periph, uint8_t val, bool close); + +#endif diff --git a/include/spi_master.h b/include/spi_master.h new file mode 100644 index 0000000..ad33e8b --- /dev/null +++ b/include/spi_master.h @@ -0,0 +1,13 @@ +#ifndef __SPI_MASTER_H__ +#define __SPI_MASTER_H__ + +#include "driver/spi_register.h" + +/*SPI number define*/ +#define SPI 0 +#define HSPI 1 + +void spi_master_init(uint8 spi_no); +void spi_master_9bit_write(uint8 spi_no, uint8 high_bit, uint8 low_8bit); + +#endif diff --git a/include/spi_register.h b/include/spi_register.h new file mode 100644 index 0000000..e0756ef --- /dev/null +++ b/include/spi_register.h @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2010 - 2011 Espressif System + * + */ + +#ifndef SPI_REGISTER_H_INCLUDED +#define SPI_REGISTER_H_INCLUDED + +#define REG_SPI_BASE(i) (0x60000200-i*0x100) +#define SPI_CMD(i) (REG_SPI_BASE(i) + 0x0) +#define SPI_USR (BIT(18)) + +#define SPI_ADDR(i) (REG_SPI_BASE(i) + 0x4) + +#define SPI_CTRL(i) (REG_SPI_BASE(i) + 0x8) +#define SPI_WR_BIT_ORDER (BIT(26)) +#define SPI_RD_BIT_ORDER (BIT(25)) +#define SPI_QIO_MODE (BIT(24)) +#define SPI_DIO_MODE (BIT(23)) +#define SPI_QOUT_MODE (BIT(20)) +#define SPI_DOUT_MODE (BIT(14)) +#define SPI_FASTRD_MODE (BIT(13)) + + + +#define SPI_RD_STATUS(i) (REG_SPI_BASE(i) + 0x10) + +#define SPI_CTRL2(i) (REG_SPI_BASE(i) + 0x14) + +#define SPI_CS_DELAY_NUM 0x0000000F +#define SPI_CS_DELAY_NUM_S 28 +#define SPI_CS_DELAY_MODE 0x00000003 +#define SPI_CS_DELAY_MODE_S 26 +#define SPI_MOSI_DELAY_NUM 0x00000007 +#define SPI_MOSI_DELAY_NUM_S 23 +#define SPI_MOSI_DELAY_MODE 0x00000003 +#define SPI_MOSI_DELAY_MODE_S 21 +#define SPI_MISO_DELAY_NUM 0x00000007 +#define SPI_MISO_DELAY_NUM_S 18 +#define SPI_MISO_DELAY_MODE 0x00000003 +#define SPI_MISO_DELAY_MODE_S 16 +#define SPI_CK_OUT_HIGH_MODE 0x0000000F +#define SPI_CK_OUT_HIGH_MODE_S 12 +#define SPI_CK_OUT_LOW_MODE 0x0000000F +#define SPI_CK_OUT_LOW_MODE_S 8 + +#define SPI_CLOCK(i) (REG_SPI_BASE(i) + 0x18) +#define SPI_CLK_EQU_SYSCLK (BIT(31)) +#define SPI_CLKDIV_PRE 0x00001FFF +#define SPI_CLKDIV_PRE_S 18 +#define SPI_CLKCNT_N 0x0000003F +#define SPI_CLKCNT_N_S 12 +#define SPI_CLKCNT_H 0x0000003F +#define SPI_CLKCNT_H_S 6 +#define SPI_CLKCNT_L 0x0000003F +#define SPI_CLKCNT_L_S 0 + +#define SPI_USER(i) (REG_SPI_BASE(i) + 0x1C) +#define SPI_USR_COMMAND (BIT(31)) +#define SPI_USR_ADDR (BIT(30)) +#define SPI_USR_DUMMY (BIT(29)) +#define SPI_USR_MISO (BIT(28)) +#define SPI_USR_MOSI (BIT(27)) + +#define SPI_USR_MOSI_HIGHPART (BIT(25)) +#define SPI_USR_MISO_HIGHPART (BIT(24)) + + +#define SPI_SIO (BIT(16)) +#define SPI_FWRITE_QIO (BIT(15)) +#define SPI_FWRITE_DIO (BIT(14)) +#define SPI_FWRITE_QUAD (BIT(13)) +#define SPI_FWRITE_DUAL (BIT(12)) +#define SPI_WR_BYTE_ORDER (BIT(11)) +#define SPI_RD_BYTE_ORDER (BIT(10)) +#define SPI_CK_OUT_EDGE (BIT(7)) +#define SPI_CK_I_EDGE (BIT(6)) +#define SPI_CS_SETUP (BIT(5)) +#define SPI_CS_HOLD (BIT(4)) +#define SPI_FLASH_MODE (BIT(2)) +#define SPI_DOUTDIN (BIT(0)) + +#define SPI_USER1(i) (REG_SPI_BASE(i) + 0x20) +#define SPI_USR_ADDR_BITLEN 0x0000003F +#define SPI_USR_ADDR_BITLEN_S 26 +#define SPI_USR_MOSI_BITLEN 0x000001FF +#define SPI_USR_MOSI_BITLEN_S 17 +#define SPI_USR_MISO_BITLEN 0x000001FF +#define SPI_USR_MISO_BITLEN_S 8 + +#define SPI_USR_DUMMY_CYCLELEN 0x000000FF +#define SPI_USR_DUMMY_CYCLELEN_S 0 + +#define SPI_USER2(i) (REG_SPI_BASE(i) + 0x24) +#define SPI_USR_COMMAND_BITLEN 0x0000000F +#define SPI_USR_COMMAND_BITLEN_S 28 +#define SPI_USR_COMMAND_VALUE 0x0000FFFF +#define SPI_USR_COMMAND_VALUE_S 0 + +#define SPI_WR_STATUS(i) (REG_SPI_BASE(i) + 0x28) +#define SPI_PIN(i) (REG_SPI_BASE(i) + 0x2C) +#define SPI_CS2_DIS (BIT(2)) +#define SPI_CS1_DIS (BIT(1)) +#define SPI_CS0_DIS (BIT(0)) + +#define SPI_SLAVE(i) (REG_SPI_BASE(i) + 0x30) +#define SPI_SYNC_RESET (BIT(31)) +#define SPI_SLAVE_MODE (BIT(30)) +#define SPI_SLV_WR_RD_BUF_EN (BIT(29)) +#define SPI_SLV_WR_RD_STA_EN (BIT(28)) +#define SPI_SLV_CMD_DEFINE (BIT(27)) +#define SPI_TRANS_CNT 0x0000000F +#define SPI_TRANS_CNT_S 23 +#define SPI_TRANS_DONE_EN (BIT(9)) +#define SPI_SLV_WR_STA_DONE_EN (BIT(8)) +#define SPI_SLV_RD_STA_DONE_EN (BIT(7)) +#define SPI_SLV_WR_BUF_DONE_EN (BIT(6)) +#define SPI_SLV_RD_BUF_DONE_EN (BIT(5)) + + + +#define SLV_SPI_INT_EN 0x0000001f +#define SLV_SPI_INT_EN_S 5 + +#define SPI_TRANS_DONE (BIT(4)) +#define SPI_SLV_WR_STA_DONE (BIT(3)) +#define SPI_SLV_RD_STA_DONE (BIT(2)) +#define SPI_SLV_WR_BUF_DONE (BIT(1)) +#define SPI_SLV_RD_BUF_DONE (BIT(0)) + +#define SPI_SLAVE1(i) (REG_SPI_BASE(i) + 0x34) +#define SPI_SLV_STATUS_BITLEN 0x0000001F +#define SPI_SLV_STATUS_BITLEN_S 27 +#define SPI_SLV_BUF_BITLEN 0x000001FF +#define SPI_SLV_BUF_BITLEN_S 16 +#define SPI_SLV_RD_ADDR_BITLEN 0x0000003F +#define SPI_SLV_RD_ADDR_BITLEN_S 10 +#define SPI_SLV_WR_ADDR_BITLEN 0x0000003F +#define SPI_SLV_WR_ADDR_BITLEN_S 4 + +#define SPI_SLV_WRSTA_DUMMY_EN (BIT(3)) +#define SPI_SLV_RDSTA_DUMMY_EN (BIT(2)) +#define SPI_SLV_WRBUF_DUMMY_EN (BIT(1)) +#define SPI_SLV_RDBUF_DUMMY_EN (BIT(0)) + + + +#define SPI_SLAVE2(i) (REG_SPI_BASE(i) + 0x38) +#define SPI_SLV_WRBUF_DUMMY_CYCLELEN 0X000000FF +#define SPI_SLV_WRBUF_DUMMY_CYCLELEN_S 24 +#define SPI_SLV_RDBUF_DUMMY_CYCLELEN 0X000000FF +#define SPI_SLV_RDBUF_DUMMY_CYCLELEN_S 16 +#define SPI_SLV_WRSTR_DUMMY_CYCLELEN 0X000000FF +#define SPI_SLV_WRSTR_DUMMY_CYCLELEN_S 8 +#define SPI_SLV_RDSTR_DUMMY_CYCLELEN 0x000000FF +#define SPI_SLV_RDSTR_DUMMY_CYCLELEN_S 0 + +#define SPI_SLAVE3(i) (REG_SPI_BASE(i) + 0x3C) +#define SPI_SLV_WRSTA_CMD_VALUE 0x000000FF +#define SPI_SLV_WRSTA_CMD_VALUE_S 24 +#define SPI_SLV_RDSTA_CMD_VALUE 0x000000FF +#define SPI_SLV_RDSTA_CMD_VALUE_S 16 +#define SPI_SLV_WRBUF_CMD_VALUE 0x000000FF +#define SPI_SLV_WRBUF_CMD_VALUE_S 8 +#define SPI_SLV_RDBUF_CMD_VALUE 0x000000FF +#define SPI_SLV_RDBUF_CMD_VALUE_S 0 + +#define SPI_W0(i) (REG_SPI_BASE(i) +0x40) +#define SPI_W1(i) (REG_SPI_BASE(i) +0x44) +#define SPI_W2(i) (REG_SPI_BASE(i) +0x48) +#define SPI_W3(i) (REG_SPI_BASE(i) +0x4C) +#define SPI_W4(i) (REG_SPI_BASE(i) +0x50) +#define SPI_W5(i) (REG_SPI_BASE(i) +0x54) +#define SPI_W6(i) (REG_SPI_BASE(i) +0x58) +#define SPI_W7(i) (REG_SPI_BASE(i) +0x5C) +#define SPI_W8(i) (REG_SPI_BASE(i) +0x60) +#define SPI_W9(i) (REG_SPI_BASE(i) +0x64) +#define SPI_W10(i) (REG_SPI_BASE(i) +0x68) +#define SPI_W11(i) (REG_SPI_BASE(i) +0x6C) +#define SPI_W12(i) (REG_SPI_BASE(i) +0x70) +#define SPI_W13(i) (REG_SPI_BASE(i) +0x74) +#define SPI_W14(i) (REG_SPI_BASE(i) +0x78) +#define SPI_W15(i) (REG_SPI_BASE(i) +0x7C) + +#define SPI_EXT3(i) (REG_SPI_BASE(i) + 0xFC) +#define SPI_INT_HOLD_ENA 0x00000003 +#define SPI_INT_HOLD_ENA_S 0 +#endif // SPI_REGISTER_H_INCLUDED diff --git a/include/uart_hw.h b/include/uart_hw.h new file mode 100644 index 0000000..02614c9 --- /dev/null +++ b/include/uart_hw.h @@ -0,0 +1,225 @@ +//Generated at 2012-07-03 18:44:06 +/* + * Copyright (c) 2010 - 2011 Espressif System + * + */ + +#ifndef UART_REGISTER_H_INCLUDED +#define UART_REGISTER_H_INCLUDED + +#define REG_UART_BASE( i ) (0x60000000+(i)*0xf00) +//version value:32'h062000 + +#define UART_FIFO( i ) (REG_UART_BASE( i ) + 0x0) +#define UART_RXFIFO_RD_BYTE 0x000000FF +#define UART_RXFIFO_RD_BYTE_S 0 + +#define UART_INT_RAW( i ) (REG_UART_BASE( i ) + 0x4) +#define UART_RXFIFO_TOUT_INT_RAW (BIT(8)) +#define UART_BRK_DET_INT_RAW (BIT(7)) +#define UART_CTS_CHG_INT_RAW (BIT(6)) +#define UART_DSR_CHG_INT_RAW (BIT(5)) +#define UART_RXFIFO_OVF_INT_RAW (BIT(4)) +#define UART_FRM_ERR_INT_RAW (BIT(3)) +#define UART_PARITY_ERR_INT_RAW (BIT(2)) +#define UART_TXFIFO_EMPTY_INT_RAW (BIT(1)) +#define UART_RXFIFO_FULL_INT_RAW (BIT(0)) + +#define UART_INT_ST( i ) (REG_UART_BASE( i ) + 0x8) +#define UART_RXFIFO_TOUT_INT_ST (BIT(8)) +#define UART_BRK_DET_INT_ST (BIT(7)) +#define UART_CTS_CHG_INT_ST (BIT(6)) +#define UART_DSR_CHG_INT_ST (BIT(5)) +#define UART_RXFIFO_OVF_INT_ST (BIT(4)) +#define UART_FRM_ERR_INT_ST (BIT(3)) +#define UART_PARITY_ERR_INT_ST (BIT(2)) +#define UART_TXFIFO_EMPTY_INT_ST (BIT(1)) +#define UART_RXFIFO_FULL_INT_ST (BIT(0)) + +#define UART_INT_ENA( i ) (REG_UART_BASE( i ) + 0xC) +#define UART_RXFIFO_TOUT_INT_ENA (BIT(8)) +#define UART_BRK_DET_INT_ENA (BIT(7)) +#define UART_CTS_CHG_INT_ENA (BIT(6)) +#define UART_DSR_CHG_INT_ENA (BIT(5)) +#define UART_RXFIFO_OVF_INT_ENA (BIT(4)) +#define UART_FRM_ERR_INT_ENA (BIT(3)) +#define UART_PARITY_ERR_INT_ENA (BIT(2)) +#define UART_TXFIFO_EMPTY_INT_ENA (BIT(1)) +#define UART_RXFIFO_FULL_INT_ENA (BIT(0)) + +#define UART_INT_CLR( i ) (REG_UART_BASE( i ) + 0x10) +#define UART_RXFIFO_TOUT_INT_CLR (BIT(8)) +#define UART_BRK_DET_INT_CLR (BIT(7)) +#define UART_CTS_CHG_INT_CLR (BIT(6)) +#define UART_DSR_CHG_INT_CLR (BIT(5)) +#define UART_RXFIFO_OVF_INT_CLR (BIT(4)) +#define UART_FRM_ERR_INT_CLR (BIT(3)) +#define UART_PARITY_ERR_INT_CLR (BIT(2)) +#define UART_TXFIFO_EMPTY_INT_CLR (BIT(1)) +#define UART_RXFIFO_FULL_INT_CLR (BIT(0)) + +#define UART_CLKDIV( i ) (REG_UART_BASE( i ) + 0x14) +#define UART_CLKDIV_CNT 0x000FFFFF +#define UART_CLKDIV_S 0 + +#define UART_AUTOBAUD( i ) (REG_UART_BASE( i ) + 0x18) +#define UART_GLITCH_FILT 0x000000FF +#define UART_GLITCH_FILT_S 8 +#define UART_AUTOBAUD_EN (BIT(0)) + +#define UART_STATUS( i ) (REG_UART_BASE( i ) + 0x1C) +#define UART_TXD (BIT(31)) +#define UART_RTSN (BIT(30)) +#define UART_DTRN (BIT(29)) +#define UART_TXFIFO_CNT 0x000000FF +#define UART_TXFIFO_CNT_S 16 +#define UART_RXD (BIT(15)) +#define UART_CTSN (BIT(14)) +#define UART_DSRN (BIT(13)) +#define UART_RXFIFO_CNT 0x000000FF +#define UART_RXFIFO_CNT_S 0 + +#define UART_CONF0( i ) (REG_UART_BASE( i ) + 0x20) +#define UART_TXFIFO_RST (BIT(18)) +#define UART_RXFIFO_RST (BIT(17)) +#define UART_IRDA_EN (BIT(16)) +#define UART_TX_FLOW_EN (BIT(15)) +#define UART_LOOPBACK (BIT(14)) +#define UART_IRDA_RX_INV (BIT(13)) +#define UART_IRDA_TX_INV (BIT(12)) +#define UART_IRDA_WCTL (BIT(11)) +#define UART_IRDA_TX_EN (BIT(10)) +#define UART_IRDA_DPLX (BIT(9)) +#define UART_TXD_BRK (BIT(8)) +#define UART_SW_DTR (BIT(7)) +#define UART_SW_RTS (BIT(6)) +#define UART_STOP_BIT_NUM 0x00000003 +#define UART_STOP_BIT_NUM_S 4 +#define UART_BIT_NUM 0x00000003 +#define UART_BIT_NUM_S 2 +#define UART_PARITY_EN (BIT(1)) +#define UART_PARITY (BIT(0)) + +#define UART_CONF1( i ) (REG_UART_BASE( i ) + 0x24) +#define UART_RX_TOUT_EN (BIT(31)) +#define UART_RX_TOUT_THRHD 0x0000007F +#define UART_RX_TOUT_THRHD_S 24 +#define UART_RX_FLOW_EN (BIT(23)) +#define UART_RX_FLOW_THRHD 0x0000007F +#define UART_RX_FLOW_THRHD_S 16 +#define UART_TXFIFO_EMPTY_THRHD 0x0000007F +#define UART_TXFIFO_EMPTY_THRHD_S 8 +#define UART_RXFIFO_FULL_THRHD 0x0000007F +#define UART_RXFIFO_FULL_THRHD_S 0 + +#define UART_LOWPULSE( i ) (REG_UART_BASE( i ) + 0x28) +#define UART_LOWPULSE_MIN_CNT 0x000FFFFF +#define UART_LOWPULSE_MIN_CNT_S 0 + +#define UART_HIGHPULSE( i ) (REG_UART_BASE( i ) + 0x2C) +#define UART_HIGHPULSE_MIN_CNT 0x000FFFFF +#define UART_HIGHPULSE_MIN_CNT_S 0 + +#define UART_PULSE_NUM( i ) (REG_UART_BASE( i ) + 0x30) +#define UART_PULSE_NUM_CNT 0x0003FF +#define UART_PULSE_NUM_CNT_S 0 + +#define UART_DATE( i ) (REG_UART_BASE( i ) + 0x78) +#define UART_ID( i ) (REG_UART_BASE( i ) + 0x7C) + +#define RX_BUFF_SIZE 256 +#define TX_BUFF_SIZE 100 +#define UART0 0 +#define UART1 1 + +//calc bit 0..5 for UART_CONF0 register +#define CALC_UARTMODE(data_bits,parity,stop_bits) \ + (((parity == NONE_BITS) ? 0x0 : (UART_PARITY_EN | (parity & UART_PARITY))) | \ + ((stop_bits & UART_STOP_BIT_NUM) << UART_STOP_BIT_NUM_S) | \ + ((data_bits & UART_BIT_NUM) << UART_BIT_NUM_S)) + +typedef enum { + FIVE_BITS = 0x0, + SIX_BITS = 0x1, + SEVEN_BITS = 0x2, + EIGHT_BITS = 0x3 +} UartBitsNum4Char; + +typedef enum { + ONE_STOP_BIT = 0, + ONE_HALF_STOP_BIT = BIT2, + TWO_STOP_BIT = BIT2 +} UartStopBitsNum; + +typedef enum { + NONE_BITS = 0, + ODD_BITS = 0, + EVEN_BITS = BIT4 +} UartParityMode; + +typedef enum { + STICK_PARITY_DIS = 0, + STICK_PARITY_EN = BIT3 | BIT5 +} UartExistParity; + +typedef enum { + BIT_RATE_9600 = 9600, + BIT_RATE_19200 = 19200, + BIT_RATE_38400 = 38400, + BIT_RATE_57600 = 57600, + BIT_RATE_74880 = 74880, + BIT_RATE_115200 = 115200, + BIT_RATE_230400 = 230400, + BIT_RATE_460800 = 460800, + BIT_RATE_921600 = 921600 +} UartBautRate; + +typedef enum { + NONE_CTRL, + HARDWARE_CTRL, + XON_XOFF_CTRL +} UartFlowCtrl; + +typedef enum { + EMPTY, + UNDER_WRITE, + WRITE_OVER +} RcvMsgBuffState; + +typedef struct { + uint32 RcvBuffSize; + uint8 *pRcvMsgBuff; + uint8 *pWritePos; + uint8 *pReadPos; + uint8 TrigLvl; //JLU: may need to pad + RcvMsgBuffState BuffState; +} RcvMsgBuff; + +typedef struct { + uint32 TrxBuffSize; + uint8 *pTrxBuff; +} TrxMsgBuff; + +typedef enum { + BAUD_RATE_DET, + WAIT_SYNC_FRM, + SRCH_MSG_HEAD, + RCV_MSG_BODY, + RCV_ESC_CHAR, +} RcvMsgState; + +typedef struct { + UartBautRate baut_rate; + UartBitsNum4Char data_bits; + UartExistParity exist_parity; + UartParityMode parity; + UartStopBitsNum stop_bits; + UartFlowCtrl flow_ctrl; + RcvMsgBuff rcv_buff; + TrxMsgBuff trx_buff; + RcvMsgState rcv_state; + int received; + int buff_uart_no; //indicate which uart use tx/rx buffer +} UartDevice; + +#endif // UART_REGISTER_H_INCLUDED diff --git a/include/user_config.h b/include/user_config.h new file mode 100644 index 0000000..e1b5d42 --- /dev/null +++ b/include/user_config.h @@ -0,0 +1,44 @@ +#ifndef _USER_CONFIG_H_ +#define _USER_CONFIG_H_ +#include +#ifdef __WIN32__ +#include <_mingw.h> +#endif + +#undef SHOW_HEAP_USE +#define DEBUGIP +#define SDK_DBG + +#define CMD_DBG +#undef ESPFS_DBG +#undef CGI_DBG +#define CGIFLASH_DBG +#define CGIMQTT_DBG +#define CGIPINS_DBG +#define CGIWIFI_DBG +#define CONFIG_DBG +#define LOG_DBG +#define STATUS_DBG +#undef HTTPD_DBG +//#define MQTT_DBG +#undef MQTT_DBG +#define MQTTCMD_DBG +#undef PKTBUF_DBG +#define REST_DBG +#define RESTCMD_DBG +#define SERBR_DBG +#define SERLED_DBG +#undef SLIP_DBG +#define UART_DBG +#define MDNS_DBG +#define OPTIBOOT_DBG +#undef SYSLOG_DBG +#undef CGISERVICES_DBG + +// If defined, the default hostname for DHCP will include the chip ID to make it unique +#undef CHIP_IN_HOSTNAME + +extern char* esp_link_version; +extern uint8_t UTILS_StrToIP(const char* str, void *ip); + +#endif diff --git a/include/ws2812.h b/include/ws2812.h new file mode 100644 index 0000000..16e5456 --- /dev/null +++ b/include/ws2812.h @@ -0,0 +1,24 @@ +#ifndef _WS2812_H +#define _WS2812_H + +#include "c_types.h" +#include "user_interface.h" +#include "ets_sys.h" +#include "gpio.h" +#include "osapi.h" + +#define LEDS 1 + +#define WSGPIO 5 +#define IO_MUX PERIPHS_IO_MUX_GPIO5_U +#define IO_FUNC FUNC_GPIO5 + +char outbuffer[LEDS*3]; + +os_timer_t led_timer; + +void WS2812OutBuffer( uint8_t * buffer, uint16_t length ); +void led_reset(void *arg); +void set_led(int r, int g, int b, long delay); +#endif + diff --git a/rest/rest.c b/rest/rest.c new file mode 100644 index 0000000..81e296a --- /dev/null +++ b/rest/rest.c @@ -0,0 +1,431 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// +// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh + +#include "esp8266.h" +#include "c_types.h" +#include "ip_addr.h" +#include "rest.h" +#include "cmd.h" + +#ifdef REST_DBG +#define DBG_REST(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG_REST(format, ...) do { } while(0) +#endif + +typedef enum { + HEADER_GENERIC = 0, + HEADER_CONTENT_TYPE, + HEADER_USER_AGENT +} HEADER_TYPE; + +typedef struct { + char *host; + uint32_t port; + uint32_t security; + ip_addr_t ip; + struct espconn *pCon; + char *header; + char *data; + uint16_t data_len; + uint16_t data_sent; + char *content_type; + char *user_agent; + uint32_t resp_cb; +} RestClient; + + +// Connection pool for REST clients. Attached MCU's just call REST_setup and this allocates +// a connection, They never call any 'free' and given that the attached MCU could restart at +// any time, we cannot really rely on the attached MCU to call 'free' ever, so better do without. +// Instead, we allocate a fixed pool of connections an round-robin. What this means is that the +// attached MCU should really use at most as many REST connections as there are slots in the pool. +#define MAX_REST 4 +static RestClient restClient[MAX_REST]; +static uint8_t restNum = 0xff; // index into restClient for next slot to allocate +#define REST_CB 0xbeef0000 // fudge added to callback for arduino so we can detect problems + +// Receive HTTP response - this hacky function assumes that the full response is received in +// one go. Sigh... +static void ICACHE_FLASH_ATTR +tcpclient_recv(void *arg, char *pdata, unsigned short len) { + struct espconn *pCon = (struct espconn*)arg; + RestClient *client = (RestClient *)pCon->reverse; + + // parse status line + int pi = 0; + int16_t code = -1; + char statusCode[4] = "\0\0\0\0"; + int statusLen = 0; + bool inStatus = false; + while (pi < len) { + if (pdata[pi] == '\n') { + // end of status line + if (code == -1) code = 502; // BAD GATEWAY + break; + } else if (pdata[pi] == ' ') { + if (inStatus) code = atoi(statusCode); + inStatus = !inStatus; + } else if (inStatus) { + if (statusLen < 3) statusCode[statusLen] = pdata[pi]; + statusLen++; + } + pi++; + } + + // parse header, all this does is look for the end of the header + bool currentLineIsBlank = false; + while (pi < len) { + if (pdata[pi] == '\n') { + if (currentLineIsBlank) { + // body is starting + pi++; + break; + } + currentLineIsBlank = true; + } else if (pdata[pi] != '\r') { + currentLineIsBlank = false; + } + pi++; + } + //if (pi < len && pdata[pi] == '\r') pi++; // hacky! + + // collect body and send it + int body_len = len-pi; + DBG_REST("REST: status=%d, body=%d\n", code, body_len); + if (pi == len) { + cmdResponseStart(CMD_RESP_CB, client->resp_cb, 1); + cmdResponseBody(&code, sizeof(code)); + cmdResponseEnd(); + } else { + cmdResponseStart(CMD_RESP_CB, client->resp_cb, 2); + cmdResponseBody(&code, sizeof(code)); + cmdResponseBody(pdata+pi, body_len>100?100:body_len); + cmdResponseEnd(); +#if 0 + os_printf("REST: body="); + for (int j=pi; jsecurity) + // espconn_secure_disconnect(client->pCon); + //else + espconn_disconnect(client->pCon); +} + +static void ICACHE_FLASH_ATTR +tcpclient_sent_cb(void *arg) { + struct espconn *pCon = (struct espconn *)arg; + RestClient* client = (RestClient *)pCon->reverse; + DBG_REST("REST: Sent\n"); + if (client->data_sent != client->data_len) { + // we only sent part of the buffer, send the rest + espconn_sent(client->pCon, (uint8_t*)(client->data+client->data_sent), + client->data_len-client->data_sent); + client->data_sent = client->data_len; + } else { + // we're done sending, free the memory + if (client->data) os_free(client->data); + client->data = 0; + } +} + +static void ICACHE_FLASH_ATTR +tcpclient_discon_cb(void *arg) { + struct espconn *pespconn = (struct espconn *)arg; + RestClient* client = (RestClient *)pespconn->reverse; + // free the data buffer, if we have one + if (client->data) os_free(client->data); + client->data = 0; +} + +static void ICACHE_FLASH_ATTR +tcpclient_recon_cb(void *arg, sint8 errType) { + struct espconn *pCon = (struct espconn *)arg; + RestClient* client = (RestClient *)pCon->reverse; + os_printf("REST #%d: conn reset, err=%d\n", client-restClient, errType); + // free the data buffer, if we have one + if (client->data) os_free(client->data); + client->data = 0; +} + +static void ICACHE_FLASH_ATTR +tcpclient_connect_cb(void *arg) { + struct espconn *pCon = (struct espconn *)arg; + RestClient* client = (RestClient *)pCon->reverse; + DBG_REST("REST #%d: connected\n", client-restClient); + espconn_regist_disconcb(client->pCon, tcpclient_discon_cb); + espconn_regist_recvcb(client->pCon, tcpclient_recv); + espconn_regist_sentcb(client->pCon, tcpclient_sent_cb); + + client->data_sent = client->data_len <= 1400 ? client->data_len : 1400; + DBG_REST("REST #%d: sending %d\n", client-restClient, client->data_sent); + //if(client->security){ + // espconn_secure_sent(client->pCon, client->data, client->data_sent); + //} + //else{ + espconn_sent(client->pCon, (uint8_t*)client->data, client->data_sent); + //} +} + +static void ICACHE_FLASH_ATTR +rest_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) { + struct espconn *pConn = (struct espconn *)arg; + RestClient* client = (RestClient *)pConn->reverse; + + if(ipaddr == NULL) { + os_printf("REST DNS: Got no ip, try to reconnect\n"); + return; + } + DBG_REST("REST DNS: found ip %d.%d.%d.%d\n", + *((uint8 *) &ipaddr->addr), + *((uint8 *) &ipaddr->addr + 1), + *((uint8 *) &ipaddr->addr + 2), + *((uint8 *) &ipaddr->addr + 3)); + if(client->ip.addr == 0 && ipaddr->addr != 0) { + os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); +#ifdef CLIENT_SSL_ENABLE + if(client->security) { + espconn_secure_connect(client->pCon); + } else +#endif + espconn_connect(client->pCon); + DBG_REST("REST: connecting...\n"); + } +} + +void ICACHE_FLASH_ATTR +REST_Setup(CmdPacket *cmd) { + CmdRequest req; + uint32_t port, security; + int32_t err = -1; // error code in case of failure + + // start parsing the command + cmdRequest(&req, cmd); + if(cmdGetArgc(&req) != 3) goto fail; + err--; + + // get the hostname + uint16_t len = cmdArgLen(&req); + if (len > 128) goto fail; // safety check + err--; + uint8_t *rest_host = (uint8_t*)os_zalloc(len + 1); + if (cmdPopArg(&req, rest_host, len)) goto fail; + err--; + rest_host[len] = 0; + + // get the port + if (cmdPopArg(&req, (uint8_t*)&port, 2)) { + os_free(rest_host); + goto fail; + } + err--; + + // get the security mode + if (cmdPopArg(&req, (uint8_t*)&security, 1)) { + os_free(rest_host); + goto fail; + } + err--; + + // clear connection structures the first time + if (restNum == 0xff) { + os_memset(restClient, 0, MAX_REST * sizeof(RestClient)); + restNum = 0; + } + + // allocate a connection structure + RestClient *client = restClient + restNum; + uint8_t clientNum = restNum; + restNum = (restNum+1)%MAX_REST; + + // free any data structure that may be left from a previous connection + if (client->header) os_free(client->header); + if (client->content_type) os_free(client->content_type); + if (client->user_agent) os_free(client->user_agent); + if (client->data) os_free(client->data); + if (client->pCon) { + if (client->pCon->proto.tcp) os_free(client->pCon->proto.tcp); + os_free(client->pCon); + } + os_memset(client, 0, sizeof(RestClient)); + DBG_REST("REST: setup #%d host=%s port=%ld security=%ld\n", clientNum, rest_host, port, security); + + client->resp_cb = cmd->value; + + client->host = (char *)rest_host; + client->port = port; + client->security = security; + + client->header = (char*)os_zalloc(4); + client->header[0] = 0; + + client->content_type = (char*)os_zalloc(22); + os_sprintf((char *)client->content_type, "x-www-form-urlencoded"); + + client->user_agent = (char*)os_zalloc(9); + os_sprintf((char *)client->user_agent, "esp-link"); + + client->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + client->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); + + client->pCon->type = ESPCONN_TCP; + client->pCon->state = ESPCONN_NONE; + client->pCon->proto.tcp->local_port = espconn_port(); + client->pCon->proto.tcp->remote_port = client->port; + + client->pCon->reverse = client; + + cmdResponseStart(CMD_RESP_V, clientNum, 0); + cmdResponseEnd(); + return; + +fail: + cmdResponseStart(CMD_RESP_V, err, 0); + cmdResponseEnd(); + return; +} + +void ICACHE_FLASH_ATTR +REST_SetHeader(CmdPacket *cmd) { + CmdRequest req; + cmdRequest(&req, cmd); + + if(cmdGetArgc(&req) != 2) return; + + // Get client + uint32_t clientNum = cmd->value; + RestClient *client = restClient + (clientNum % MAX_REST); + + // Get header selector + uint32_t header_index; + if (cmdPopArg(&req, (uint8_t*)&header_index, 4)) return; + + // Get header value + uint16_t len = cmdArgLen(&req); + if (len > 256) return; //safety check + switch(header_index) { + case HEADER_GENERIC: + if(client->header) os_free(client->header); + client->header = (char*)os_zalloc(len + 3); + cmdPopArg(&req, (uint8_t*)client->header, len); + client->header[len] = '\r'; + client->header[len+1] = '\n'; + client->header[len+2] = 0; + DBG_REST("REST: Set header: %s\r\n", client->header); + break; + case HEADER_CONTENT_TYPE: + if(client->content_type) os_free(client->content_type); + client->content_type = (char*)os_zalloc(len + 3); + cmdPopArg(&req, (uint8_t*)client->content_type, len); + client->content_type[len] = '\r'; + client->content_type[len+1] = '\n'; + client->content_type[len+2] = 0; + DBG_REST("REST: Set content_type: %s\r\n", client->content_type); + break; + case HEADER_USER_AGENT: + if(client->user_agent) os_free(client->user_agent); + client->user_agent = (char*)os_zalloc(len + 3); + cmdPopArg(&req, (uint8_t*)client->user_agent, len); + client->user_agent[len] = '\r'; + client->user_agent[len+1] = '\n'; + client->user_agent[len+2] = 0; + DBG_REST("REST: Set user_agent: %s\r\n", client->user_agent); + break; + } +} + +void ICACHE_FLASH_ATTR +REST_Request(CmdPacket *cmd) { + CmdRequest req; + cmdRequest(&req, cmd); + DBG_REST("REST: request"); + if (cmd->argc != 2 && cmd->argc != 3) return; + + // Get client + uint32_t clientNum = cmd->value; + RestClient *client = restClient + (clientNum % MAX_REST); + DBG_REST(" #%ld", clientNum); + + // Get HTTP method + uint16_t len = cmdArgLen(&req); + if (len > 15) goto fail; + char method[16]; + cmdPopArg(&req, method, len); + method[len] = 0; + DBG_REST(" method=%s", method); + + // Get HTTP path + len = cmdArgLen(&req); + if (len > 1023) goto fail; + char path[1024]; + cmdPopArg(&req, path, len); + path[len] = 0; + DBG_REST(" path=%s", path); + + // Get HTTP body + uint32_t realLen = 0; + if (cmdGetArgc(&req) == 2) { + realLen = 0; + } else { + realLen = cmdArgLen(&req); + if (realLen > 2048) goto fail; + } + DBG_REST(" bodyLen=%ld", realLen); + + // we need to allocate memory for the header plus the body. First we count the length of the + // header (including some extra counted "%s" and then we add the body length. We allocate the + // whole shebang and copy everything into it. + // BTW, use http/1.0 to avoid responses with transfer-encoding: chunked + char *headerFmt = "%s %s HTTP/1.0\r\n" + "Host: %s\r\n" + "%s" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "Content-Type: %s\r\n" + "User-Agent: %s\r\n\r\n"; + uint16_t headerLen = strlen(headerFmt) + strlen(method) + strlen(path) + strlen(client->host) + + strlen(client->header) + strlen(client->content_type) + strlen(client->user_agent); + DBG_REST(" hdrLen=%d", headerLen); + if (client->data) os_free(client->data); + client->data = (char*)os_zalloc(headerLen + realLen); + if (client->data == NULL) goto fail; + DBG_REST(" totLen=%ld data=%p", headerLen + realLen, client->data); + client->data_len = os_sprintf((char*)client->data, headerFmt, method, path, client->host, + client->header, realLen, client->content_type, client->user_agent); + DBG_REST(" hdrLen=%d", client->data_len); + + if (realLen > 0) { + cmdPopArg(&req, client->data + client->data_len, realLen); + client->data_len += realLen; + } + DBG_REST("\n"); + + //DBG_REST("REST request: %s", (char*)client->data); + + //DBG_REST("REST: pCon state=%d\n", client->pCon->state); + client->pCon->state = ESPCONN_NONE; + espconn_regist_connectcb(client->pCon, tcpclient_connect_cb); + espconn_regist_reconcb(client->pCon, tcpclient_recon_cb); + + if(UTILS_StrToIP((char *)client->host, &client->pCon->proto.tcp->remote_ip)) { + DBG_REST("REST: Connect to ip %s:%ld\n",client->host, client->port); + //if(client->security){ + // espconn_secure_connect(client->pCon); + //} + //else { + espconn_connect(client->pCon); + //} + } else { + DBG_REST("REST: Connect to host %s:%ld\n", client->host, client->port); + espconn_gethostbyname(client->pCon, (char *)client->host, &client->ip, rest_dns_found); + } + + return; + +fail: + DBG_REST("\n"); +} diff --git a/rest/rest.h b/rest/rest.h new file mode 100644 index 0000000..281a9f4 --- /dev/null +++ b/rest/rest.h @@ -0,0 +1,17 @@ +/* + * api.h + * + * Created on: Mar 4, 2015 + * Author: Minh + */ + +#ifndef MODULES_API_H_ +#define MODULES_API_H_ + +#include "cmd.h" + +void REST_Setup(CmdPacket *cmd); +void REST_Request(CmdPacket *cmd); +void REST_SetHeader(CmdPacket *cmd); + +#endif /* MODULES_INCLUDE_API_H_ */ diff --git a/syslog/syslog.c b/syslog/syslog.c new file mode 100644 index 0000000..20a51cb --- /dev/null +++ b/syslog/syslog.c @@ -0,0 +1,525 @@ +/* + * syslog.c + * + * + * Copyright 2015 Susi's Strolch + * + * For license information see projects "License.txt" + * + */ + +#include +#include "config.h" +#include "syslog.h" +#include "time.h" +#include "task.h" + +extern void * mem_trim(void *m, size_t s); // not well documented... + +#ifdef SYSLOG_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + +#define WIFI_CHK_INTERVAL 1000 // ms to check Wifi statis + +static struct espconn *syslog_espconn = NULL; +static uint32_t syslog_msgid = 1; +static uint8_t syslog_task = 0; + +static syslog_host_t syslogHost; +static syslog_entry_t *syslogQueue = NULL; + +static enum syslog_state syslogState = SYSLOG_NONE; + +static bool syslog_timer_armed = false; + +static void ICACHE_FLASH_ATTR syslog_add_entry(syslog_entry_t *entry); +static void ICACHE_FLASH_ATTR syslog_chk_status(void); +static void ICACHE_FLASH_ATTR syslog_udp_sent_cb(void *arg); +static syslog_entry_t ICACHE_FLASH_ATTR *syslog_compose(uint8_t facility, uint8_t severity, const char *tag, const char *fmt, ...); + +#ifdef SYSLOG_UDP_RECV +static void ICACHE_FLASH_ATTR syslog_udp_recv_cb(void *arg, char *pusrdata, unsigned short length); +#endif + +#define syslog_send_udp() post_usr_task(syslog_task,0) + +#ifdef SYSLOG_DBG +static char ICACHE_FLASH_ATTR *syslog_get_status(void) { + switch (syslogState) + { + case SYSLOG_NONE: + return "SYSLOG_NONE"; + case SYSLOG_WAIT: + return "SYSLOG_WAIT"; + case SYSLOG_INIT: + return "SYSLOG_INIT"; + case SYSLOG_INITDONE: + return "SYSLOG_INITDONE"; + case SYSLOG_DNSWAIT: + return "SYSLOG_DNSWAIT"; + case SYSLOG_READY: + return "SYSLOG_READY"; + case SYSLOG_SENDING: + return "SYSLOG_SENDING"; + case SYSLOG_SEND: + return "SYSLOG_SEND"; + case SYSLOG_SENT: + return "SYSLOG_SENT"; + case SYSLOG_HALTED: + return "SYSLOG_HALTED"; + case SYSLOG_ERROR: + return "SYSLOG_ERROR"; + default: + break; + } + return "UNKNOWN "; +} +#endif + +static void ICACHE_FLASH_ATTR syslog_set_status(enum syslog_state state) { + syslogState = state; + DBG("[%dµs] %s: %s (%d)\n", WDEV_NOW(), __FUNCTION__, syslog_get_status(), state); +} + +static void ICACHE_FLASH_ATTR syslog_timer_arm(int delay) { + static os_timer_t wifi_chk_timer = {}; + syslog_timer_armed = true; + os_timer_disarm(&wifi_chk_timer); + os_timer_setfn(&wifi_chk_timer, (os_timer_func_t *)syslog_chk_status, NULL); + os_timer_arm(&wifi_chk_timer, delay, 0); +} + +/****************************************************************************** + * FunctionName : syslog_chk_status + * Description : check whether get ip addr or not + * Parameters : none + * Returns : none +*******************************************************************************/ +static void ICACHE_FLASH_ATTR syslog_chk_status(void) +{ + struct ip_info ipconfig; + + DBG("[%uµs] %s: id=%lu ", WDEV_NOW(), __FUNCTION__, syslogQueue ? syslogQueue->msgid : 0); + + //disarm timer first + syslog_timer_armed = false; + + //try to get ip info of ESP8266 station + wifi_get_ip_info(STATION_IF, &ipconfig); + int wifi_status = wifi_station_get_connect_status(); + if (wifi_status == STATION_GOT_IP && ipconfig.ip.addr != 0) + { + // it seems we have to add an additional delay after the Wifi is up and running. + // so we simply add an intermediate state with 25ms delay + switch (syslogState) + { + case SYSLOG_WAIT: + DBG("%s: Wifi connected\n", syslog_get_status()); + syslog_set_status(SYSLOG_INIT); + syslog_timer_arm(100); + break; + + case SYSLOG_INIT: + DBG("%s: init syslog\n", syslog_get_status()); + syslog_set_status(SYSLOG_INITDONE); + syslog_init(flashConfig.syslog_host); + syslog_timer_arm(10); + break; + + case SYSLOG_DNSWAIT: + DBG("%s: wait for DNS resolver\n", syslog_get_status()); + syslog_timer_arm(10); + break; + + case SYSLOG_READY: + DBG("%s: enforce sending\n", syslog_get_status()); + syslog_send_udp(); + break; + + case SYSLOG_SENDING: + DBG("%s: delay\n", syslog_get_status()); + syslog_set_status(SYSLOG_SEND); + syslog_timer_arm(2); + break; + + case SYSLOG_SEND: + DBG("%s: start sending\n", syslog_get_status()); + syslog_send_udp(); + break; + + default: + DBG("%s: %d\n", syslog_get_status(), syslogState); + break; + } + } else { + if ((wifi_status == STATION_WRONG_PASSWORD || + wifi_status == STATION_NO_AP_FOUND || + wifi_status == STATION_CONNECT_FAIL)) { + syslog_set_status(SYSLOG_ERROR); + os_printf("*** connect failure!!!\n"); + } else { + DBG("re-arming timer...\n"); + syslog_timer_arm(WIFI_CHK_INTERVAL); + } + } +} + +/****************************************************************************** + * FunctionName : syslog_sent_cb + * Description : udp sent successfully + * fetch next syslog package, free old message + * Parameters : arg -- Additional argument to pass to the callback function + * Returns : none + ******************************************************************************/ +static void ICACHE_FLASH_ATTR syslog_udp_sent_cb(void *arg) +{ + struct espconn *pespconn = arg; + (void) pespconn; + + DBG("[%uµs] %s: id=%lu\n", WDEV_NOW(), __FUNCTION__, syslogQueue ? syslogQueue->msgid : 0); + + // datagram is delivered - free and advance queue + syslog_entry_t *pse = syslogQueue; + syslogQueue = syslogQueue -> next; + os_free(pse); + + if (syslogQueue == NULL) + syslog_set_status(SYSLOG_READY); + else { + // UDP seems timecritical - we must ensure a minimum delay after each package... + syslog_set_status(SYSLOG_SENDING); + if (! syslog_timer_armed) + syslog_chk_status(); + } +} + +static void ICACHE_FLASH_ATTR +syslog_udp_send_event(os_event_t *events) { +// os_printf("syslog_udp_send_event: %d %lu, %lu\n", syslogState, syslogQueue->msgid, syslogQueue->tick); + DBG("[%uµs] %s: id=%lu\n", WDEV_NOW(), __FUNCTION__, syslogQueue ? syslogQueue->msgid : 0); + + if (syslogQueue == NULL) + syslog_set_status(SYSLOG_READY); + else { + int res = 0; + syslog_espconn->proto.udp->remote_port = syslogHost.port; // ESP8266 udp remote port + os_memcpy(syslog_espconn->proto.udp->remote_ip, &syslogHost.addr.addr, 4); // ESP8266 udp remote IP + res = espconn_send(syslog_espconn, (uint8_t *)syslogQueue->datagram, syslogQueue->datagram_len); + if (res != 0) { + os_printf("syslog_udp_send: error %d\n", res); + } + } +} + + /***************************************************************************** + * FunctionName : syslog_recv_cb + * Description : Processing the received udp packet + * Parameters : arg -- Additional argument to pass to the callback function + * pusrdata -- The received data (or NULL when the connection has been closed!) + * length -- The length of received data + * Returns : none + ******************************************************************************/ +#ifdef SYSLOG_UDP_RECV +static void ICACHE_FLASH_ATTR syslog_udp_recv_cb(void *arg, char *pusrdata, unsigned short length) +{ + DBG("syslog_udp_recv_cb: %p, %p, %d\n", arg, pusrdata, length); +} +#endif + +/****************************************************************************** + * + ******************************************************************************/ +static void ICACHE_FLASH_ATTR syslog_gethostbyname_cb(const char *name, ip_addr_t *ipaddr, void *arg) +{ + struct espconn *pespconn = (struct espconn *)arg; + (void) pespconn; + + DBG("[%uµs] %s\n", WDEV_NOW(), __FUNCTION__); + if (ipaddr != NULL) { + syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "SYSLOG", + "resolved hostname: %s: " IPSTR, name, IP2STR(ipaddr)); + syslogHost.addr.addr = ipaddr->addr; + syslog_set_status(SYSLOG_READY); + } else { + syslog_set_status(SYSLOG_ERROR); + DBG("syslog_gethostbyname_cb: status=%s\n", syslog_get_status()); + } + DBG("[%uµs] ex syslog_gethostbyname_cb()\n", WDEV_NOW()); +} + + /****************************************************************************** + * FunctionName : initSyslog + * Description : Initialize the syslog library + * Parameters : syslog_host -- the syslog host (host:port) + * host: IP-Addr | hostname + * Returns : none + *******************************************************************************/ +void ICACHE_FLASH_ATTR syslog_init(char *syslog_host) +{ + + DBG("[%uµs] %s\n", WDEV_NOW(), __FUNCTION__); + if (!*syslog_host) { + syslog_set_status(SYSLOG_HALTED); + return; + } + + if (syslog_host == NULL) { + // disable and unregister syslog handler + syslog_set_status(SYSLOG_HALTED); + if (syslog_espconn != NULL) { + if (syslog_espconn->proto.udp) { + // there's no counterpart to espconn_create... + os_free(syslog_espconn->proto.udp); + } + os_free(syslog_espconn); + } + syslog_espconn = NULL; + + // clean up syslog queue + syslog_entry_t *pse = syslogQueue; + while (pse != NULL) { + syslog_entry_t *next = pse->next; + os_free(pse); + pse = next; + } + syslogQueue = NULL; + return; + } + + char host[32], *port = &host[0]; + os_strncpy(host, syslog_host, 32); + while (*port && *port != ':') // find port delimiter + port++; + + if (*port) { + *port++ = '\0'; + syslogHost.port = atoi(port); + } + + if (syslogHost.port == 0) + syslogHost.port = 514; + + // allocate structures, init syslog_handler + if (syslog_espconn == NULL) + syslog_espconn = (espconn *)os_zalloc(sizeof(espconn)); + + if (syslog_espconn->proto.udp == NULL) + syslog_espconn->proto.udp = (esp_udp *)os_zalloc(sizeof(esp_udp)); + + syslog_espconn->type = ESPCONN_UDP; + syslog_espconn->proto.udp->local_port = espconn_port(); // set a available port +#ifdef SYSLOG_UDP_RECV + espconn_regist_recvcb(syslog_espconn, syslog_udp_recv_cb); // register a udp packet receiving callback +#endif + espconn_regist_sentcb(syslog_espconn, syslog_udp_sent_cb); // register a udp packet sent callback + syslog_task = register_usr_task(syslog_udp_send_event); + syslogHost.min_heap_size = flashConfig.syslog_minheap; + +// the wifi_set_broadcast_if must be handled global in connection handler... +// wifi_set_broadcast_if(STATIONAP_MODE); // send UDP broadcast from both station and soft-AP interface + espconn_create(syslog_espconn); // create udp + + syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "SYSLOG", + "syslogserver: %s:%d %d", host, syslogHost.port, syslog_espconn->proto.udp->local_port); + + if (UTILS_StrToIP((const char *)host, (void*)&syslogHost.addr)) { + syslog_set_status(SYSLOG_READY); + } else { + syslog_set_status(SYSLOG_DNSWAIT); + syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "SYSLOG", + "must resolve hostname \"%s\"", host); + espconn_gethostbyname(syslog_espconn, host, &syslogHost.addr, syslog_gethostbyname_cb); + } +} + +/****************************************************************************** + * FunctionName : syslog_add_entry + * Description : add a syslog_entry_t to the syslogQueue + * Parameters : entry: the syslog_entry_t + * Returns : none + ******************************************************************************/ +static void ICACHE_FLASH_ATTR +syslog_add_entry(syslog_entry_t *entry) +{ + syslog_entry_t *pse = syslogQueue; + + DBG("[%dµs] %s id=%lu\n", WDEV_NOW(), __FUNCTION__, entry->msgid); + // append msg to syslog_queue + if (pse == NULL) + syslogQueue = entry; + else { + while (pse->next != NULL) + pse = pse->next; + pse->next = entry; // append msg to syslog queue + } +// DBG("%p %lu %d\n", entry, entry->msgid, system_get_free_heap_size()); + + // ensure we have sufficient heap for the rest of the system + if (system_get_free_heap_size() < syslogHost.min_heap_size) { + if (syslogState != SYSLOG_HALTED) { + os_printf("syslog_add_entry: Warning: queue filled up, halted\n"); + entry->next = syslog_compose(SYSLOG_FAC_USER, SYSLOG_PRIO_CRIT, "SYSLOG", "queue filled up, halted"); + if (syslogState == SYSLOG_READY) + syslog_send_udp(); + syslog_set_status(SYSLOG_HALTED); + } + } +} + +/****************************************************************************** + * FunctionName : syslog_compose + * Description : compose a syslog_entry_t from va_args + * Parameters : va_args + * Returns : the malloced syslog_entry_t + ******************************************************************************/ +LOCAL syslog_entry_t ICACHE_FLASH_ATTR * +syslog_compose(uint8_t facility, uint8_t severity, const char *tag, const char *fmt, ...) +{ + DBG("[%dµs] %s id=%lu\n", WDEV_NOW(), __FUNCTION__, syslog_msgid); + syslog_entry_t *se = os_zalloc(sizeof (syslog_entry_t) + 1024); // allow up to 1k datagram + char *p = se->datagram; + se->tick = WDEV_NOW(); // 0 ... 4294.967295s + se->msgid = syslog_msgid; + + // The Priority value is calculated by first multiplying the Facility + // number by 8 and then adding the numerical value of the Severity. + p += os_sprintf(p, "<%d> ", facility * 8 + severity); + + // strftime doesn't work as expected - or adds 8k overhead. + // so let's do poor man conversion - format is fixed anyway + if (flashConfig.syslog_showdate == 0) + p += os_sprintf(p, "- "); + else { + time_t now = NULL; + struct tm *tp = NULL; + + // create timestamp: FULL-DATE "T" PARTIAL-TIME "Z": 'YYYY-mm-ddTHH:MM:SSZ ' + // as long as realtime_stamp is 0 we use tick div 10⁶ as date + now = (realtime_stamp == 0) ? (se->tick / 1000000) : realtime_stamp; + tp = gmtime(&now); + + p += os_sprintf(p, "%4d-%02d-%02dT%02d:%02d:%02d", + tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec); + if (realtime_stamp == 0) + p += os_sprintf(p, ".%06luZ ", se->tick % 1000000); + else + p += os_sprintf(p, "%+03d:00 ", flashConfig.timezone_offset); + } + + // add HOSTNAME APP-NAME PROCID MSGID + if (flashConfig.syslog_showtick) + p += os_sprintf(p, "%s %s %lu.%06lu %lu ", flashConfig.hostname, tag, se->tick / 1000000, se->tick % 1000000, syslog_msgid++); + else + p += os_sprintf(p, "%s %s - %lu ", flashConfig.hostname, tag, syslog_msgid++); + + // append syslog message + va_list arglist; + va_start(arglist, fmt); + p += ets_vsprintf(p, fmt, arglist ); + va_end(arglist); + + se->datagram_len = p - se->datagram; + se = mem_trim(se, sizeof(syslog_entry_t) + se->datagram_len + 1); + return se; +} + + /***************************************************************************** + * FunctionName : syslog + * Description : compose and queue a new syslog message + * Parameters : facility + * severity + * tag + * message + * ... + * + * SYSLOG-MSG = HEADER SP STRUCTURED-DATA [SP MSG] + + HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME + SP APP-NAME SP PROCID SP MSGID + PRI = "<" PRIVAL ">" + PRIVAL = 1*3DIGIT ; range 0 .. 191 + VERSION = NONZERO-DIGIT 0*2DIGIT + HOSTNAME = NILVALUE / 1*255PRINTUSASCII + + APP-NAME = NILVALUE / 1*48PRINTUSASCII + PROCID = NILVALUE / 1*128PRINTUSASCII + MSGID = NILVALUE / 1*32PRINTUSASCII + + TIMESTAMP = NILVALUE / FULL-DATE "T" FULL-TIME + FULL-DATE = DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY + DATE-FULLYEAR = 4DIGIT + DATE-MONTH = 2DIGIT ; 01-12 + DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on + ; month/year + FULL-TIME = PARTIAL-TIME TIME-OFFSET + PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND + [TIME-SECFRAC] + TIME-HOUR = 2DIGIT ; 00-23 + TIME-MINUTE = 2DIGIT ; 00-59 + TIME-SECOND = 2DIGIT ; 00-59 + TIME-SECFRAC = "." 1*6DIGIT + TIME-OFFSET = "Z" / TIME-NUMOFFSET + TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE + + + STRUCTURED-DATA = NILVALUE / 1*SD-ELEMENT + SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]" + SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34 + SD-ID = SD-NAME + PARAM-NAME = SD-NAME + PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and + ; ']' MUST be escaped. + SD-NAME = 1*32PRINTUSASCII + ; except '=', SP, ']', %d34 (") + + MSG = MSG-ANY / MSG-UTF8 + MSG-ANY = *OCTET ; not starting with BOM + MSG-UTF8 = BOM UTF-8-STRING + BOM = %xEF.BB.BF + UTF-8-STRING = *OCTET ; UTF-8 string as specified + ; in RFC 3629 + + OCTET = %d00-255 + SP = %d32 + PRINTUSASCII = %d33-126 + NONZERO-DIGIT = %d49-57 + DIGIT = %d48 / NONZERO-DIGIT + NILVALUE = "-" + * + * TIMESTAMP: realtime_clock == 0 ? timertick / 10⁶ : realtime_clock + * HOSTNAME hostname + * APPNAME: ems-esp-link + * PROCID: timertick + * MSGID: NILVALUE + * + * Returns : none + *******************************************************************************/ +void ICACHE_FLASH_ATTR syslog(uint8_t facility, uint8_t severity, const char *tag, const char *fmt, ...) +{ + DBG("[%dµs] %s status: %s\n", WDEV_NOW(), __FUNCTION__, syslog_get_status()); + + if (syslogState == SYSLOG_ERROR || + syslogState == SYSLOG_HALTED) + return; + + if (severity > flashConfig.syslog_filter) + return; + + // compose the syslog message + void *arg = __builtin_apply_args(); + void *res = __builtin_apply((void*)syslog_compose, arg, 128); + syslog_entry_t *se = *(syslog_entry_t **)res; + + // and append it to the message queue + syslog_add_entry(se); + + if (syslogState == SYSLOG_NONE) + syslog_set_status(SYSLOG_WAIT); + + if (! syslog_timer_armed) + syslog_chk_status(); +} diff --git a/syslog/syslog.h b/syslog/syslog.h new file mode 100644 index 0000000..ed453ab --- /dev/null +++ b/syslog/syslog.h @@ -0,0 +1,117 @@ +/* + * syslog.h + * + * + * Copyright 2015 Susi's Strolch + * + * For license information see projects "License.txt" + * + * part of syslog.c - client library + * + */ + + +#ifndef _SYSLOG_H +#define _SYSLOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum syslog_state { + SYSLOG_NONE, // not initialized + SYSLOG_WAIT, // waiting for Wifi + SYSLOG_INIT, // WIFI avail, must initialize + SYSLOG_INITDONE, + SYSLOG_DNSWAIT, // WIFI avail, init done, waiting for DNS resolve + SYSLOG_READY, // Wifi established, ready to send + SYSLOG_SENDING, // UDP package on the air + SYSLOG_SEND, + SYSLOG_SENT, + SYSLOG_HALTED, // heap full, discard message + SYSLOG_ERROR, +}; + +enum syslog_priority { + SYSLOG_PRIO_EMERG, /* system is unusable */ + SYSLOG_PRIO_ALERT, /* action must be taken immediately */ + SYSLOG_PRIO_CRIT, /* critical conditions */ + SYSLOG_PRIO_ERR, /* error conditions */ + SYSLOG_PRIO_WARNING, /* warning conditions */ + SYSLOG_PRIO_NOTICE, /* normal but significant condition */ + SYSLOG_PRIO_INFO, /* informational */ + SYSLOG_PRIO_DEBUG, /* debug-level messages */ +}; + +enum syslog_facility { + SYSLOG_FAC_KERN, /* kernel messages */ + SYSLOG_FAC_USER, /* random user-level messages */ + SYSLOG_FAC_MAIL, /* mail system */ + SYSLOG_FAC_DAEMON, /* system daemons */ + SYSLOG_FAC_AUTH, /* security/authorization messages */ + SYSLOG_FAC_SYSLOG, /* messages generated internally by syslogd */ + SYSLOG_FAC_LPR, /* line printer subsystem */ + SYSLOG_FAC_NEWS, /* network news subsystem */ + SYSLOG_FAC_UUCP, /* UUCP subsystem */ + SYSLOG_FAC_CRON, /* clock daemon */ + SYSLOG_FAC_AUTHPRIV,/* security/authorization messages (private) */ + SYSLOG_FAC_FTP, /* ftp daemon */ + SYSLOG_FAC_LOCAL0, /* reserved for local use */ + SYSLOG_FAC_LOCAL1, /* reserved for local use */ + SYSLOG_FAC_LOCAL2, /* reserved for local use */ + SYSLOG_FAC_LOCAL3, /* reserved for local use */ + SYSLOG_FAC_LOCAL4, /* reserved for local use */ + SYSLOG_FAC_LOCAL5, /* reserved for local use */ + SYSLOG_FAC_LOCAL6, /* reserved for local use */ + SYSLOG_FAC_LOCAL7, /* reserved for local use */ +}; + +#define MINIMUM_HEAP_SIZE 8192 +#define REG_READ(_r) (*(volatile uint32 *)(_r)) +#define WDEV_NOW() REG_READ(0x3ff20c00) + +extern uint32_t realtime_stamp; // 1sec NTP ticker + +typedef struct syslog_host_t syslog_host_t; +struct syslog_host_t { + uint32_t min_heap_size; // minimum allowed heap size when buffering + ip_addr_t addr; + uint16_t port; +}; + +// buffered syslog event - f.e. if network stack isn't up and running +typedef struct syslog_entry_t syslog_entry_t; +struct syslog_entry_t { + syslog_entry_t *next; + uint32_t msgid; + uint32_t tick; + uint16_t datagram_len; + char datagram[]; +}; + +syslog_host_t syslogserver; + +void ICACHE_FLASH_ATTR syslog_init(char *syslog_host); +void ICACHE_FLASH_ATTR syslog(uint8_t facility, uint8_t severity, const char tag[], const char message[], ...); + +// some convenience macros +#ifdef SYSLOG +// extern char *esp_link_version; // in user_main.c +#define LOG_DEBUG(format, ...) syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_DEBUG, "esp_link", format, ## __VA_ARGS__ ) +#define LOG_NOTICE(format, ...) syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "esp_link", format, ## __VA_ARGS__ ) +#define LOG_INFO(format, ...) syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_INFO, "esp_link", format, ## __VA_ARGS__ ) +#define LOG_WARN(format, ...) syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_WARNING, "esp_link", format, ## __VA_ARGS__ ) +#define LOG_ERR(format, ...) syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_ERR, "esp_link", format, ## __VA_ARGS__ ) +#else +#define LOG_DEBUG(format, ...) do { } while(0) +#define LOG_NOTICE(format, ...) do { } while(0) +#define LOG_WARN(format, ...) do { } while(0) +#define LOG_INFO(format, ...) do { } while(0) +#define LOG_ERR(format, ...) do { } while(0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _SYSLOG_H */ diff --git a/syslog/syslog.md b/syslog/syslog.md new file mode 100644 index 0000000..9841f91 --- /dev/null +++ b/syslog/syslog.md @@ -0,0 +1,146 @@ +syslog +====== + +The lib (tries to )implement a RFC5424 compliant syslog interface for ESP8266. syslog +messages are send via UDP. Messages are send in the following format: + +``` +PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP MSG + PRI: msg priority: facility * 8 + severity + TIMESTAMP: dash (no timestamp) or ISO8601 (2015-12-14T17:26:32Z) + HOSTNAME: flashConfig.hostname + APP-NAME: tag - (e.g. MQTT, mySQL, REST, ...) + PROCID: dash or ESP system tick (µseconds since reboot) + MSGID: counter - # syslog messages since reboot + MSG: the syslog message +``` + +The meaning of TIMESTAMP, HOSTNAME, PROCID and MSGID is hardcoded, all others are parameters for the syslog function. + +syslog messages are queued on heap until the Wifi stack is fully initialized: + +``` +Jan 1 00:00:00 192.168.254.82 esp_link 0.126850 1 Reset cause: 4=restart +Jan 1 00:00:00 192.168.254.82 esp_link 0.133970 2 exccause=0 epc1=0x0 epc2=0x0 epc3=0x0 excvaddr=0x0 depc=0x0 +Jan 1 00:00:00 192.168.254.82 esp_link 0.151069 3 Flash map 4MB:512/512, manuf 0xC8 chip 0x4016 +Jan 1 00:00:00 192.168.254.82 esp_link 0.166935 4 ** esp-link ready +Jan 1 00:00:00 192.168.254.82 esp_link 0.185586 5 initializing MQTT +Jan 1 00:00:00 192.168.254.82 esp_link 0.200681 6 initializing user application +Jan 1 00:00:00 192.168.254.82 esp_link 0.215169 7 waiting for work to do... +Jan 1 00:00:03 192.168.254.82 SYSLOG 3.325626 8 syslogserver: 192.168.254.216:514 +Jan 1 00:00:03 192.168.254.82 esp_link 3.336756 9 syslog_init: host: 192.168.254.216, port: 514, lport: 24377, state: 4 +Dec 15 11:49:14 192.168.254.82 esp-link 18.037949 10 Accept port 23, conn=3fff5f68, pool slot 0 +``` + +If the remaining heap size reaches a given limit, syslog will add a final obituary +and stop further logging until the queue is empty and sufficient heap space is +available again. + +The module may be controlled by flashconfig variables: + +* **syslog_host: host[:port]** + + **host** is an IP-address or DNS-name. **port** is optional and defaults to 514. +DNS-Resolution is done as soon as the Wifi stack is up and running. + +* **syslog_minheap: 8192** + + **minheap** specifies the minimum amount of remaining free heap when queuing up +syslog messages. If the remaining heap size is below **minheap**, syslog will insert +an obituary message and stop queuing. After processing all queued messages, the +logging will be enabled again. + +* **syslog_filter: 0..7** + + **syslog_filter** is the minimum severity for sending a syslog message. The filter +is applied against the message queue, so any message with a severity numerical higher +than **syslog_filter** will be dropped instead of being queued/send. + +* **syslog_showtick: 0|1** + + If **syslog_showtick** is set to **1**, syslog will insert an additional timestamp +(system tick) as "PROCID" field (before the users real syslog message). +The value shown is in seconds, with 1µs resolution since (re)boot or timer overflow. + +* **syslog_showdate: 0|1** + + If **syslog_showdate** is set to **1**, syslog will insert the ESPs NTP time +into the syslog message. If "realtime_stamp" (NTP 1s ticker) is **NULL**, the +time is derived from a pseudo-time based on the absolute value of systemticks. + + Some syslog servers (e.g. Synology) will do crazy things if you set **syslog_showdate** to **1** + + +The syslog module exports two functions: + +``` +syslog_init(char *server_name); +syslog(uint8_t facility, uint8_t severity, const char *tag, const char *fmt, ...); +``` + +syslog_init +----------- +usage: `syslog_init(char *server_name);` + +**syslog_init** expects a server name in format "host:port" (see **syslog_host** flashconfig). + +If **server_name** is **NULL**, all dynamic allocated memory (buffers, queues, interfaces) +are released and the syslog state is set to "SYSLOG_HALTED". + +If **server_name** is **""**, syslog state is set to "SYSLOG_HALTED", without clearing +the queue. + +Otherwise, syslog_init will allocate all required structures (buffers, interfaces) and +send all collected syslog messages. + +syslog is self-initializing, meaning the syslog_init(server_name) is called on first +invocation. The syslog_init function is only for convenience if you have to stop or disable syslog functions. + + +syslog +------ +usage: `syslog(uint8_t facility, uint8_t severity, const char *tag, const char *fmt, ...);` + +* **facility** + + the message facility (see syslog.h, **enum syslog_facility**). + +* **severity** + + the message severity (see syslog.h, **enum syslog_severity**) + +* **tag** + + user defined tag (e.g. "MQTT", "REST", "UART") to specify where the message belongs to + +* ** const char *fmt, ...** + + the desired message, in printf format. + +Examples +======== + hostname="ems-link02", showtick=0, showdate=0 + Syslog message: USER.NOTICE: - ems-link02 esp_link - 20 syslog_init: host: 192.168.254.216, port: 514, lport: 28271, rsentcb: 40211e08, state: 4\n + + hostname="ems-link02", showtick=1, showdate=0 + Syslog message: USER.NOTICE: - ems-link02 esp_link 3.325677 8 syslog_init: host: 192.168.254.216, port: 514, lport: 19368, rsentcb: 40211e08, state: 4\n + + hostname="ems-link02", showtick=1, showdate=1, NTP not available + Syslog message: USER.NOTICE: 1970-01-01T00:00:03.325668Z ems-link02 esp_link 3.325668 8 syslog_init: host: 192.168.254.216, port: 514, lport: 36802, rsentcb: 40211e08, state: 4\n + + hostname="ems-link02", showtick=1, showdate=1, NTP available + Syslog message: USER.NOTICE: 2015-12-15T11:15:29+00:00 ems-link02 esp_link 182.036860 13 syslog_init: host: 192.168.254.216, port: 514, lport: 43626, rsentcb: 40291db8, state: 4\n + +Notes +===== ++ The ESP8266 (NON-OS) needs a delay of **at least 2ms** between consecutive UDP packages. So the syslog throughput is restricted to approx. 500EPS. + ++ If a syslog message doesn't have the timestamp set ( **syslog_showdate** == 0), the syslog _server_ will insert _it's own receive timestamp_ into the log message. + ++ If **syslog_showdate** == 1, the syslog _server_ MAY replace it's own receive timestamp with the timestamp sent by the syslog client. + ++ Some syslog servers don't show the fractional seconds of the syslog timestamp + ++ Setting **syslog_showdate** will send timestamps from 1970 (because of using the internal ticker) until the **SNTP-client** got a valid NTP datagram. Some syslog servers (for example _Synology_) will roll over their database if they get such "old" syslog messages. In fact, you won't see those messages in your current syslog. + ++ Some servers (e.g. _Synology_) won't show the syslog message if you set **facility** to **SYSLOG_FAC_SYSLOG**. diff --git a/user/gpio16.c b/user/gpio16.c new file mode 100644 index 0000000..26de620 --- /dev/null +++ b/user/gpio16.c @@ -0,0 +1,42 @@ +#include "ets_sys.h" +#include "osapi.h" +#include "gpio16.h" + +void ICACHE_FLASH_ATTR +gpio16_output_conf(void) +{ + WRITE_PERI_REG(PAD_XPD_DCDC_CONF, + (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbc) | (uint32)0x1); // mux configuration for XPD_DCDC to output rtc_gpio0 + + WRITE_PERI_REG(RTC_GPIO_CONF, + (READ_PERI_REG(RTC_GPIO_CONF) & (uint32)0xfffffffe) | (uint32)0x0); //mux configuration for out enable + + WRITE_PERI_REG(RTC_GPIO_ENABLE, + (READ_PERI_REG(RTC_GPIO_ENABLE) & (uint32)0xfffffffe) | (uint32)0x1); //out enable +} + +void ICACHE_FLASH_ATTR +gpio16_output_set(uint8 value) +{ + WRITE_PERI_REG(RTC_GPIO_OUT, + (READ_PERI_REG(RTC_GPIO_OUT) & (uint32)0xfffffffe) | (uint32)(value & 1)); +} + +void ICACHE_FLASH_ATTR +gpio16_input_conf(void) +{ + WRITE_PERI_REG(PAD_XPD_DCDC_CONF, + (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbc) | (uint32)0x1); // mux configuration for XPD_DCDC and rtc_gpio0 connection + + WRITE_PERI_REG(RTC_GPIO_CONF, + (READ_PERI_REG(RTC_GPIO_CONF) & (uint32)0xfffffffe) | (uint32)0x0); //mux configuration for out enable + + WRITE_PERI_REG(RTC_GPIO_ENABLE, + READ_PERI_REG(RTC_GPIO_ENABLE) & (uint32)0xfffffffe); //out disable +} + +uint8 ICACHE_FLASH_ATTR +gpio16_input_get(void) +{ + return (uint8)(READ_PERI_REG(RTC_GPIO_IN_DATA) & 1); +} diff --git a/user/hmac_sha1.c b/user/hmac_sha1.c new file mode 100644 index 0000000..1e1ff39 --- /dev/null +++ b/user/hmac_sha1.c @@ -0,0 +1,195 @@ +/* + * hmac_sha1.c + * + * Version 1.0.0 + * + * Written by Aaron D. Gifford + * + * Copyright 1998, 2000 Aaron D. Gifford. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * The HMAC-SHA1 has is defined as: + * + * HMAC = SHA1(K XOR opad, SHA1(K XOR ipad, message)) + * + * "opad" is 64 bytes filled with 0x5c + * "ipad" is 64 bytes filled with 0x36 + * "K" is the key material + * + * If the key material "K" is longer than 64 bytes, then the key material + * will first be digested (K = SHA1(K)) resulting in a 20-byte hash. + * If the key material is shorter than 64 bytes, it is padded with zero + * bytes. + * + * This code precomputes "K XOR ipad" and "K XOR opad" since that just makes + * sense. + * + * This code was heavily influenced by Eric A. Young's in how the interface + * was designed and how this file is formatted. + */ + +#ifndef __HMAC_SHA1_H__ +#define __HMAC_SHA1_H__ + +#include "hmac_sha1.h" +#include +#include "espmissingincludes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Filler bytes: */ +#define IPAD_BYTE 0x36 +#define OPAD_BYTE 0x5c +#define ZERO_BYTE 0x00 + +void HMAC_SHA1_Init(HMAC_SHA1_CTX *ctx) { + memset(&(ctx->key[0]), ZERO_BYTE, HMAC_SHA1_BLOCK_LENGTH); + memset(&(ctx->ipad[0]), IPAD_BYTE, HMAC_SHA1_BLOCK_LENGTH); + memset(&(ctx->opad[0]), OPAD_BYTE, HMAC_SHA1_BLOCK_LENGTH); + ctx->keylen = 0; + ctx->hashkey = 0; +} + +void ICACHE_FLASH_ATTR HMAC_SHA1_UpdateKey(HMAC_SHA1_CTX *ctx, unsigned char *key, unsigned int keylen) { + + /* Do we have anything to work with? If not, return right away. */ + if (keylen < 1) + return; + + /* + * Is the total key length (current data and any previous data) + * longer than the hash block length? + */ + if (ctx->hashkey !=0 || (keylen + ctx->keylen) > HMAC_SHA1_BLOCK_LENGTH) { + /* + * Looks like the key data exceeds the hash block length, + * so that means we use a hash of the key as the key data + * instead. + */ + if (ctx->hashkey == 0) { + /* + * Ah, we haven't started hashing the key + * data yet, so we must init. the hash + * monster to begin feeding it. + */ + + /* Set the hash key flag to true (non-zero) */ + ctx->hashkey = 1; + + /* Init. the hash beastie... */ + SHA1_Init(&ctx->shactx); + + /* If there's any previous key data, use it */ + if (ctx->keylen > 0) { + SHA1_Update(&ctx->shactx, &(ctx->key[0]), ctx->keylen); + } + + /* + * Reset the key length to the future true + * key length, HMAC_SHA1_DIGEST_LENGTH + */ + ctx->keylen = HMAC_SHA1_DIGEST_LENGTH; + } + /* Now feed the latest key data to the has monster */ + SHA1_Update(&ctx->shactx, key, keylen); + } else { + /* + * Key data length hasn't yet exceeded the hash + * block length (HMAC_SHA1_BLOCK_LENGTH), so theres + * no need to hash the key data (yet). Copy it + * into the key buffer. + */ + memcpy(&(ctx->key[ctx->keylen]), key, keylen); + ctx->keylen += keylen; + } +} + +void ICACHE_FLASH_ATTR HMAC_SHA1_EndKey(HMAC_SHA1_CTX *ctx) { + unsigned char *ipad, *opad, *key; + int i; + + /* Did we end up hashing the key? */ + if (ctx->hashkey) { + memset(&(ctx->key[0]), ZERO_BYTE, HMAC_SHA1_BLOCK_LENGTH); + /* Yes, so finish up and copy the key data */ + SHA1_Final(&(ctx->key[0]), &ctx->shactx); + /* ctx->keylen was already set correctly */ + } + /* Pad the key if necessary with zero bytes */ + if ((i = HMAC_SHA1_BLOCK_LENGTH - ctx->keylen) > 0) { + memset(&(ctx->key[ctx->keylen]), ZERO_BYTE, i); + } + + ipad = &(ctx->ipad[0]); + opad = &(ctx->opad[0]); + + /* Precompute the respective pads XORed with the key */ + key = &(ctx->key[0]); + for (i = 0; i < ctx->keylen; i++, key++) { + /* XOR the key byte with the appropriate pad filler byte */ + *ipad++ ^= *key; + *opad++ ^= *key; + } +} + +void ICACHE_FLASH_ATTR HMAC_SHA1_StartMessage(HMAC_SHA1_CTX *ctx) { + SHA1_Init(&ctx->shactx); + SHA1_Update(&ctx->shactx, &(ctx->ipad[0]), HMAC_SHA1_BLOCK_LENGTH); +} + +void ICACHE_FLASH_ATTR HMAC_SHA1_UpdateMessage(HMAC_SHA1_CTX *ctx, unsigned char *data, unsigned int datalen) { + SHA1_Update(&ctx->shactx, data, datalen); +} + +void ICACHE_FLASH_ATTR HMAC_SHA1_EndMessage(unsigned char *out, HMAC_SHA1_CTX *ctx) { + unsigned char buf[HMAC_SHA1_DIGEST_LENGTH]; + SHA_CTX *c = &ctx->shactx; + + SHA1_Final(&(buf[0]), c); + SHA1_Init(c); + SHA1_Update(c, &(ctx->opad[0]), HMAC_SHA1_BLOCK_LENGTH); + SHA1_Update(c, buf, HMAC_SHA1_DIGEST_LENGTH); + SHA1_Final(out, c); +} + +void ICACHE_FLASH_ATTR HMAC_SHA1_Done(HMAC_SHA1_CTX *ctx) { + /* Just to be safe, toast all context data */ + memset(&(ctx->ipad[0]), ZERO_BYTE, HMAC_SHA1_BLOCK_LENGTH); + memset(&(ctx->ipad[0]), ZERO_BYTE, HMAC_SHA1_BLOCK_LENGTH); + memset(&(ctx->key[0]), ZERO_BYTE, HMAC_SHA1_BLOCK_LENGTH); + ctx->keylen = 0; + ctx->hashkey = 0; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/user/httpclient.c b/user/httpclient.c new file mode 100644 index 0000000..f43682c --- /dev/null +++ b/user/httpclient.c @@ -0,0 +1,355 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Martin d'Allens wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + +// FIXME: sprintf->snprintf everywhere. +// FIXME: support null characters in responses. + +#include "espmissingincludes.h" +#include "ets_sys.h" +#include "osapi.h" +#include "user_interface.h" +#include "espconn.h" +#include "mem.h" +#include "httpclient.h" + + +// Debug output. +#if 0 +#define PRINTF(...) os_printf(__VA_ARGS__) +#else +#define PRINTF(...) +#endif + +// Internal state. +typedef struct { + char * path; + int port; + char * post_data; + char * hostname; + char * buffer; + int buffer_size; + http_callback user_callback; +} request_args; + +static char * esp_strdup(const char * str) +{ + if (str == NULL) { + return NULL; + } + char * new_str = (char *)os_malloc(os_strlen(str) + 1); // 1 for null character + if (new_str == NULL) { + os_printf("esp_strdup: malloc error"); + return NULL; + } + os_strcpy(new_str, str); + return new_str; +} + +static void ICACHE_FLASH_ATTR receive_callback(void * arg, char * buf, unsigned short len) +{ + struct espconn * conn = (struct espconn *)arg; + request_args * req = (request_args *)conn->reverse; + + if (req->buffer == NULL) { + return; + } + + // Let's do the equivalent of a realloc(). + const int new_size = req->buffer_size + len; + char * new_buffer; + if (new_size > BUFFER_SIZE_MAX || NULL == (new_buffer = (char *)os_malloc(new_size))) { + os_printf("Response too long %d\n", new_size); + os_free(req->buffer); + req->buffer = NULL; + // TODO: espconn_disconnect(conn) without crashing. + return; + } + + os_memcpy(new_buffer, req->buffer, req->buffer_size); + os_memcpy(new_buffer + req->buffer_size - 1 /*overwrite the null character*/, buf, len); // Append new data. + new_buffer[new_size - 1] = '\0'; // Make sure there is an end of string. + + os_free(req->buffer); + req->buffer = new_buffer; + req->buffer_size = new_size; +} + +static void ICACHE_FLASH_ATTR sent_callback(void * arg) +{ + struct espconn * conn = (struct espconn *)arg; + request_args * req = (request_args *)conn->reverse; + + if (req->post_data == NULL) { + PRINTF("All sent\n"); + } + else { + // The headers were sent, now send the contents. + PRINTF("Sending request body\n"); + espconn_sent(conn, (uint8_t *)req->post_data, strlen(req->post_data)); + os_free(req->post_data); + req->post_data = NULL; + } +} + +// enum espconn state, see file /include/lwip/api/err.c +const char *sEspconnErr[] = +{ + "Ok", // ERR_OK 0 + "Out of memory error", // ERR_MEM -1 + "Buffer error", // ERR_BUF -2 + "Timeout", // ERR_TIMEOUT -3 + "Routing problem", // ERR_RTE -4 + "Operation in progress", // ERR_INPROGRESS -5 + "Illegal value", // ERR_VAL -6 + "Operation would block", // ERR_WOULDBLOCK -7 + "Connection aborted", // ERR_ABRT -8 + "Connection reset", // ERR_RST -9 + "Connection closed", // ERR_CLSD -10 + "Not connected", // ERR_CONN -11 + "Illegal argument", // ERR_ARG -12 + "Address in use", // ERR_USE -13 + "Low-level netif error", // ERR_IF -14 + "Already connected" // ERR_ISCONN -15 +}; + +static void ICACHE_FLASH_ATTR reconnect_callback(void * arg, sint8 errType) +{ + struct espconn *conn = (struct espconn *)arg; + PRINTF("RECONNECT\n"); + if (errType != ESPCONN_OK) + PRINTF("Connection error: %d - %s\r\n", errType, ((errType>-16)&&(errType<1))? sEspconnErr[-errType] : "?"); + if(conn->proto.tcp != NULL) + { + os_free(conn->proto.tcp); + PRINTF("os_free: conn->proto.tcp\r\n"); + } + os_free(conn); + PRINTF("os_free: conn\r\n"); +} + +static void ICACHE_FLASH_ATTR connect_callback(void * arg) +{ + PRINTF("Connected\n"); + struct espconn * conn = (struct espconn *)arg; + request_args * req = (request_args *)conn->reverse; + + espconn_regist_recvcb(conn, receive_callback); + espconn_regist_sentcb(conn, sent_callback); + + const char * method = "GET"; + char post_headers[128] = ""; + + if (req->post_data != NULL) { // If there is data this is a POST request. + method = "POST"; + os_sprintf(post_headers, + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %d\r\n", strlen(req->post_data)); + } + + char buf[2048]; + int len = os_sprintf(buf, + "%s %s HTTP/1.1\r\n" + "Host: %s:%d\r\n" + "Connection: close\r\n" + "User-Agent: ESP8266\r\n" + "%s" + "\r\n", + method, req->path, req->hostname, req->port, post_headers); + + espconn_sent(conn, (uint8_t *)buf, len); + PRINTF("Sending request header\n"); +} + +static void ICACHE_FLASH_ATTR disconnect_callback(void * arg) +{ + PRINTF("Disconnected\n"); + struct espconn *conn = (struct espconn *)arg; + + if(conn == NULL) { + return; + } + + if(conn->proto.tcp != NULL) { + os_free(conn->proto.tcp); + } + if(conn->reverse != NULL) { + request_args * req = (request_args *)conn->reverse; + if (req->buffer != NULL) { + // FIXME: make sure this is not a partial response, using the Content-Length header. + + const char * version = "HTTP/1.1 "; + if (os_strncmp(req->buffer, version, strlen(version)) != 0) { + os_printf("Invalid version in %s\n", req->buffer); + return; + } + int http_status = atoi(req->buffer + strlen(version)); + + char * body = (char *)os_strstr(req->buffer, "\r\n\r\n") + 4; + + if (req->user_callback != NULL) { // Callback is optional. + req->user_callback(body, http_status, req->buffer); + } + os_free(req->buffer); + } + os_free(req->hostname); + os_free(req->path); + os_free(req); + } + os_free(conn); +} + +static void ICACHE_FLASH_ATTR dns_callback(const char * hostname, ip_addr_t * addr, void * arg) +{ + request_args * req = (request_args *)arg; + + if (addr == NULL) { + os_printf("DNS failed %s\n", hostname); + req->user_callback(NULL, 700, NULL); + } + else { + PRINTF("DNS found %s " IPSTR "\n", hostname, IP2STR(addr)); + + struct espconn * conn = (struct espconn *)os_malloc(sizeof(struct espconn)); + conn->type = ESPCONN_TCP; + conn->state = ESPCONN_NONE; + conn->proto.tcp = (esp_tcp *)os_malloc(sizeof(esp_tcp)); + conn->proto.tcp->local_port = espconn_port(); + conn->proto.tcp->remote_port = req->port; + conn->reverse = req; + + os_memcpy(conn->proto.tcp->remote_ip, addr, 4); + + espconn_regist_connectcb(conn, connect_callback); + espconn_regist_disconcb(conn, disconnect_callback); + + espconn_regist_reconcb(conn, reconnect_callback); + + // TODO: consider using espconn_regist_reconcb (for timeouts?) + // cf esp8266_sdk_v0.9.1/examples/at/user/at_ipCmd.c (TCP ARQ retransmission?) + + espconn_connect(conn); + } +} + +void ICACHE_FLASH_ATTR http_raw_request(const char * hostname, int port, const char * path, const char * post_data, http_callback user_callback) +{ + PRINTF("DNS request\n"); + + request_args * req = (request_args *)os_malloc(sizeof(request_args)); + req->hostname = esp_strdup(hostname); + req->path = esp_strdup(path); + req->port = port; + req->post_data = esp_strdup(post_data); + req->buffer_size = 1; + req->buffer = (char *)os_malloc(1); + req->buffer[0] = '\0'; // Empty string. + req->user_callback = user_callback; + + ip_addr_t addr; + err_t error = espconn_gethostbyname((struct espconn *)req, // It seems we don't need a real espconn pointer here. + hostname, &addr, dns_callback); + + if (error == ESPCONN_INPROGRESS) { + PRINTF("DNS pending\n"); + } + else if (error == ESPCONN_OK) { + // Already in the local names table (or hostname was an IP address), execute the callback ourselves. + dns_callback(hostname, &addr, req); + } + else if (error == ESPCONN_ARG) { + os_printf("DNS error %s\n", hostname); + } + else { + os_printf("DNS error code %d\n", error); + } +} + +/* + * Parse an URL of the form http://host:port/path + * can be a hostname or an IP address + * is optional + */ +void ICACHE_FLASH_ATTR http_post(const char * url, const char * post_data, http_callback user_callback) +{ + // FIXME: handle HTTP auth with http://user:pass@host/ + // FIXME: make https work. + // FIXME: get rid of the #anchor part if present. + + char hostname[128] = ""; + int port = 80; + + if (os_strncmp(url, "http://", strlen("http://")) != 0) { + os_printf("URL is not HTTP %s\n", url); + return; + } + url += strlen("http://"); // Get rid of the protocol. + + char * path = os_strchr(url, '/'); + if (path == NULL) { + path = os_strchr(url, '\0'); // Pointer to end of string. + } + + char * colon = os_strchr(url, ':'); + if (colon > path) { + colon = NULL; // Limit the search to characters before the path. + } + + if (colon == NULL) { // The port is not present. + os_memcpy(hostname, url, path - url); + hostname[path - url] = '\0'; + } + else { + port = atoi(colon + 1); + if (port == 0) { + os_printf("Port error %s\n", url); + return; + } + + os_memcpy(hostname, url, colon - url); + hostname[colon - url] = '\0'; + } + + + if (path[0] == '\0') { // Empty path is not allowed. + path = "/"; + } + + PRINTF("hostname=%s\n", hostname); + PRINTF("port=%d\n", port); + PRINTF("path=%s\n", path); + http_raw_request(hostname, port, path, post_data, user_callback); +} + +void ICACHE_FLASH_ATTR http_get(const char * url, http_callback user_callback) +{ + http_post(url, NULL, user_callback); +} + +void http_callback_example(char * response, int http_status, char * full_response) +{ + os_printf("http_status=%d\n", http_status); + os_printf("strlen(response)=%d\n", strlen(response)); + os_printf("strlen(full_response)=%d\n", strlen(full_response)); + os_printf("response=%s\n(end)\n", response); +} + +void http_test() +{ + /* Test cases: + http_get("https://google.com"); // Should fail. + http_get("http://google.com/search?q=1"); + http_get("http://google.com"); + http_get("http://portquiz.net:8080/"); + http_raw_request("google.com", 80, "/search?q=2", NULL); + http_get("http://173.194.45.65"); // Fails if not online yet. FIXME: we should wait for DHCP to have finished before connecting. + */ + + http_get("http://wtfismyip.com/text", http_callback_example); + http_post("http://httpbin.org/post", "first_word=hello&second_word=world", http_callback_example); +} diff --git a/user/jsmn.c b/user/jsmn.c new file mode 100644 index 0000000..7cfc8d9 --- /dev/null +++ b/user/jsmn.c @@ -0,0 +1,310 @@ +#include + +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Filsl next token with JSON string. + */ +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + jsmnerr_t r; + int i; + jsmntok_t *token; + int count = 0; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} diff --git a/user/libc_replacements.c b/user/libc_replacements.c new file mode 100644 index 0000000..9068834 --- /dev/null +++ b/user/libc_replacements.c @@ -0,0 +1,75 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include "ets_sys.h" +#include "os_type.h" +#include "osapi.h" +#include "mem.h" +#include "user_interface.h" + +char* ICACHE_FLASH_ATTR strtok_r(char* s, const char* delim, char** last) { + const char* spanp; + char* tok; + char c; + char sc; + + if (s == NULL && (s = *last) == NULL) { + return (NULL); + } + + + // Skip (span) leading delimiters + // +cont: + c = *s++; + for (spanp = delim; (sc = *spanp++) != 0;) { + if (c == sc) { + goto cont; + } + } + + // check for no delimiters left + // + if (c == '\0') { + *last = NULL; + return (NULL); + } + + tok = s - 1; + + + // Scan token + // Note that delim must have one NUL; we stop if we see that, too. + // + for (;;) { + c = *s++; + spanp = (char *)delim; + do { + if ((sc = *spanp++) == c) { + if (c == 0) { + s = NULL; + } + else { + s[-1] = '\0'; + } + *last = s; + return (tok); + } + + } while (sc != 0); + } + + // NOTREACHED EVER +} + +char* ICACHE_FLASH_ATTR strtok(char* s, const char* delim) { + static char* last; + + return (strtok_r(s, delim, &last)); +} diff --git a/user/mfrc522.c b/user/mfrc522.c new file mode 100644 index 0000000..551fe7c --- /dev/null +++ b/user/mfrc522.c @@ -0,0 +1,1218 @@ +/* +* MFRC522.cpp - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT. +* _Please_ see the comments in MFRC522.h - they give useful hints and background. +* Released into the public domain. +*/ + +#include "mfrc522.h" +#include "espmissingincludes.h" +#define HSPI 1 + +#define PERIPHS_IO_MUX_PULLDWN BIT6 +#define PIN_PULLDWN_DIS(PIN_NAME) CLEAR_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLDWN) + +static const char* const _TypeNamePICC[] = +{ + "Unknown type", + "PICC compliant with ISO/IEC 14443-4", + "PICC compliant with ISO/IEC 18092 (NFC)", + "MIFARE Mini, 320 bytes", + "MIFARE 1KB", + "MIFARE 4KB", + "MIFARE Ultralight or Ultralight C", + "MIFARE Plus", + "MIFARE TNP3XXX", + + /* not complete UID */ + "SAK indicates UID is not complete" +}; + +static const char* const _ErrorMessage[] = +{ + "Unknown error", + "Success", + "Error in communication", + "Collision detected", + "Timeout in communication", + "A buffer is not big enough", + "Internal error in the code, should not happen", + "Invalid argument", + "The CRC_A does not match", + "A MIFARE PICC responded with NAK" +}; + +#define MFRC522_MaxPICCs (sizeof(_TypeNamePICC)/sizeof(_TypeNamePICC[0])) +#define MFRC522_MaxError (sizeof(_ErrorMessage)/sizeof(_ErrorMessage[0])) + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for setting up the driver +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Constructor. + * Prepares the output pins. + */ + + +///////////////////////////////////////////////////////////////////////////////////// +// Basic interface functions for communicating with the MFRC522 +///////////////////////////////////////////////////////////////////////////////////// + +void wait_ms(int ms) +{ + os_delay_us(1000*ms); +} + +/** + * Writes a byte to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void ICACHE_FLASH_ATTR PCD_WriteRegister(uint8_t reg, uint8_t value) +{ + /* Select SPI Chip MFRC522 */ + //GPIO_OUTPUT_SET(15, 0); + + // MSB == 0 is for writing. LSB is not used in address. Datasheet section 8.1.2.3. + //(void) m_SPI.write(reg & 0x7E); + //(void) m_SPI.write(value); + //spi_mast_byte_write(HSPI, reg & 0x7E); + //spi_mast_byte_write(HSPI, value); + platform_spi_send_recv(HSPI, reg & 0x7E, 0); + platform_spi_send_recv(HSPI, value, 1); + + /* Release SPI Chip MFRC522 */ + //GPIO_OUTPUT_SET(15, 1); +} // End PCD_WriteRegister() + +/** + * Writes a number of bytes to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void ICACHE_FLASH_ATTR PCD_WriteRegister2(uint8_t reg, uint8_t count, uint8_t *values) +{ + /* Select SPI Chip MFRC522 */ + //GPIO_OUTPUT_SET(15, 0); + + // MSB == 0 is for writing. LSB is not used in address. Datasheet section 8.1.2.3. + //(void) m_SPI.write(reg & 0x7E); + //spi_mast_byte_write(HSPI, reg & 0x7E); + platform_spi_send_recv(HSPI, reg & 0x7E, 0); + bool close = false; + for (uint8_t index = 0; index < count; index++) + { + //(void) m_SPI.write(values[index]); + //spi_mast_byte_write(HSPI, values[index]); + // + if(index == count-1) + { + close = true; + } + platform_spi_send_recv(HSPI, values[index], close); + } + + /* Release SPI Chip MFRC522 */ + //GPIO_OUTPUT_SET(15, 1); +} // End PCD_WriteRegister() + +/** + * Reads a byte from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +uint8_t ICACHE_FLASH_ATTR PCD_ReadRegister(uint8_t reg) +{ + uint8_t value; + /* Select SPI Chip MFRC522 */ + //GPIO_OUTPUT_SET(15, 0); + + // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. + //(void) m_SPI.write(0x80 | reg); + //spi_mast_byte_write(HSPI, 0x80 | reg); + platform_spi_send_recv(HSPI, 0x80 | reg, false); + + // Read the value back. Send 0 to stop reading. + //value = m_SPI.write(0); + //TODO CHECK THIS + //spi_mast_byte_write(HSPI, 0); + value = platform_spi_send_recv(HSPI, 0, true); + + /* Release SPI Chip MFRC522 */ + //GPIO_OUTPUT_SET(15, 1); + + return value; +} // End PCD_ReadRegister() + +/** + * Reads a number of bytes from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void ICACHE_FLASH_ATTR PCD_ReadRegister2(uint8_t reg, uint8_t count, uint8_t *values, uint8_t rxAlign) +{ + rxAlign = 0; + if (count == 0) { return; } + + uint8_t address = 0x80 | reg; // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. + uint8_t index = 0; // Index in values array. + + /* Select SPI Chip MFRC522 */ + //GPIO_OUTPUT_SET(15, 0); + count--; // One read is performed outside of the loop + //(void) m_SPI.write(address); // Tell MFRC522 which address we want to read + //spi_mast_byte_write(HSPI, address); + platform_spi_send_recv(HSPI, address, false); + + while (index < count) + { + if ((index == 0) && rxAlign) // Only update bit positions rxAlign..7 in values[0] + { + // Create bit mask for bit positions rxAlign..7 + uint8_t mask = 0; + for (uint8_t i = rxAlign; i <= 7; i++) + { + mask |= (1 << i); + } + + // Read value and tell that we want to read the same address again. + //uint8_t value = m_SPI.write(address); + //TODO FIX + //spi_mast_byte_write(HSPI, address); + uint8_t value = platform_spi_send_recv(HSPI, address, false); + + // Apply mask to both current value of values[0] and the new data in value. + values[0] = (values[index] & ~mask) | (value & mask); + } + else + { + // Read value and tell that we want to read the same address again. + //values[index] = m_SPI.write(address); + //TODO FIX + //spi_mast_byte_write(HSPI, address); + values[index] = platform_spi_send_recv(HSPI, address, false); + } + + index++; + } + + //values[index] = m_SPI.write(0); // Read the final byte. Send 0 to stop reading. + //TODO fix + //spi_mast_byte_write(HSPI, 0); + values[index] = platform_spi_send_recv(HSPI, 0, true); + + /* Release SPI Chip MFRC522 */ + //GPIO_OUTPUT_SET(15, 1); +} // End PCD_ReadRegister() + +/** + * Sets the bits given in mask in register reg. + */ +void ICACHE_FLASH_ATTR PCD_SetRegisterBits(uint8_t reg, uint8_t mask) +{ + uint8_t tmp = PCD_ReadRegister(reg); + PCD_WriteRegister(reg, tmp | mask); // set bit mask +} // End PCD_SetRegisterBitMask() + +/** + * Clears the bits given in mask from register reg. + */ +void ICACHE_FLASH_ATTR PCD_ClrRegisterBits(uint8_t reg, uint8_t mask) +{ + uint8_t tmp = PCD_ReadRegister(reg); + PCD_WriteRegister(reg, tmp & (~mask)); // clear bit mask +} // End PCD_ClearRegisterBitMask() + + +/** + * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. + */ +uint8_t ICACHE_FLASH_ATTR PCD_CalculateCRC(uint8_t *data, uint8_t length, uint8_t *result) +{ + PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. + PCD_WriteRegister(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit + PCD_SetRegisterBits(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization + PCD_WriteRegister2(FIFODataReg, length, data); // Write data to the FIFO + PCD_WriteRegister(CommandReg, PCD_CalcCRC); // Start the calculation + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. + uint16_t i = 5000; + uint8_t n; + while (1) + { + n = PCD_ReadRegister(DivIrqReg); // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved + if (n & 0x04) + { + // CRCIRq bit set - calculation done + break; + } + + if (--i == 0) + { + // The emergency break. We will eventually terminate on this one after 89ms. + // Communication with the MFRC522 might be down. + return STATUS_TIMEOUT; + } + } + + // Stop calculating CRC for new content in the FIFO. + PCD_WriteRegister(CommandReg, PCD_Idle); + + // Transfer the result from the registers to the result buffer + result[0] = PCD_ReadRegister(CRCResultRegL); + result[1] = PCD_ReadRegister(CRCResultRegH); + return STATUS_OK; +} // End PCD_CalculateCRC() + + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for manipulating the MFRC522 +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the MFRC522 chip. + */ +void ICACHE_FLASH_ATTR PCD_Init() +{ + gpio_init(); + spi_init(); + //set CS high + //GPIO_OUTPUT_SET(15, 1); + + /* Initialize SPI 1 */ + //spi_master_init(HSPI, 0, 0, 0, 0); + //spi.begin(15); + //spi.setDataMode(SPI_MODE0); + //spi.setBitOrder(MSBFIRST); + + /* Initialize GPIO5 for setting the RST line */ + //Set GPIO5 to output mode + //WTF! GPIO4 and GPIO5 are reversed... + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_GPIO2_U); + PIN_PULLDWN_DIS(PERIPHS_IO_MUX_GPIO2_U); + + /* Reset MFRC522 */ + //set GPIO16 low + //gpio16_output_set(0); + GPIO_OUTPUT_SET(BIT2, 0); + wait_ms(10); + //set high + GPIO_OUTPUT_SET(BIT2, 1); + //gpio16_output_set(0); + + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74us. Let us be generous: 50ms. + wait_ms(100); + + // When communicating with a PICC we need a timeout if something goes wrong. + // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. + // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. + PCD_WriteRegister(TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds + PCD_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25us. + PCD_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. + PCD_WriteRegister(TReloadRegL, 0xE8); + + PCD_WriteRegister(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting + PCD_WriteRegister(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) + + PCD_WriteRegister(RFCfgReg, (0x07<<4)); // Set Rx Gain to max + + PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) +} // End PCD_Init() + +/** + * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. + */ +void PCD_Reset() +{ + PCD_WriteRegister(CommandReg, PCD_SoftReset); // Issue the SoftReset command. + // The datasheet does not mention how long the SoftRest command takes to complete. + // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74us. Let us be generous: 50ms. + wait_ms(50); + + // Wait for the PowerDown bit in CommandReg to be cleared + while (PCD_ReadRegister(CommandReg) & (1<<4)) + { + // PCD still restarting - unlikely after waiting 50ms, but better safe than sorry. + } +} // End PCD_Reset() + +/** + * Turns the antenna on by enabling pins TX1 and TX2. + * After a reset these pins disabled. + */ +void ICACHE_FLASH_ATTR PCD_AntennaOn() +{ + uint8_t value = PCD_ReadRegister(TxControlReg); + //os_printf("antenna val %x\r\n", value); + if ((value & 0x03) != 0x03) + { + PCD_WriteRegister(TxControlReg, value | 0x03); + } +} // End PCD_AntennaOn() + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for communicating with PICCs +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Executes the Transceive command. + * CRC validation can only be done if backData and backLen are specified. + */ +uint8_t ICACHE_FLASH_ATTR PCD_TransceiveData(uint8_t *sendData, + uint8_t sendLen, + uint8_t *backData, + uint8_t *backLen, + uint8_t *validBits, + uint8_t rxAlign, + bool checkCRC) +{ + uint8_t waitIRq = 0x30; // RxIRq and IdleIRq + return PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, sendData, sendLen, backData, backLen, validBits, rxAlign, checkCRC); +} // End PCD_TransceiveData() + +/** + * Transfers data to the MFRC522 FIFO, executes a commend, waits for completion and transfers data back from the FIFO. + * CRC validation can only be done if backData and backLen are specified. + */ +uint8_t ICACHE_FLASH_ATTR PCD_CommunicateWithPICC(uint8_t command, + uint8_t waitIRq, + uint8_t *sendData, + uint8_t sendLen, + uint8_t *backData, + uint8_t *backLen, + uint8_t *validBits, + uint8_t rxAlign, + bool checkCRC) +{ + uint8_t n, _validBits = 0; + uint32_t i; + + // Prepare values for BitFramingReg + uint8_t txLastBits = validBits ? *validBits : 0; + uint8_t bitFraming = (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. + PCD_WriteRegister(ComIrqReg, 0x7F); // Clear all seven interrupt request bits + PCD_SetRegisterBits(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization + PCD_WriteRegister2(FIFODataReg, sendLen, sendData); // Write sendData to the FIFO + PCD_WriteRegister(BitFramingReg, bitFraming); // Bit adjustments + PCD_WriteRegister(CommandReg, command); // Execute the command + if (command == PCD_Transceive) + { + PCD_SetRegisterBits(BitFramingReg, 0x80); // StartSend=1, transmission of data starts + } + + // Wait for the command to complete. + // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops transmitting. + // Each iteration of the do-while-loop takes 17.86us. + //i = 2000; + i = 20; + while (1) + { + n = PCD_ReadRegister(ComIrqReg); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq + if (n & waitIRq) + { // One of the interrupts that signal success has been set. + break; + } + + if (n & 0x01) + { // Timer interrupt - nothing received in 25ms + return STATUS_TIMEOUT; + } + + if (--i == 0) + { // The emergency break. If all other condions fail we will eventually terminate on this one after 35.7ms. Communication with the MFRC522 might be down. + //return STATUS_TIMEOUT; + return 11; + } + } + + // Stop now if any errors except collisions were detected. + uint8_t errorRegValue = PCD_ReadRegister(ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr + if (errorRegValue & 0x13) + { // BufferOvfl ParityErr ProtocolErr + //return STATUS_ERROR; + return 201; + } + + // If the caller wants data back, get it from the MFRC522. + if (backData && backLen) + { + n = PCD_ReadRegister(FIFOLevelReg); // Number of bytes in the FIFO + if (n > *backLen) + { + return STATUS_NO_ROOM; + } + + *backLen = n; // Number of bytes returned + PCD_ReadRegister2(FIFODataReg, n, backData, rxAlign); // Get received data from FIFO + _validBits = PCD_ReadRegister(ControlReg) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last received byte. If this value is 000b, the whole byte is valid. + if (validBits) + { + *validBits = _validBits; + } + } + + // Tell about collisions + if (errorRegValue & 0x08) + { // CollErr + return STATUS_COLLISION; + } + + // Perform CRC_A validation if requested. + if (backData && backLen && checkCRC) + { + // In this case a MIFARE Classic NAK is not OK. + if ((*backLen == 1) && (_validBits == 4)) + { + return STATUS_MIFARE_NACK; + } + + // We need at least the CRC_A value and all 8 bits of the last byte must be received. + if ((*backLen < 2) || (_validBits != 0)) + { + return STATUS_CRC_WRONG; + } + + // Verify CRC_A - do our own calculation and store the control in controlBuffer. + uint8_t controlBuffer[2]; + n = PCD_CalculateCRC(&backData[0], *backLen - 2, &controlBuffer[0]); + if (n != STATUS_OK) + { + return n; + } + + if ((backData[*backLen - 2] != controlBuffer[0]) || (backData[*backLen - 1] != controlBuffer[1])) + { + return STATUS_CRC_WRONG; + } + } + + return STATUS_OK; +} // End PCD_CommunicateWithPICC() + +/* + * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + */ +uint8_t ICACHE_FLASH_ATTR PICC_RequestA(uint8_t *bufferATQA, uint8_t *bufferSize) +{ + return PICC_REQA_or_WUPA(PICC_CMD_REQA, bufferATQA, bufferSize); +} // End PICC_RequestA() + +/** + * Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + */ +uint8_t ICACHE_FLASH_ATTR PICC_WakeupA(uint8_t *bufferATQA, uint8_t *bufferSize) +{ + return PICC_REQA_or_WUPA(PICC_CMD_WUPA, bufferATQA, bufferSize); +} // End PICC_WakeupA() + +/* + * Transmits REQA or WUPA commands. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + */ +uint8_t ICACHE_FLASH_ATTR PICC_REQA_or_WUPA(uint8_t command, uint8_t *bufferATQA, uint8_t *bufferSize) +{ + uint8_t validBits; + uint8_t status; + + if (bufferATQA == NULL || *bufferSize < 2) + { // The ATQA response is 2 bytes long. + return STATUS_NO_ROOM; + } + + // ValuesAfterColl=1 => Bits received after collision are cleared. + PCD_ClrRegisterBits(CollReg, 0x80); + + // For REQA and WUPA we need the short frame format + // - transmit only 7 bits of the last (and only) byte. TxLastBits = BitFramingReg[2..0] + validBits = 7; + + status = PCD_TransceiveData(&command, 1, bufferATQA, bufferSize, &validBits, 0, 0); + if (status != STATUS_OK) + { + //os_printf("returning status %d\r\n",status); + return status; + } + + if ((*bufferSize != 2) || (validBits != 0)) + { // ATQA must be exactly 16 bits. + //os_printf("returning error status\r\n"); + //return STATUS_ERROR; + return 202; + } + + //os_printf("returning status OK\r\n"); + return STATUS_OK; +} // End PICC_REQA_or_WUPA() + +/* + * Transmits SELECT/ANTICOLLISION commands to select a single PICC. + */ +uint8_t ICACHE_FLASH_ATTR PICC_Select(Uid *uid, uint8_t validBits) +{ + bool uidComplete; + bool selectDone; + bool useCascadeTag; + uint8_t cascadeLevel = 1; + uint8_t result; + uint8_t count; + uint8_t index; + uint8_t uidIndex; // The first index in uid->uidByte[] that is used in the current Cascade Level. + uint8_t currentLevelKnownBits; // The number of known UID bits in the current Cascade Level. + uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A + uint8_t bufferUsed; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO. + uint8_t rxAlign; // Used in BitFramingReg. Defines the bit position for the first bit received. + uint8_t txLastBits; // Used in BitFramingReg. The number of valid bits in the last transmitted byte. + uint8_t *responseBuffer; + uint8_t responseLength; + + // Description of buffer structure: + // Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 + // Byte 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits. + // Byte 2: UID-data or CT See explanation below. CT means Cascade Tag. + // Byte 3: UID-data + // Byte 4: UID-data + // Byte 5: UID-data + // Byte 6: BCC Block Check Character - XOR of bytes 2-5 + // Byte 7: CRC_A + // Byte 8: CRC_A + // The BCC and CRC_A is only transmitted if we know all the UID bits of the current Cascade Level. + // + // Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) + // UID size Cascade level Byte2 Byte3 Byte4 Byte5 + // ======== ============= ===== ===== ===== ===== + // 4 bytes 1 uid0 uid1 uid2 uid3 + // 7 bytes 1 CT uid0 uid1 uid2 + // 2 uid3 uid4 uid5 uid6 + // 10 bytes 1 CT uid0 uid1 uid2 + // 2 CT uid3 uid4 uid5 + // 3 uid6 uid7 uid8 uid9 + + // Sanity checks + if (validBits > 80) + { + return STATUS_INVALID; + } + + // Prepare MFRC522 + // ValuesAfterColl=1 => Bits received after collision are cleared. + PCD_ClrRegisterBits(CollReg, 0x80); + + // Repeat Cascade Level loop until we have a complete UID. + uidComplete = false; + while ( ! uidComplete) + { + // Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2. + switch (cascadeLevel) + { + case 1: + buffer[0] = PICC_CMD_SEL_CL1; + uidIndex = 0; + useCascadeTag = validBits && (uid->size > 4); // When we know that the UID has more than 4 bytes + break; + + case 2: + buffer[0] = PICC_CMD_SEL_CL2; + uidIndex = 3; + useCascadeTag = validBits && (uid->size > 7); // When we know that the UID has more than 7 bytes + break; + + case 3: + buffer[0] = PICC_CMD_SEL_CL3; + uidIndex = 6; + useCascadeTag = false; // Never used in CL3. + break; + + default: + return STATUS_INTERNAL_ERROR; + //break; + } + + // How many UID bits are known in this Cascade Level? + if(validBits > (8 * uidIndex)) + { + currentLevelKnownBits = validBits - (8 * uidIndex); + } + else + { + currentLevelKnownBits = 0; + } + + // Copy the known bits from uid->uidByte[] to buffer[] + index = 2; // destination index in buffer[] + if (useCascadeTag) + { + buffer[index++] = PICC_CMD_CT; + } + + uint8_t bytesToCopy = currentLevelKnownBits / 8 + (currentLevelKnownBits % 8 ? 1 : 0); // The number of bytes needed to represent the known bits for this level. + if (bytesToCopy) + { + // Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag + uint8_t maxBytes = useCascadeTag ? 3 : 4; + if (bytesToCopy > maxBytes) + { + bytesToCopy = maxBytes; + } + + for (count = 0; count < bytesToCopy; count++) + { + buffer[index++] = uid->uidByte[uidIndex + count]; + } + } + + // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits + if (useCascadeTag) + { + currentLevelKnownBits += 8; + } + + // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. + selectDone = false; + while ( ! selectDone) + { + // Find out how many bits and bytes to send and receive. + if (currentLevelKnownBits >= 32) + { // All UID bits in this Cascade Level are known. This is a SELECT. + //Serial.print("SELECT: currentLevelKnownBits="); Serial.println(currentLevelKnownBits, DEC); + buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole bytes + + // Calulate BCC - Block Check Character + buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; + + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 7, &buffer[7]); + if (result != STATUS_OK) + { + return result; + } + + txLastBits = 0; // 0 => All 8 bits are valid. + bufferUsed = 9; + + // Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx) + responseBuffer = &buffer[6]; + responseLength = 3; + } + else + { // This is an ANTICOLLISION. + //Serial.print("ANTICOLLISION: currentLevelKnownBits="); Serial.println(currentLevelKnownBits, DEC); + txLastBits = currentLevelKnownBits % 8; + count = currentLevelKnownBits / 8; // Number of whole bytes in the UID part. + index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs + buffer[1] = (index << 4) + txLastBits; // NVB - Number of Valid Bits + bufferUsed = index + (txLastBits ? 1 : 0); + + // Store response in the unused part of buffer + responseBuffer = &buffer[index]; + responseLength = sizeof(buffer) - index; + } + + // Set bit adjustments + rxAlign = txLastBits; // Having a seperate variable is overkill. But it makes the next line easier to read. + PCD_WriteRegister(BitFramingReg, (rxAlign << 4) + txLastBits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + // Transmit the buffer and receive the response. + result = PCD_TransceiveData(buffer, bufferUsed, responseBuffer, &responseLength, &txLastBits, rxAlign, 0); + if (result == STATUS_COLLISION) + { // More than one PICC in the field => collision. + result = PCD_ReadRegister(CollReg); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] + if (result & 0x20) + { // CollPosNotValid + return STATUS_COLLISION; // Without a valid collision position we cannot continue + } + + uint8_t collisionPos = result & 0x1F; // Values 0-31, 0 means bit 32. + if (collisionPos == 0) + { + collisionPos = 32; + } + + if (collisionPos <= currentLevelKnownBits) + { // No progress - should not happen + return STATUS_INTERNAL_ERROR; + } + + // Choose the PICC with the bit set. + currentLevelKnownBits = collisionPos; + count = (currentLevelKnownBits - 1) % 8; // The bit to modify + index = 1 + (currentLevelKnownBits / 8) + (count ? 1 : 0); // First byte is index 0. + buffer[index] |= (1 << count); + } + else if (result != STATUS_OK) + { + return result; + } + else + { // STATUS_OK + if (currentLevelKnownBits >= 32) + { // This was a SELECT. + selectDone = true; // No more anticollision + // We continue below outside the while. + } + else + { // This was an ANTICOLLISION. + // We now have all 32 bits of the UID in this Cascade Level + currentLevelKnownBits = 32; + // Run loop again to do the SELECT. + } + } + } // End of while ( ! selectDone) + + // We do not check the CBB - it was constructed by us above. + + // Copy the found UID bytes from buffer[] to uid->uidByte[] + index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] + bytesToCopy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; + for (count = 0; count < bytesToCopy; count++) + { + uid->uidByte[uidIndex + count] = buffer[index++]; + } + + // Check response SAK (Select Acknowledge) + if (responseLength != 3 || txLastBits != 0) + { // SAK must be exactly 24 bits (1 byte + CRC_A). + //return STATUS_ERROR; + return 203; + } + + // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore. + result = PCD_CalculateCRC(responseBuffer, 1, &buffer[2]); + if (result != STATUS_OK) + { + return result; + } + + if ((buffer[2] != responseBuffer[1]) || (buffer[3] != responseBuffer[2])) + { + return STATUS_CRC_WRONG; + } + + if (responseBuffer[0] & 0x04) + { // Cascade bit set - UID not complete yes + cascadeLevel++; + } + else + { + uidComplete = true; + uid->sak = responseBuffer[0]; + } + } // End of while ( ! uidComplete) + + // Set correct uid->size + uid->size = 3 * cascadeLevel + 1; + + return STATUS_OK; +} // End PICC_Select() + +/* + * Instructs a PICC in state ACTIVE(*) to go to state HALT. + */ +uint8_t ICACHE_FLASH_ATTR PICC_HaltA() +{ + uint8_t result; + uint8_t buffer[4]; + + // Build command buffer + buffer[0] = PICC_CMD_HLTA; + buffer[1] = 0; + + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 2, &buffer[2]); + if (result == STATUS_OK) + { + // Send the command. + // The standard says: + // If the PICC responds with any modulation during a period of 1 ms after the end of the frame containing the + // HLTA command, this response shall be interpreted as 'not acknowledge'. + // We interpret that this way: Only STATUS_TIMEOUT is an success. + result = PCD_TransceiveData(buffer, sizeof(buffer), NULL, 0,0,0,0); + if (result == STATUS_TIMEOUT) + { + result = STATUS_OK; + } + else if (result == STATUS_OK) + { // That is ironically NOT ok in this case ;-) + //result = STATUS_ERROR; + result = 204; + } + } + + return result; +} // End PICC_HaltA() + + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for communicating with MIFARE PICCs +///////////////////////////////////////////////////////////////////////////////////// + +/* + * Executes the MFRC522 MFAuthent command. + */ +/* +uint8_t PCD_Authenticate(uint8_t command, uint8_t blockAddr, MIFARE_Key *key, Uid *uid) +{ + uint8_t i, waitIRq = 0x10; // IdleIRq + + // Build command buffer + uint8_t sendData[12]; + sendData[0] = command; + sendData[1] = blockAddr; + + for (i = 0; i < MF_KEY_SIZE; i++) + { // 6 key bytes + sendData[2+i] = key->keyByte[i]; + } + + for (i = 0; i < 4; i++) + { // The first 4 bytes of the UID + sendData[8+i] = uid->uidByte[i]; + } + + // Start the authentication. + return PCD_CommunicateWithPICC(PCD_MFAuthent, waitIRq, &sendData[0], sizeof(sendData),0,0,0,0,0); +} // End PCD_Authenticate() +*/ + +/* + * Used to exit the PCD from its authenticated state. + * Remember to call this function after communicating with an authenticated PICC - otherwise no new communications can start. + */ +void PCD_StopCrypto1() +{ + // Clear MFCrypto1On bit + PCD_ClrRegisterBits(Status2Reg, 0x08); // Status2Reg[7..0] bits are: TempSensClear I2CForceHS reserved reserved MFCrypto1On ModemState[2:0] +} // End PCD_StopCrypto1() + +/* + * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. + */ +uint8_t ICACHE_FLASH_ATTR MIFARE_Read(uint8_t blockAddr, uint8_t *buffer, uint8_t *bufferSize) +{ + uint8_t result = STATUS_NO_ROOM; + + // Sanity check + if ((buffer == NULL) || (*bufferSize < 18)) + { + return result; + } + + // Build command buffer + buffer[0] = PICC_CMD_MF_READ; + buffer[1] = blockAddr; + + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 2, &buffer[2]); + if (result != STATUS_OK) + { + return result; + } + + // Transmit the buffer and receive the response, validate CRC_A. + return PCD_TransceiveData(buffer, 4, buffer, bufferSize, NULL, 0, true); +} // End MIFARE_Read() + +/* + * Writes 16 bytes to the active PICC. + */ +/* +uint8_t MIFARE_Write(uint8_t blockAddr, uint8_t *buffer, uint8_t bufferSize) +{ + uint8_t result; + + // Sanity check + if (buffer == NULL || bufferSize < 16) + { + return STATUS_INVALID; + } + + // Mifare Classic protocol requires two communications to perform a write. + // Step 1: Tell the PICC we want to write to block blockAddr. + uint8_t cmdBuffer[2]; + cmdBuffer[0] = PICC_CMD_MF_WRITE; + cmdBuffer[1] = blockAddr; + // Adds CRC_A and checks that the response is MF_ACK. + result = PCD_MIFARE_Transceive(cmdBuffer, 2, 0); + if (result != STATUS_OK) + { + return result; + } + + // Step 2: Transfer the data + // Adds CRC_A and checks that the response is MF_ACK. + result = PCD_MIFARE_Transceive(buffer, bufferSize, 0); + if (result != STATUS_OK) + { + return result; + } + + return STATUS_OK; +} // End MIFARE_Write() +*/ + +/* + * Writes a 4 byte page to the active MIFARE Ultralight PICC. + */ +/* +uint8_t MIFARE_UltralightWrite(uint8_t page, uint8_t *buffer, uint8_t bufferSize) +{ + uint8_t result; + + // Sanity check + if (buffer == NULL || bufferSize < 4) + { + return STATUS_INVALID; + } + + // Build commmand buffer + uint8_t cmdBuffer[6]; + cmdBuffer[0] = PICC_CMD_UL_WRITE; + cmdBuffer[1] = page; + memcpy(&cmdBuffer[2], buffer, 4); + + // Perform the write + result = PCD_MIFARE_Transceive(cmdBuffer, 6, 0); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) + { + return result; + } + + return STATUS_OK; +} // End MIFARE_Ultralight_Write() +*/ + +/* + * MIFARE Decrement subtracts the delta from the value of the addressed block, and stores the result in a volatile memory. + */ +uint8_t ICACHE_FLASH_ATTR MIFARE_Decrement(uint8_t blockAddr, uint32_t delta) +{ + return MIFARE_TwoStepHelper(PICC_CMD_MF_DECREMENT, blockAddr, delta); +} // End MIFARE_Decrement() + +/* + * MIFARE Increment adds the delta to the value of the addressed block, and stores the result in a volatile memory. + */ +uint8_t ICACHE_FLASH_ATTR MIFARE_Increment(uint8_t blockAddr, uint32_t delta) +{ + return MIFARE_TwoStepHelper(PICC_CMD_MF_INCREMENT, blockAddr, delta); +} // End MIFARE_Increment() + +/** + * MIFARE Restore copies the value of the addressed block into a volatile memory. + */ +uint8_t ICACHE_FLASH_ATTR MIFARE_Restore(uint8_t blockAddr) +{ + // The datasheet describes Restore as a two step operation, but does not explain what data to transfer in step 2. + // Doing only a single step does not work, so I chose to transfer 0L in step two. + return MIFARE_TwoStepHelper(PICC_CMD_MF_RESTORE, blockAddr, 0L); +} // End MIFARE_Restore() + +/* + * Helper function for the two-step MIFARE Classic protocol operations Decrement, Increment and Restore. + */ +uint8_t ICACHE_FLASH_ATTR MIFARE_TwoStepHelper(uint8_t command, uint8_t blockAddr, uint32_t data) +{ + uint8_t result; + uint8_t cmdBuffer[2]; // We only need room for 2 bytes. + + // Step 1: Tell the PICC the command and block address + cmdBuffer[0] = command; + cmdBuffer[1] = blockAddr; + + // Adds CRC_A and checks that the response is MF_ACK. + result = PCD_MIFARE_Transceive(cmdBuffer, 2, 0); + if (result != STATUS_OK) + { + return result; + } + + // Step 2: Transfer the data + // Adds CRC_A and accept timeout as success. + result = PCD_MIFARE_Transceive((uint8_t *) &data, 4, true); + if (result != STATUS_OK) + { + return result; + } + + return STATUS_OK; +} // End MIFARE_TwoStepHelper() + +/* + * MIFARE Transfer writes the value stored in the volatile memory into one MIFARE Classic block. + */ +uint8_t ICACHE_FLASH_ATTR MIFARE_Transfer(uint8_t blockAddr) +{ + uint8_t cmdBuffer[2]; // We only need room for 2 bytes. + + // Tell the PICC we want to transfer the result into block blockAddr. + cmdBuffer[0] = PICC_CMD_MF_TRANSFER; + cmdBuffer[1] = blockAddr; + + // Adds CRC_A and checks that the response is MF_ACK. + return PCD_MIFARE_Transceive(cmdBuffer, 2, 0); +} // End MIFARE_Transfer() + + +///////////////////////////////////////////////////////////////////////////////////// +// Support functions +///////////////////////////////////////////////////////////////////////////////////// + +/* + * Wrapper for MIFARE protocol communication. + * Adds CRC_A, executes the Transceive command and checks that the response is MF_ACK or a timeout. + */ +uint8_t ICACHE_FLASH_ATTR PCD_MIFARE_Transceive(uint8_t *sendData, uint8_t sendLen, bool acceptTimeout) +{ + uint8_t result; + uint8_t cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. + + // Sanity check + if (sendData == NULL || sendLen > 16) + { + return STATUS_INVALID; + } + + // Copy sendData[] to cmdBuffer[] and add CRC_A + memcpy(cmdBuffer, sendData, sendLen); + result = PCD_CalculateCRC(cmdBuffer, sendLen, &cmdBuffer[sendLen]); + if (result != STATUS_OK) + { + return result; + } + + sendLen += 2; + + // Transceive the data, store the reply in cmdBuffer[] + uint8_t waitIRq = 0x30; // RxIRq and IdleIRq + uint8_t cmdBufferSize = sizeof(cmdBuffer); + uint8_t validBits = 0; + result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, sendLen, cmdBuffer, &cmdBufferSize, &validBits, 0, 0); + if (acceptTimeout && result == STATUS_TIMEOUT) + { + return STATUS_OK; + } + + if (result != STATUS_OK) + { + return result; + } + + // The PICC must reply with a 4 bit ACK + if (cmdBufferSize != 1 || validBits != 4) + { + //return STATUS_ERROR; + return 205; + } + + if (cmdBuffer[0] != MF_ACK) + { + return STATUS_MIFARE_NACK; + } + + return STATUS_OK; +} // End PCD_MIFARE_Transceive() + + +/* + * Translates the SAK (Select Acknowledge) to a PICC type. + */ +uint8_t ICACHE_FLASH_ATTR PICC_GetType(uint8_t sak) +{ + uint8_t retType = PICC_TYPE_UNKNOWN; + + if (sak & 0x04) + { // UID not complete + retType = PICC_TYPE_NOT_COMPLETE; + } + else + { + switch (sak) + { + case 0x09: retType = PICC_TYPE_MIFARE_MINI; break; + case 0x08: retType = PICC_TYPE_MIFARE_1K; break; + case 0x18: retType = PICC_TYPE_MIFARE_4K; break; + case 0x00: retType = PICC_TYPE_MIFARE_UL; break; + case 0x10: + case 0x11: retType = PICC_TYPE_MIFARE_PLUS; break; + case 0x01: retType = PICC_TYPE_TNP3XXX; break; + default: + if (sak & 0x20) + { + retType = PICC_TYPE_ISO_14443_4; + } + else if (sak & 0x40) + { + retType = PICC_TYPE_ISO_18092; + } + break; + } + } + + return (retType); +} // End PICC_GetType() + +/* + * Returns a string pointer to the PICC type name. + */ +char* ICACHE_FLASH_ATTR PICC_GetTypeName(uint8_t piccType) +{ + if(piccType == PICC_TYPE_NOT_COMPLETE) + { + piccType = MFRC522_MaxPICCs - 1; + } + + return((char *) _TypeNamePICC[piccType]); +} // End PICC_GetTypeName() + +/* + * Returns a string pointer to a status code name. + */ +char* ICACHE_FLASH_ATTR GetStatusCodeName(uint8_t code) +{ + return((char *) _ErrorMessage[code]); +} // End GetStatusCodeName() + +/* + * Calculates the bit pattern needed for the specified access bits. In the [C1 C2 C3] tupples C1 is MSB (=4) and C3 is LSB (=1). + */ +void ICACHE_FLASH_ATTR MIFARE_SetAccessBits(uint8_t *accessBitBuffer, + uint8_t g0, + uint8_t g1, + uint8_t g2, + uint8_t g3) +{ + uint8_t c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); + uint8_t c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); + uint8_t c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); + + accessBitBuffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF); + accessBitBuffer[1] = c1 << 4 | (~c3 & 0xF); + accessBitBuffer[2] = c3 << 4 | c2; +} // End MIFARE_SetAccessBits() + +///////////////////////////////////////////////////////////////////////////////////// +// Convenience functions - does not add extra functionality +///////////////////////////////////////////////////////////////////////////////////// + +/* + * Returns true if a PICC responds to PICC_CMD_REQA. + * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. + */ +bool ICACHE_FLASH_ATTR PICC_IsNewCardPresent(void) +{ + uint8_t bufferATQA[2]; + uint8_t bufferSize = sizeof(bufferATQA); + uint8_t result = PICC_RequestA(bufferATQA, &bufferSize); + //return ((result == STATUS_OK) || (result == STATUS_COLLISION)); + return result; +} // End PICC_IsNewCardPresent() + +/* + * Simple wrapper around PICC_Select. + */ +bool ICACHE_FLASH_ATTR PICC_ReadCardSerial(void) +{ + uint8_t result = PICC_Select(&uid, 0); + return (result == STATUS_OK); +} // End PICC_ReadCardSerial() diff --git a/user/sha1.c b/user/sha1.c new file mode 100644 index 0000000..444a19d --- /dev/null +++ b/user/sha1.c @@ -0,0 +1,158 @@ +/* + * sha1.c + * + * Originally witten by Steve Reid + * + * Modified by Aaron D. Gifford + * + * NO COPYRIGHT - THIS IS 100% IN THE PUBLIC DOMAIN + * + * The original unmodified version is available at: + * ftp://ftp.funet.fi/pub/crypt/hash/sha/sha1.c + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "sha.h" +#include +#include "espmissingincludes.h" + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ + +#ifdef LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&(sha1_quadbyte)0xFF00FF00) \ + |(rol(block->l[i],8)&(sha1_quadbyte)0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif + +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +typedef union _BYTE64QUAD16 { + sha1_byte c[64]; + sha1_quadbyte l[16]; +} BYTE64QUAD16; + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +void ICACHE_FLASH_ATTR SHA1_Transform(sha1_quadbyte state[5], sha1_byte buffer[64]) { + sha1_quadbyte a, b, c, d, e; + BYTE64QUAD16 *block; + + block = (BYTE64QUAD16*)buffer; + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1_Init - Initialize new context */ +void ICACHE_FLASH_ATTR SHA1_Init(SHA_CTX* context) { + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ +void ICACHE_FLASH_ATTR SHA1_Update(SHA_CTX *context, sha1_byte *data, unsigned int len) { + unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1_Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1_Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ +void ICACHE_FLASH_ATTR SHA1_Final(sha1_byte digest[SHA1_DIGEST_LENGTH], SHA_CTX *context) { + sha1_quadbyte i, j; + sha1_byte finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (sha1_byte)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1_Update(context, (sha1_byte *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1_Update(context, (sha1_byte *)"\0", 1); + } + /* Should cause a SHA1_Transform() */ + SHA1_Update(context, finalcount, 8); + for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { + digest[i] = (sha1_byte) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, SHA1_BLOCK_LENGTH); + memset(context->state, 0, SHA1_DIGEST_LENGTH); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +} + diff --git a/user/spi.c b/user/spi.c new file mode 100644 index 0000000..d4529e3 --- /dev/null +++ b/user/spi.c @@ -0,0 +1,132 @@ +#include "ets_sys.h" +#include "espmissingincludes.h" + +#define PERIPHS_IO_MUX_PULLDWN BIT6 +#define PIN_PULLDWN_DIS(PIN_NAME) CLEAR_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLDWN) +#include "spi.h" +#include "espmissingincludes.h" + + +void ICACHE_FLASH_ATTR spi_init() +{ + //miso to input + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDI_U); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 0x1 << MISO); + + //mosi to input + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 0x1 << MOSI); + + //clock to output + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTMS_U); + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 0x1 << CLK); + + // SS pin to output + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U); + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 0x1 << SS); + GPIO_OUTPUT_SET(SS, 1); +} + +uint8_t ICACHE_FLASH_ATTR platform_spi_send_recv(int spi_periph, uint8_t val, bool close) +{ + uint8_t i; + uint8_t polarity = 0; + uint8_t phase = 0; + uint8_t buffer = 0; + uint8_t mask = 0x80; + + // slave select low + GPIO_OUTPUT_SET(SS, 0); + + // clock + GPIO_OUTPUT_SET(CLK, polarity); + + //mosi to output + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U); + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 0x1 << MOSI); + + if (phase == 0) + { + if(val & mask) + { + GPIO_OUTPUT_SET(MOSI, 1); + } + else + { + GPIO_OUTPUT_SET(MOSI, 0); + } + + val <<= 1; + } + + os_delay_us(2); + + for (i = 0; i < 8; ++i) + { + GPIO_OUTPUT_SET(CLK, !polarity); + + if(phase == 1) + { + if(val & mask) + { + GPIO_OUTPUT_SET(MOSI, 1); + } + else + { + GPIO_OUTPUT_SET(MOSI, 0); + } + + val <<= 1; + } + else + { + buffer |= GPIO_INPUT_GET(MISO); + if (i != 7) + { + buffer <<= 1; + } + } + os_delay_us(2); + + GPIO_OUTPUT_SET(CLK, polarity); + + if(phase == 0) + { + if(val & mask) + { + GPIO_OUTPUT_SET(MOSI, 1); + } + else + { + GPIO_OUTPUT_SET(MOSI, 0); + } + val <<= 1; + } + else + { + buffer |= GPIO_INPUT_GET(MISO); + if (i != 7) + { + buffer <<= 1; + } + } + os_delay_us(2); + } + + if(close) + { + //mosi to input + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 0x1 << MOSI); + + GPIO_OUTPUT_SET(SS, 1); + } + + return buffer; +} diff --git a/user/stdout.c b/user/stdout.c new file mode 100644 index 0000000..04473cc --- /dev/null +++ b/user/stdout.c @@ -0,0 +1,50 @@ +//Stupid bit of code that does the bare minimum to make os_printf work. + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + + +#include "espmissingincludes.h" +#include "ets_sys.h" +#include "osapi.h" +#include "uart_hw.h" + +static void ICACHE_FLASH_ATTR stdoutUartTxd(char c) { + //Wait until there is room in the FIFO + while (((READ_PERI_REG(UART_STATUS(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ; + //Send the character + WRITE_PERI_REG(UART_FIFO(0), c); +} + +static void ICACHE_FLASH_ATTR stdoutPutchar(char c) { + //convert \n -> \r\n + if (c=='\n') stdoutUartTxd('\r'); + stdoutUartTxd(c); +} + + +void stdoutInit() { + //Enable TxD pin + PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD); + + //Set baud rate and other serial parameters to 115200,n,8,1 + uart_div_modify(0, UART_CLK_FREQ/BIT_RATE_115200); + WRITE_PERI_REG(UART_CONF0(0), (STICK_PARITY_DIS)|(ONE_STOP_BIT << UART_STOP_BIT_NUM_S)| \ + (EIGHT_BITS << UART_BIT_NUM_S)); + + //Reset tx & rx fifo + SET_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST|UART_TXFIFO_RST); + CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST|UART_TXFIFO_RST); + //Clear pending interrupts + WRITE_PERI_REG(UART_INT_CLR(0), 0xffff); + + //Install our own putchar handler + os_install_putc1((void *)stdoutPutchar); +} diff --git a/user/stdout.h b/user/stdout.h new file mode 100644 index 0000000..589da44 --- /dev/null +++ b/user/stdout.h @@ -0,0 +1 @@ +void stdoutInit(); diff --git a/user/user_main.c b/user/user_main.c new file mode 100644 index 0000000..adf653c --- /dev/null +++ b/user/user_main.c @@ -0,0 +1,321 @@ +#include +#include +#include "osapi.h" + +#include "espfs.h" + +#include "stdout.h" +#include "gpio16.h" + +#include "mfrc522.h" +#include "httpclient.h" +#include "jsmn.h" +#include "hmac_sha1.h" + +#define PERIPHS_IO_MUX_PULLDWN BIT6 +#define PIN_PULLDWN_DIS(PIN_NAME) CLEAR_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLDWN) +#define sleepms(x) os_delay_us(x*1000); + +#define RFID_LOOP_DELAY 200 +#define LAST_TAG_TIME_THRESHOLD 5 // 5 seconds + +//0 is lowest priority +#define procTaskPrio 0 +#define procTaskQueueLen 1 + +static os_timer_t rfid_timer; +static bool did_sync_once = false; + +static uint32_t rtc_delta_secs = 0; +static char last_tag [64]; +static uint32_t last_tag_time = 0; + +static char secret [50]; +static char device_id [30]; + +static bool led_toggle = false; + +uint32_t atoint32(char *instr) +{ + uint32_t retval; + int i; + + retval = 0; + for (; *instr; instr++) { + retval = 10*retval + (*instr - '0'); + } + return retval; +} + +void ICACHE_FLASH_ATTR create_json(char * output, char * dev_id, char * tag_uid, uint32_t counter, char * hash) +{ + os_sprintf(output, "{\"device_id\": \"%s\",\r\n\"tag_uid\": \"%s\",\r\n\"counter\": %ld,\r\n\"hash\": \"%s\"}", dev_id, tag_uid, counter, hash); +} + +void strip_newlines(char * inbuff, int len, char * outbuff) +{ + if( inbuff[len-1] == '\n' ) + { + inbuff[len-1] = 0; + } + strcpy(outbuff, inbuff); +} + +bool is_connected() +{ + int x=wifi_station_get_connect_status(); + if (x != STATION_GOT_IP) { + return false; + } + return true; +} + +void ICACHE_FLASH_ATTR get_serial_secret() +{ + strcpy(secret, flashConfig.nfc_device_secret); + strcpy(device_id, flashConfig.nfc_device_id); +} + +uint32_t get_time_secs() +{ + uint32_t out = (system_get_time() / 1000000) + rtc_delta_secs; + return out; +} + +os_event_t procTaskQueue[procTaskQueueLen]; + +static void ICACHE_FLASH_ATTR +procTask(os_event_t *events) +{ + system_os_post(procTaskPrio, 0, 0 ); + if( events->sig == 0 && events->par == 0 ) + { + //Idle Event. + } +} + +void ICACHE_FLASH_ATTR hex2str(char *out, unsigned char *s, size_t len) +{ + char tmp_str[3]; + memset(out, 0, 40); + memset(tmp_str, 0, 3); + os_sprintf(tmp_str, "%02x", s[0]); + strcpy(out, tmp_str); + int ii; + for(ii=1; iiend] = '\0'; + return js + t->start; +} + +void rfid_http_cb(char * response_body, int http_status, char * full_response) +{ + os_printf("got http response status %d \r\nbody: '%s'\r\n", http_status, response_body); + if(http_status == 200) + { + // parse the return body with jsmn + jsmn_init(&parser); + int jp = jsmn_parse(&parser, response_body, strlen(response_body), tokens, 30); + + //try to find color r/g/b's + int r, g, b = 0; + int delay = 1000; //milliseconds + for(int i=1; i<30; i++) + { + // red + if(strcmp(json_token_tostr(response_body, &tokens[i]), "r") == 0) + { + r = atoi(json_token_tostr(response_body, &tokens[i+1])); + } + + // green + if(strcmp(json_token_tostr(response_body, &tokens[i]), "g") == 0) + { + g = atoi(json_token_tostr(response_body, &tokens[i+1])); + } + + // blue + if(strcmp(json_token_tostr(response_body, &tokens[i]), "b") == 0) + { + b = atoi(json_token_tostr(response_body, &tokens[i+1])); + } + + // delay + if(strcmp(json_token_tostr(response_body, &tokens[i]), "delay") == 0) + { + delay = atoi(json_token_tostr(response_body, &tokens[i+1])); + } + } + + up_count(); + set_led(r, g, b, delay); + } + else + { + set_led(255, 0, 0, 900); + } + gpio16_output_set(1); +} + +void ICACHE_FLASH_ATTR send_tag( char* tag_uid ) +{ + static unsigned char hash[HMAC_SHA1_DIGEST_LENGTH]; + static char tosign[200]; + static char json_send[500]; + static char hashstr[40]; + HMAC_SHA1_CTX c; + + strcpy(tosign, device_id); + strcat(tosign, tag_uid); + uint32_t counter = flashConfig.nfc_counter; + char countstr[80]; + os_sprintf(countstr, "%ld", counter); + strcat(tosign, countstr); + + //HMAC_SHA1_Init(&c); + HMAC_SHA1_Init(&c); + HMAC_SHA1_UpdateKey(&c, secret, 3); + HMAC_SHA1_UpdateKey(&c, &(secret[3]), strlen(secret) - 3); + HMAC_SHA1_EndKey(&c); + HMAC_SHA1_StartMessage(&c); + HMAC_SHA1_UpdateMessage(&c, tosign, 7); + HMAC_SHA1_UpdateMessage(&c, &(tosign[7]), strlen(tosign) - 7); + HMAC_SHA1_EndMessage(&(hash[0]), &c); + HMAC_SHA1_Done(&c); + + hex2str(hashstr, hash, 20); + //os_printf("hashstr with secret %s is %s\n\r", secret, hashstr); + create_json(json_send, device_id, tag_uid, counter, hashstr); + //http_get("http://wtfismyip.com/text", rfid_http_cb); + os_printf("SENDING TAG json: %s \n\r", json_send); + http_post(flashConfig.nfc_url, json_send, rfid_http_cb); + + wait_ms(700); + gpio16_output_set(0); +} + +void ICACHE_FLASH_ATTR rfid_loop() +{ + // Look for new cards + uint8_t status = PICC_IsNewCardPresent(); + os_printf("status %d - %d - %d\r\n",status, system_get_time() / 1000000, get_time_secs()); + if ( ! (status == STATUS_OK || status == STATUS_COLLISION)) + { + //wait_ms(500); + //continue; + return; + } + + // Select one of the cards + if ( ! PICC_ReadCardSerial()) + { + //wait_ms(500); + //continue; + return; + } + + wait_ms(100); + + // short little dance when got a tag + set_led(233, 90, 99, 150); + + // Print Card UID + os_printf("Card UID: "); + char tag_uid [64]; + char tag_tmp [4]; + strcpy(tag_uid, ""); + for (uint8_t i = 0; i < uid.size; i++) + { + //os_printf("%02X", uid.uidByte[i]); + os_sprintf(tag_tmp, "%02x", uid.uidByte[i]); + strcat(tag_uid, tag_tmp); + } + os_printf("%s \n\r", tag_uid); + // Print Card type + uint8_t piccType = PICC_GetType(uid.sak); + os_printf("PICC Type: %s \n\r", PICC_GetTypeName(piccType)); + + // use the internal clock for this because time is synced on every request + // the compensation for drift is often enough to throw off this short re-try period and + // send the tag up too often + uint32_t time_tmp = system_get_time() / 1000000; + if(strcmp(last_tag, tag_uid) != 0 || time_tmp - last_tag_time > LAST_TAG_TIME_THRESHOLD ) + { + send_tag(tag_uid); + strcpy(last_tag, tag_uid); + last_tag_time = system_get_time() / 1000000; + //wait_ms(500); + } +} + +//Timer event. +static void ICACHE_FLASH_ATTR rfidTimer(void *arg) +{ + //os_printf("doing rfid loop\r\n"); + rfid_loop(); +} + +// initialize the custom stuff that goes beyond esp-link +void app_init() { + stdoutInit(); + get_serial_secret(); + + // init status LED + gpio16_output_conf(); + + os_printf("\nReady\n"); + os_printf("\nGOING TO TRY RFID\n"); + PCD_Init(); + + // purple startup + set_led(155, 0, 155, 0); + sleepms(100); + set_led(0, 0, 0, 0); + sleepms(100); + set_led(155, 0, 155, 0); + sleepms(100); + set_led(0, 0, 0, 0); + sleepms(100); + set_led(155, 0, 155, 0); + sleepms(100); + set_led(0, 0, 0, 0); + sleepms(100); + set_led(155, 0, 155, 0); + sleepms(100); + set_led(0, 0, 0, 0); + + //Add a process + system_os_task(procTask, procTaskPrio, procTaskQueue, procTaskQueueLen); + + //RFID Timer + os_timer_disarm(&rfid_timer); + os_timer_setfn(&rfid_timer, (os_timer_func_t *)rfidTimer, NULL); + os_timer_arm(&rfid_timer, RFID_LOOP_DELAY, 1); + + system_os_post(procTaskPrio, 0, 0 ); + + //wifi_set_sleep_type(LIGHT_SLEEP_T); + +} diff --git a/user/ws2812.c b/user/ws2812.c new file mode 100644 index 0000000..130ffb1 --- /dev/null +++ b/user/ws2812.c @@ -0,0 +1,68 @@ +#include "ets_sys.h" +#include "osapi.h" +#include "c_types.h" +#include "ws2812.h" + +void __attribute__((optimize("O2"))) send_ws_0(uint8_t gpio){ + uint8_t i; + i = 4; while (i--) GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << gpio); + i = 9; while (i--) GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << gpio); +} +void __attribute__((optimize("O2"))) send_ws_1(uint8_t gpio){ + uint8_t i; + i = 8; while (i--) GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << gpio); + i = 6; while (i--) GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << gpio); +} + +void WS2812OutBuffer( uint8_t * buffer, uint16_t length) +{ + PIN_FUNC_SELECT(IO_MUX, IO_FUNC); + PIN_PULLUP_EN(IO_MUX); + GPIO_REG_WRITE(GPIO_ENABLE_W1TS_ADDRESS, (1 << WSGPIO)); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (1 << WSGPIO)); + + // Ignore incomplete Byte triples at the end of buffer: + length -= length % 3; + + // Do not remove these: + os_delay_us(1); + os_delay_us(1); + + uint16_t i; +// GPIO_OUTPUT_SET(GPIO_ID_PIN(WSGPIO), 0); + for( i = 0; i < length; i++ ) + { + system_soft_wdt_feed(); + + uint8_t byte = buffer[i]; + if( byte & 0x80 ) send_ws_1(WSGPIO); else send_ws_0(WSGPIO); + if( byte & 0x40 ) send_ws_1(WSGPIO); else send_ws_0(WSGPIO); + if( byte & 0x20 ) send_ws_1(WSGPIO); else send_ws_0(WSGPIO); + if( byte & 0x10 ) send_ws_1(WSGPIO); else send_ws_0(WSGPIO); + if( byte & 0x08 ) send_ws_1(WSGPIO); else send_ws_0(WSGPIO); + if( byte & 0x04 ) send_ws_1(WSGPIO); else send_ws_0(WSGPIO); + if( byte & 0x02 ) send_ws_1(WSGPIO); else send_ws_0(WSGPIO); + if( byte & 0x01 ) send_ws_1(WSGPIO); else send_ws_0(WSGPIO); + } +} + +void led_reset(void *arg) +{ + memset(outbuffer, 0, LEDS*3); + WS2812OutBuffer(outbuffer, LEDS*3); +} + +void set_led(int r, int g, int b, long delay) +{ + memset(outbuffer, 0, LEDS*3); + outbuffer[0] = g; // green + outbuffer[1] = r; // red + outbuffer[2] = b; // blue + WS2812OutBuffer(outbuffer, LEDS*3); + if(delay > 0) + { + os_timer_disarm(&led_timer); + os_timer_setfn(&led_timer, (os_timer_func_t *)led_reset, NULL); + os_timer_arm(&led_timer, delay, 0); + } +} diff --git a/wiflash b/wiflash new file mode 100755 index 0000000..d8c12ea --- /dev/null +++ b/wiflash @@ -0,0 +1,133 @@ +#! /bin/bash +# +# Flash an esp8266 over wifi. This communicates with the esphttpd's /flash handlers +# and POSTS the correct binary depending on the parittion that needs to be flashed +# next. +# +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# Thorsten von Eicken wrote this file. As long as you retain +# this notice you can do whatever you want with this stuff. If we meet some day, +# and you think this stuff is worth it, you can buy me a beer in return. +# ---------------------------------------------------------------------------- + +show_help() { + cat < with either or +depending on its current state. Reboot the esp8266 after flashing and wait for it to come +up again. + -v Be verbose + -h show this help + +Example: ${0##*/} -v esp8266 firmware/user1.bin firmware/user2.bin + ${0##*/} 192.168.4.1 firmware/user1.bin firmware/user2.bin +EOT +} + +if ! which curl >/dev/null; then + echo "ERROR: Cannot find curl: it is required for this script." >&2 + exit 1 +fi + +start=`date +%s` + +# ===== Parse arguments + +verbose= + +while getopts "hvx:" opt; do + case "$opt" in + h) show_help; exit 0 ;; + v) verbose=1 ;; + x) foo="$OPTARG" ;; + '?') show_help >&2; exit 1 ;; + esac +done + +# Shift off the options and optional --. +shift "$((OPTIND-1))" + +# Get the fixed arguments +if [[ $# != 3 ]]; then + show_help >&2 + exit 1 +fi +hostname=$1 +user1=$2 +user2=$3 + +re='[-A-Za-z0-9.]+' +if [[ ! "$hostname" =~ $re ]]; then + echo "ERROR: hostname ${hostname} is not a valid hostname or ip address" >&2 + exit 1 +fi + +if [[ ! -r "$user1" ]]; then + echo "ERROR: cannot read user1 firmware file ($user1)" >&2 + exit 1 +fi + +if [[ ! -r "$user2" ]]; then + echo "ERROR: cannot read user2 firmware file ($user2)" >&2 + exit 1 +fi + +# ===== Retrieve the 'next' firmware required + +fw= +while true; do + [[ -n "$verbose" ]] && echo "Fetching http://$hostname/flash/next" >&2 + v=; [[ -n "$verbose" ]] && v=-v + next=`curl -m 10 $v -s "http://$hostname/flash/next"` + if [[ $? != 0 ]]; then + echo "Error retrieving http://$hostname/flash/next" >&2 + exit 1 + fi + case "$next" in + user1.bin) + echo "Flashing user1.bin" >&2 + fw="$user1" + break;; + user2.bin) + echo "Flashing user2.bin" >&2 + fw="$user2" + break;; + *) + echo "Error retrieving or parsing http://$hostname/flash/next" >&2 + exit 1 + ;; + esac +done + +#silent=-s +[[ -n "$verbose" ]] && silent= +res=`curl $silent -XPOST --data-binary "@$fw" "http://$hostname/flash/upload"` +if [[ $? != 0 ]]; then + echo "Error flashing $fw" >&2 + exit 1 +fi + +sleep 2 +echo "Rebooting into new firmware" >&2 +curl -m 10 -s "http://$hostname/flash/reboot" + +sleep 2 +echo "Waiting for ESP8266 to come back" +while true; do + [[ -n "$verbose" ]] && echo "Fetching http://$hostname/flash/next" >&2 + next2=`curl -m 10 $v -s "http://$hostname/flash/next"` + [[ -n "$verbose" ]] && echo "got: $next2" + re='user[12]\.bin' + if [[ "$next2" =~ $re ]]; then + if [[ "$next2" != "$next" ]]; then + sec=$(( `date +%s` - $start )) + echo "Success, took $sec seconds" >&2 + exit 0 + else + echo "Flashing seems to have failed and it reverted to the old firmware?" >&2 + exit 1 + fi + fi + sleep 1 +done