Browse Source

rebirthing project

psy 5 years ago
commit
a33012f7ba
100 changed files with 25705 additions and 0 deletions
  1. 30 0
      README.md
  2. 4 0
      meta-id/.directory
  3. 24 0
      meta-id/.gitignore
  4. 36 0
      meta-id/.travis.yml
  5. 213 0
      meta-id/BOARDS.md
  6. 73 0
      meta-id/BUILDING.md
  7. 29 0
      meta-id/CONTRIBUTING.md
  8. 78 0
      meta-id/FLASH.md
  9. 204 0
      meta-id/FLASHING.md
  10. 123 0
      meta-id/LICENSE.txt
  11. 545 0
      meta-id/Makefile
  12. 14 0
      meta-id/RESTMQTT.md
  13. 36 0
      meta-id/TROUBLESHOOTING.md
  14. 183 0
      meta-id/UC-FLASHING.md
  15. 69 0
      meta-id/WEB-SERVER.md
  16. 87 0
      meta-id/WIFI-CONFIG.md
  17. 14 0
      meta-id/WINDOWS.md
  18. 110 0
      meta-id/avrflash
  19. 194 0
      meta-id/cmd/cmd.c
  20. 124 0
      meta-id/cmd/cmd.h
  21. 407 0
      meta-id/cmd/handlers.c
  22. 4 0
      meta-id/envvars.template
  23. 293 0
      meta-id/espfs/espfs.c
  24. 42 0
      meta-id/espfs/espfs.h
  25. 33 0
      meta-id/espfs/espfsformat.h
  26. 56 0
      meta-id/espfs/mkespfsimage/Makefile
  27. 310 0
      meta-id/espfs/mkespfsimage/main.c
  28. 48 0
      meta-id/espfs/mkespfsimage/mman-win32/Makefile
  29. 11 0
      meta-id/espfs/mkespfsimage/mman-win32/config.mak
  30. 157 0
      meta-id/espfs/mkespfsimage/mman-win32/configure
  31. 180 0
      meta-id/espfs/mkespfsimage/mman-win32/mman.c
  32. 55 0
      meta-id/espfs/mkespfsimage/mman-win32/mman.h
  33. 235 0
      meta-id/espfs/mkespfsimage/mman-win32/test.c
  34. 7 0
      meta-id/espmake.cmd
  35. 5 0
      meta-id/html/.directory
  36. BIN
      meta-id/html/Lato-Embed.ttf
  37. 275 0
      meta-id/html/META.css
  38. 41 0
      meta-id/html/about.html
  39. BIN
      meta-id/html/attic/Lato-Black.ttf
  40. BIN
      meta-id/html/attic/Lato-BlackItalic.ttf
  41. BIN
      meta-id/html/attic/Lato-Bold.ttf
  42. BIN
      meta-id/html/attic/Lato-BoldItalic.ttf
  43. 15601 0
      meta-id/html/attic/Lato-Embed.sfd
  44. BIN
      meta-id/html/attic/Lato-Hairline.ttf
  45. BIN
      meta-id/html/attic/Lato-HairlineItalic.ttf
  46. BIN
      meta-id/html/attic/Lato-Italic.ttf
  47. BIN
      meta-id/html/attic/Lato-Light.ttf
  48. BIN
      meta-id/html/attic/Lato-LightItalic.ttf
  49. BIN
      meta-id/html/attic/Lato-Regular.ttf
  50. 133 0
      meta-id/html/attic/console.html
  51. 152 0
      meta-id/html/attic/console.js
  52. 43 0
      meta-id/html/attic/flash.html
  53. 33 0
      meta-id/html/attic/flash.js
  54. 161 0
      meta-id/html/attic/home.html
  55. 25 0
      meta-id/html/attic/init_old.html
  56. 60 0
      meta-id/html/attic/log.html
  57. 80 0
      meta-id/html/attic/meta.html
  58. 71 0
      meta-id/html/attic/meta.js
  59. 228 0
      meta-id/html/attic/meta_old.css
  60. 123 0
      meta-id/html/attic/mqtt.html
  61. 87 0
      meta-id/html/attic/mqtt.js
  62. 97 0
      meta-id/html/attic/services.html
  63. 68 0
      meta-id/html/attic/services.js
  64. 484 0
      meta-id/html/attic/ui.js
  65. 21 0
      meta-id/html/attic/user.html
  66. 242 0
      meta-id/html/attic/userpage.js
  67. 59 0
      meta-id/html/attic/web-server.html
  68. 38 0
      meta-id/html/attic/welcome-old.html
  69. BIN
      meta-id/html/favicon.ico
  70. 15 0
      meta-id/html/head-
  71. 43 0
      meta-id/html/id.html
  72. 11 0
      meta-id/html/init.html
  73. BIN
      meta-id/html/jl-400x110.png-
  74. 669 0
      meta-id/html/meta-ui.js
  75. 22 0
      meta-id/html/start.html
  76. 101 0
      meta-id/html/text/t1.html
  77. 66 0
      meta-id/html/text/t2.html
  78. 54 0
      meta-id/html/text/t3.html
  79. 107 0
      meta-id/html/text/t4.html
  80. 73 0
      meta-id/html/text/t5.html
  81. 76 0
      meta-id/html/text/t6.html
  82. 383 0
      meta-id/html/text/t7.html.toobig
  83. 25 0
      meta-id/html/text/t8.html.toobig
  84. 34 0
      meta-id/html/text/text.html
  85. 283 0
      meta-id/html/texts-braman.html
  86. 90 0
      meta-id/html/texts-breke.html
  87. 220 0
      meta-id/html/texts-bullot.html
  88. 57 0
      meta-id/html/texts-daniela.html
  89. 314 0
      meta-id/html/texts-havas.html
  90. 127 0
      meta-id/html/texts-janich.html
  91. 63 0
      meta-id/html/texts-kiesewetter.html
  92. 200 0
      meta-id/html/texts-shah.html
  93. 60 0
      meta-id/html/texts-vega-mazon.html
  94. 75 0
      meta-id/html/texts.html
  95. 32 0
      meta-id/html/welcome.html
  96. BIN
      meta-id/html/wifi.gif
  97. 147 0
      meta-id/html/wifi.html
  98. 118 0
      meta-id/html/wifi/wifiAp.html
  99. 85 0
      meta-id/html/wifi/wifiAp.js
  100. 0 0
      meta-id/html/wifi/wifiSta.html

+ 30 - 0
README.md

@@ -0,0 +1,30 @@
+META-ID: Wifi-Serial Bridge w/REST&MQTT
+========================================
+
+http://www.meta-id.info/
+
+The meta-id firmware connects a micro-controller to the internet using an ESP8266 Wifi module.
+
+it is based on the awesome esp-link firmware from jeelabs : https://github.com/jeelabs/esp-link
+
+THIS PROJECT IS WORK IN PROGRESS
+================================
+
+It implements a number of features:
+
+- transparent bridge between Wifi and serial, useful for debugging or inputting into a uC
+- flash-programming attached Arduino/AVR microcontrollers and
+  LPC800-series and other ARM microcontrollers via Wifi
+- built-in stk500v1 programmer for AVR uC's: program using HTTP upload of hex file
+- outbound REST HTTP requests from the attached micro-controller to the internet
+- MQTT client pub/sub from the attached micro-controller to the internet
+- serve custom web pages containing data that is dynamically pulled from the attached uC and
+  that contain buttons and fields that are transmitted to the attached uC (feature not
+  fully ready yet)
+
+The firmware includes a tiny HTTP server based on
+[esphttpd](http://www.esp8266.com/viewforum.php?f=34)
+with a simple web interface, many thanks to Jeroen Domburg for making it available!
+The REST and MQTT functionality are loosely based on [espduino](https://github.com/tuanpmt/espduino)
+but significantly rewritten and no longer protocol compatible, thanks to tuanpmt for the
+inspiration!

+ 4 - 0
meta-id/.directory

@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,2,18,13,25,15
+Version=3
+ViewMode=1

+ 24 - 0
meta-id/.gitignore

@@ -0,0 +1,24 @@
+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/
+local.conf
+*.tgz
+esp-open-sdk/
+*~
+envvars
+esphttpclient meta.wav dnsd/DNSServer.cpp
+dnsd

+ 36 - 0
meta-id/.travis.yml

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

+ 213 - 0
meta-id/BOARDS.md

@@ -0,0 +1,213 @@
+Boards with meta-id
+====================
+
+This readme provides instructions for PCBs that I've made that are designed for meta-id.
+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 meta-id.
+
+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 meta-id.
+
+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 <ESC> 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 meta-id 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:meta-id:23" (you can use an IP address instead of `meta-id`). 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:meta-id:23 -carduino -b115200 -U flash:w:greenhouse.hex:i
+```
+If you're using "edam's Arduino makefile" then you can simply set `SERIALDEV=net:meta-id: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 meta-id 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 meta-id 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 meta-id
+
+Using esptool.py a meta-id 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
+```

+ 73 - 0
meta-id/BUILDING.md

@@ -0,0 +1,73 @@
+Building meta-id
+=================
+
+Before you build meta-id, consider that you can download ready-made firmware images!
+Just head over to the [release section](https://github.com/jeelabs/meta-id/releases)
+and download the tgz archive.
+
+If you decide to build your own, there are a number of options:
+- On linux x86 download the ready-built toolchain and patched SDK like the automated build does
+  and compile the firmware
+- On linux download and build the toolchain, download and patch the SDK, then compile the firmware
+- On windows install mingw, python, java, and a slew of other tools and then build the
+  firmware
+
+Once you have built the firmware you will want to flash it to your esp8266 module.
+Assuming you already have meta-id running you can either go back to the initial flashing
+via the serial port or you can use the over-the-air (i.e. Wifi) update method, which is faster
+and more reliable (unless you have a non-booting version of meta-id).
+The OTA flashing is described at the end of this page,
+the serial flashing is described in [FLASHING.md](FLASHING.md).
+
+### Automated builds
+
+For every commit on github an automated build is made. This means that every branch, including
+master, and every pull request always has an up-to-date build. These builds are made by Travis
+using the instructions in `.travis.yml`, which basically consist of cloning the meta-id repo,
+downloading the compiler toolchain, downloading the Espressif SDK, and running `make`.
+If you're looking for how to build meta-id the travis instructions will always give you
+accurate pointers to what to download.
+
+### Linux
+
+The firmware has been built using the https://github.com/pfalcon/esp-open-sdk[esp-open-sdk]
+on a Linux system. Create an esp8266 directory, install the esp-open-sdk into a sub-directory
+using the *non-standalone* install (i.e., there should not be an sdk directory in the esp-open-sdk
+dir when done installing, *if you use the standalone install you will get compilation errors*
+with std types, such as `uint32_t`).
+
+Download the Espressif "NONOS" SDK (use the version mentioned in the release notes) from their
+http://bbs.espressif.com/viewforum.php?f=5[download forum] and also expand it into a
+sub-directory. Often there are patches to apply, in that case you need to download the patches
+from the same source and apply them.
+
+You can simplify your life (and avoid the hour-long build time for esp-open-sdk) if you are
+on an x86 box by downloading the packaged and built esp-open-sdk and the fully patches SDKfrom the
+links used in the `.travis.yaml`.
+
+Clone the meta-id repository into a third sub-directory and check out the tag you would like,
+such as `git checkout v2.2.3`.
+This way the relative paths in the Makefile will work.
+If you choose a different directory structure look at the top of the Makefile for the
+appropriate environment variables to define.
+Do not use the source tarballs from the release page on github,
+these will give you trouble compiling because the Makefile uses git to determine the meta-id
+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 meta-id. If you want to se the commands being
+issued, use `VERBOSE=1 make`.
+
+A few notes from others (I can't fully verify these):
+
+- You may need to install `zlib1g-dev` and `python-serial`
+- Make sure you have the correct version of the SDK
+- Make sure the paths at the beginning of the makefile are correct
+- Make sure `esp-open-sdk/xtensa-lx106-elf/bin` is in the PATH set in the Makefile
+
+### Windows
+
+It is possible to build meta-id on Windows, but it requires a 
+[gaggle of software to be installed](WINDOWS.md)

+ 29 - 0
meta-id/CONTRIBUTING.md

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

+ 78 - 0
meta-id/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.
+
+

+ 204 - 0
meta-id/FLASHING.md

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

+ 123 - 0
meta-id/LICENSE.txt

@@ -0,0 +1,123 @@
+"THE BEER-WARE LICENSE" (Revision 42):
+ikujam (ikujam@ikujam.org) adapted this project. 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.
+
+
+----
+This project is based on esp-link (https://github.com/jeelabs/esp-link)
+
+having the following licence:
+
+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 <jeroen@spritesmods.com> 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 <http://www.gnu.org/licenses/>.
+
+---------------------------------------------------------------------
+
+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.
+

+ 545 - 0
meta-id/Makefile

@@ -0,0 +1,545 @@
+#
+# Makefile for meta-id - https://github.com/jeelabs/meta-id
+#
+# Makefile heavily adapted to meta-id and wireless flashing by Thorsten von Eicken
+# Lots of work, in particular to support windows, by brunnels
+# Original from esphttpd and others...
+#
+# 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 meta-id 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 ?=esp_link_test
+# AP_PASS ?=esp_link_test
+# AP_AUTH_MODE ?=4
+# AP_SSID_HIDDEN ?=0
+# AP_MAX_CONN ?=4
+# AP_BEACON_INTERVAL ?=100
+AP_SSID ?=.META_NNNN
+AP_PASS ?=
+AP_AUTH_MODE ?=0
+
+# If CHANGE_TO_STA is set to "yes" the meta-id module will switch to station mode
+# once successfully connected to an access point. Else it will stay in STA+AP mode.
+CHANGE_TO_STA ?= no
+
+# hostname or IP address for wifi flashing
+ESP_HOSTNAME  ?= meta-id
+
+# --------------- toolchain configuration ---------------
+
+# Base directory for the compiler. Needs a / at the end.
+# Typically you'll install https://github.com/pfalcon/esp-open-sdk
+# IMPORTANT: use esp-open-sdk `make STANDALONE=n`: the SDK bundled with esp-open-sdk will *not* work!
+XTENSA_TOOLS_ROOT ?= $(abspath ../esp-open-sdk/xtensa-lx106-elf/bin)/
+
+# Firmware version 
+# WARNING: if you change this expect to make code adjustments elsewhere, don't expect
+# that meta-id will magically work with a different version of the SDK!!!
+SDK_VERS ?= esp_iot_sdk_v2.1.0
+
+# Try to find the firmware manually extracted, e.g. after downloading from Espressif's BBS,
+# http://bbs.espressif.com/viewforum.php?f=46
+# USING THE SDK BUNDLED WITH ESP-OPEN-SDK WILL NOT WORK!!!
+SDK_BASE ?= $(wildcard ../$(SDK_VERS))
+
+# If the firmware isn't there, see whether it got downloaded as part of esp-open-sdk
+# This used to work at some point, but is not supported, uncomment if you feel lucky ;-)
+#ifeq ($(SDK_BASE),)
+#SDK_BASE := $(wildcard $(XTENSA_TOOLS_ROOT)/../../$(SDK_VERS))
+#endif
+
+# Clean up SDK path
+SDK_BASE := $(abspath $(SDK_BASE))
+$(info SDK     is $(SDK_BASE))
+
+# Path to bootloader file
+BOOTFILE	?= $(SDK_BASE/bin/boot_v1.6.bin)
+
+# 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		?= 230400
+
+# --------------- 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
+
+# --------------- meta-id modules config options ---------------
+
+# Optional Modules: mqtt rest socket web-server syslog
+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.
+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/
+TEXT_PATH = $(HTML_PATH)text/
+FONT_PATH = $(HTML_PATH)fonts/
+
+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_INIT_DATA		?= 0x7C000 # where to flash esp_init_data_default.bin
+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_INIT_DATA		?= 0xFC000 # where to flash esp_init_data_default.bin
+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
+:q
+ET_FS               ?= 16m     # 16Mbit flash size in esptool flash command
+ET_FF               ?= 80m     # 80Mhz flash speed in esptool flash command
+ET_INIT_DATA		?= 0x1FC000 # where to flash esp_init_data_default.bin
+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_INIT_DATA		?= 0x3FC000 # where to flash esp_init_data_default.bin
+ET_BLANK            ?= 0x3FE000 # where to flash blank.bin to erase wireless settings
+endif
+
+# --------------- meta-id version        ---------------
+
+# Version-fu :-) This code assumes that a new maj.minor is started using a "vN.M.0" tag on master
+# and that thereafter the desired patchlevel number is just the number of commits since the tag.
+#
+# Get the current branch name if not using travis
+TRAVIS_BRANCH?=$(shell git symbolic-ref --short HEAD --quiet)
+# Use git describe to get the latest version tag, commits since then, sha and dirty flag, this
+# results is something like "v1.2.0-13-ab6cedf-dirty"
+NO_TAG ?= "no-tag"
+VERSION := $(shell (git describe --tags --match 'v*.0' --long --dirty || echo $(NO_TAG)) | sed -re 's/(\.0)?-/./')
+# If not on master then insert the branch name
+ifneq ($(TRAVIS_BRANCH),master)
+ifneq ($(findstring V%,$(TRAVIS_BRANCH)),)
+VERSION := $(shell echo $(VERSION) | sed -e 's/-/-$(TRAVIS_BRANCH)-/')
+endif
+endif
+VERSION :=$(VERSION)
+$(info VERSION is $(VERSION))
+
+# 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
+
+ifneq (,$(findstring web-server,$(MODULES)))
+	CFLAGS		+= -DWEBSERVER
+endif
+
+ifneq (,$(findstring socket,$(MODULES)))
+	CFLAGS		+= -DSOCKET
+endif
+
+# flag to disable espconn_secure functions
+CFLAGS		+= -DNOSSL 
+
+# which modules (subdirectories) of the project to include in compiling
+LIBRARIES_DIR 	= libraries
+MODULES		+= espfs httpd cmd serial meta-id user
+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_536 crypto pwm
+
+# compiler flags using during compilation of source files
+CFLAGS	+= -Os -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
+	-nostdlib -mlongcalls -mtext-section-literals -ffunction-sections -fdata-sections \
+	-D__ets__ -DICACHE_FLASH -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="meta-id $(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
+ELF_SIZE	:= $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-size
+
+####
+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: checkdirs $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin
+
+$(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
+	$(Q) $(ELF_SIZE) -A $(USER1_OUT) |grep -v " 0$$" |grep .
+	$(Q) COMPILE=gcc PATH=$(XTENSA_TOOLS_ROOT):$(PATH) python2 $(APPGEN_TOOL) $(USER1_OUT) 2 $(ESP_FLASH_MODE) $(ESP_FLASH_FREQ_DIV) $(ESP_SPI_SIZE) 0 >/dev/null
+	$(Q) rm -f eagle.app.v6.*.bin
+	$(Q) 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) python2 $(APPGEN_TOOL) $(USER2_OUT) 2 $(ESP_FLASH_MODE) $(ESP_FLASH_FREQ_DIV) $(ESP_SPI_SIZE) 1 >/dev/null
+	$(Q) rm -f eagle.app.v6.*.bin
+	$(Q) 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.6.bin" 0x01000 $(FW_BASE)/user1.bin \
+	  $(ET_BLANK) $(SDK_BASE)/bin/blank.bin
+
+fullflash: all
+	$(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash -fs $(ET_FS) -ff $(ET_FF) \
+	  0x00000 "$(SDK_BASE)/bin/boot_v1.6.bin" 0x01000 $(FW_BASE)/user1.bin \
+	  $(ET_INIT_DATA) $(SDK_BASE)/bin/esp_init_data_default.bin $(ET_BLANK) $(SDK_BASE)/bin/blank.bin
+
+
+
+tools/$(HTML_COMPRESSOR):
+	$(Q) echo "The jar files in the tools dir are missing, they should be in the source repo"
+	$(Q) echo "The following commands can be used to fetch them, but the URLs have changed..."
+	$(Q) echo mkdir -p tools
+	$(Q) echo "cd tools; wget --no-check-certificate https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI_COMPRESSOR) -O $(YUI_COMPRESSOR)"
+	$(Q) echo "cd tools; wget --no-check-certificate https://htmlcompressor.googlecode.com/files/$(HTML_COMPRESSOR) -O $(HTML_COMPRESSOR)"
+
+ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes")
+$(BUILD_BASE)/espfs_img.o: tools/$(HTML_COMPRESSOR)
+endif
+
+$(BUILD_BASE)/espfs_img.o: html/ html/wifi/ html/text/ espfs/mkespfsimage/mkespfsimage
+#	$(Q) rm -rf html_compressed; mkdir html_compressed; mkdir html_compressed/wifi; mkdir html_compressed/text;mkdir html_compressed/fonts;
+	$(Q) rm -rf html_compressed; mkdir html_compressed; mkdir html_compressed/fonts;
+	$(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/*.js html_compressed/wifi;
+	$(Q) cp -r html/Lato-Embed.ttf html_compressed/fonts;
+ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes")
+	$(Q) echo "Compressing 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)/text/ \
+#	  $(TEXT_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 "Compressing 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/text/*.html html_compressed/text;	
+#	$(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/meta-id-$(VERSION)
+	$(Q) egrep -a 'meta-id [a-z0-9.]+ - 201' $(FW_BASE)/user1.bin | cut -b 1-80
+	$(Q) egrep -a 'meta-id [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.7.bin" "$(SDK_BASE)/bin/esp_init_data_default.bin" \
+	       wiflash avrflash megaflash release/meta-id-$(VERSION)
+	$(Q) tar zcf meta-id-$(VERSION).tgz -C release meta-id-$(VERSION)
+	$(Q) echo "Release file: meta-id-$(VERSION).tgz"
+	$(Q) rm -rf release
+
+docker:
+	$(Q) docker build -t jeelabs/meta-id .
+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))))
+
+depend:
+	makedepend -p${BUILD_BASE}/ -Y -- $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) -I${XTENSA_TOOLS_ROOT}../xtensa-lx106-elf/include -I${XTENSA_TOOLS_ROOT}../lib/gcc/xtensa-lx106-elf/4.8.2/include -- */*.c
+
+# Rebuild version at least at every Makefile change
+
+${BUILD_BASE}/meta-id/main.o: Makefile
+
+# DO NOT DELETE
+

+ 14 - 0
meta-id/RESTMQTT.md

@@ -0,0 +1,14 @@
+Meta-ID: 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 meta-id can notify the
+uC when requests complete or when other actions happen, such as wifi connectivity status changes.
+
+You can find REST and MQTT libraries as well as demo sketches in the
+[el-client](https://github.com/jeelabs/el-client) repository.

+ 36 - 0
meta-id/TROUBLESHOOTING.md

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

+ 183 - 0
meta-id/UC-FLASHING.md

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

+ 69 - 0
meta-id/WEB-SERVER.md

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

+ 87 - 0
meta-id/WIFI-CONFIG.md

@@ -0,0 +1,87 @@
+meta-id Wifi configuration
+===========================
+
+For proper operation the end state that meta-id needs to arrive at is to have it
+join your pre-existing wifi network as a pure station.
+However, in order to get there meta-id will start out as an access point and you'll have
+to join its network to configure it. The short version is:
+
+ 1. meta-id 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 meta-id's network as a station and you configure
+    meta-id wifi with your network info by pointing your browser at `http://192.168.4.1/`
+ 3. you set a hostname for meta-id on the "home" page, or leave the default ("meta-id")
+ 4. meta-id starts to connect to your network while continuing to also be an access point
+    ("AP+STA"), the meta-id may show up with a `${hostname}.local` hostname
+    (depends on your DHCP/DNS config)
+ 4. meta-id succeeds in connecting and shuts down its own access point after 15 seconds,
+    you reconnect your laptop/phone to your normal network and access meta-id via its hostname
+    or IP address
+
+### Notes on using AP (access point) mode
+
+meta-id does not support STA+AP mode, however it does support STA mode and AP mode. What happens
+is that STA+AP mode is used at boot and when making STA changes to allow for recovery: the AP
+mode stays on for a while so you can connect to it and fix the STA mode. Once STA has connected,
+meta-id switches to STA-only mode. There is no setting to stay in STA+AP mode. So... if you want
+to use AP ensure you set meta-id to AP-only mode. If you want STA+AP mode you're gonna have to
+modify the source for yourself. (This stuff is painful to test and rather tricky, so don't expect
+the way it works to change.)
+
+Configuration details
+---------------------
+
+### Wifi
+
+After you have serially flashed the module it will create a wifi access point (AP) with an
+SSID of the form `ESP_012ABC` where 012ABC is a piece of the module's MAC address.
+Using a laptop, phone, or tablet connect to this SSID and then open a browser pointed at
+http://192.168.4.1/, you should then see the meta-id web site.
+
+Now configure the wifi. The desired configuration is for the meta-id 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 meta-id 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 meta-id 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 meta-id's IP address or, depending on your
+network's DHCP/DNS config you may be able to go to http://meta-id.local
+
+At this point the meta-id 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 meta-id
+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 meta-id's AP and reconfigure the station part.
+
+One open issue (#28) is that meta-id 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
+- meta-id 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, meta-id 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.

+ 14 - 0
meta-id/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

+ 110 - 0
meta-id/avrflash

@@ -0,0 +1,110 @@
+#! /bin/bash
+#
+# Flash an AVR with optiboot using the meta-id 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 <<EOT
+Usage: ${0##*/} [-options...] hostname sketch.hex
+Flash the AVR running optiboot attached to meta-id with the sketch.
+  -v                    Be verbose
+  -h                    show this help
+
+Example: ${0##*/} -v meta-id mysketch.hex
+         ${0##*/} 192.168.4.1 mysketch.hex
+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 [[ $# != 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

+ 194 - 0
meta-id/cmd/cmd.c

@@ -0,0 +1,194 @@
+// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
+//
+// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
+
+#include "esp8266.h"
+#include "cmd.h"
+#include "crc16.h"
+#include "uart.h"
+
+#ifdef CMD_DBG
+#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
+#else
+#define DBG(format, ...) do { } while(0)
+#endif
+
+//===== ESP -> Serial responses
+
+static void ICACHE_FLASH_ATTR
+cmdProtoWrite(uint8_t data) {
+  switch(data){
+  case SLIP_END:
+    uart0_write_char(SLIP_ESC);
+    uart0_write_char(SLIP_ESC_END);
+    break;
+  case SLIP_ESC:
+    uart0_write_char(SLIP_ESC);
+    uart0_write_char(SLIP_ESC_ESC);
+    break;
+  default:
+    uart0_write_char(data);
+  }
+}
+
+static void ICACHE_FLASH_ATTR
+cmdProtoWriteBuf(const uint8_t *data, short len) {
+  while (len--) cmdProtoWrite(*data++);
+}
+
+static uint16_t resp_crc;
+
+// Start a response, returns the partial CRC
+void ICACHE_FLASH_ATTR
+cmdResponseStart(uint16_t cmd, uint32_t value, uint16_t argc) {
+  DBG("cmdResponse: cmd=%d val=%d argc=%d\n", cmd, value, argc);
+
+  uart0_write_char(SLIP_END);
+  cmdProtoWriteBuf((uint8_t*)&cmd, 2);
+  resp_crc = crc16_data((uint8_t*)&cmd, 2, 0);
+  cmdProtoWriteBuf((uint8_t*)&argc, 2);
+  resp_crc = crc16_data((uint8_t*)&argc, 2, resp_crc);
+  cmdProtoWriteBuf((uint8_t*)&value, 4);
+  resp_crc = crc16_data((uint8_t*)&value, 4, resp_crc);
+}
+
+// Adds data to a response, returns the partial CRC
+void ICACHE_FLASH_ATTR
+cmdResponseBody(const void *data, uint16_t len) {
+  cmdProtoWriteBuf((uint8_t*)&len, 2);
+  resp_crc = crc16_data((uint8_t*)&len, 2, resp_crc);
+
+  cmdProtoWriteBuf(data, len);
+  resp_crc = crc16_data(data, len, resp_crc);
+
+  uint16_t pad = (4-((len+2)&3))&3; // get to multiple of 4
+  if (pad > 0) {
+    uint32_t temp = 0;
+    cmdProtoWriteBuf((uint8_t*)&temp, pad);
+    resp_crc = crc16_data((uint8_t*)&temp, pad, resp_crc);
+  }
+}
+
+// Ends a response
+void ICACHE_FLASH_ATTR
+cmdResponseEnd() {
+  cmdProtoWriteBuf((uint8_t*)&resp_crc, 2);
+  uart0_write_char(SLIP_END);
+}
+
+//===== serial -> ESP commands
+
+// Execute a parsed command
+static void ICACHE_FLASH_ATTR
+cmdExec(const CmdList *scp, CmdPacket *packet) {
+  // Iterate through the command table and call the appropriate function
+  while (scp->sc_function != NULL) {
+    if(scp->sc_name == packet->cmd) {
+      DBG("cmdExec: Dispatching cmd=%s\n", scp->sc_text);
+      // call command function
+      scp->sc_function(packet);
+      return;
+    }
+    scp++;
+  }
+  DBG("cmdExec: cmd=%d not found\n", packet->cmd);
+}
+
+// Parse a packet and print info about it
+void ICACHE_FLASH_ATTR
+cmdParsePacket(uint8_t *buf, short len) {
+  // minimum command length
+  if (len < sizeof(CmdPacket)) return;
+
+  // init pointers into buffer
+  CmdPacket *packet = (CmdPacket*)buf;
+  uint8_t *data_ptr = (uint8_t*)&packet->args;
+  uint8_t *data_limit = data_ptr+len;
+
+  DBG("cmdParsePacket: cmd=%d argc=%d value=%u\n",
+      packet->cmd,
+      packet->argc,
+      packet->value
+  );
+
+#if 0
+  // print out arguments
+  uint16_t argn = 0;
+  uint16_t argc = packet->argc;
+  while (data_ptr+2 < data_limit && argc--) {
+    short l = *(uint16_t*)data_ptr;
+    os_printf("cmdParsePacket: arg[%d] len=%d:", argn++, l);
+    data_ptr += 2;
+    while (data_ptr < data_limit && l--) {
+      os_printf(" %02X", *data_ptr++);
+    }
+    os_printf("\n");
+  }
+#endif
+
+  if (!cmdInSync && packet->cmd != CMD_SYNC) {
+    // we have not received a sync, perhaps we reset? Tell MCU to do a sync
+    cmdResponseStart(CMD_SYNC, 0, 0);
+    cmdResponseEnd();
+  } else if (data_ptr <= data_limit) {
+    cmdExec(commands, packet);
+  } else {
+    DBG("cmdParsePacket: packet length overrun, parsing arg %d\n", packet->argc);
+  }
+}
+
+//===== Helpers to parse a command packet
+
+// Fill out a CmdRequest struct given a CmdPacket
+void ICACHE_FLASH_ATTR
+cmdRequest(CmdRequest *req, CmdPacket* cmd) {
+  req->cmd = cmd;
+  req->arg_num = 0;
+  req->arg_ptr = (uint8_t*)&cmd->args;
+}
+
+// Return the number of arguments given a command struct
+uint32_t ICACHE_FLASH_ATTR
+cmdGetArgc(CmdRequest *req) {
+  return req->cmd->argc;
+}
+
+// Copy the next argument from a command structure into the data pointer, returns 0 on success
+// -1 on error
+int32_t ICACHE_FLASH_ATTR
+cmdPopArg(CmdRequest *req, void *data, uint16_t len) {
+  uint16_t length;
+
+  if (req->arg_num >= req->cmd->argc)
+    return -1;
+
+  length = *(uint16_t*)req->arg_ptr;
+  if (length != len) return -1; // safety check
+
+  req->arg_ptr += 2;
+  os_memcpy(data, req->arg_ptr, length);
+  req->arg_ptr += (length+3)&~3; // round up to multiple of 4
+
+  req->arg_num ++;
+  return 0;
+}
+
+// Skip the next argument
+void ICACHE_FLASH_ATTR
+cmdSkipArg(CmdRequest *req) {
+  uint16_t length;
+
+  if (req->arg_num >= req->cmd->argc) return;
+
+  length = *(uint16_t*)req->arg_ptr;
+
+  req->arg_ptr += 2;
+  req->arg_ptr += (length+3)&~3;
+  req->arg_num ++;
+}
+
+// Return the length of the next argument
+uint16_t ICACHE_FLASH_ATTR
+cmdArgLen(CmdRequest *req) {
+  return *(uint16_t*)req->arg_ptr;
+}

+ 124 - 0
meta-id/cmd/cmd.h

@@ -0,0 +1,124 @@
+// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
+//
+// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
+
+#ifndef CMD_H
+#define CMD_H
+#include <esp8266.h>
+
+// keep track of whether we received a sync command from uC
+extern bool cmdInSync;
+
+// Standard SLIP escape chars from RFC
+#define SLIP_END      0300    // indicates end of packet
+#define SLIP_ESC      0333    // indicates byte stuffing
+#define SLIP_ESC_END  0334    // ESC ESC_END means END data byte
+#define SLIP_ESC_ESC  0335    // ESC ESC_ESC means ESC data byte
+
+typedef struct __attribute__((__packed__)) {
+  uint16_t  len;      // length of data
+  uint8_t   data[0];  // really data[len]
+} CmdArg;
+
+typedef struct __attribute__((__packed__)) {
+  uint16_t  cmd;      // command to perform, from CmdName enum
+  uint16_t  argc;     // number of arguments to command
+  uint32_t  value;    // callback pointer for response or first argument
+  CmdArg    args[0];  // really args[argc]
+} CmdPacket;
+
+typedef struct {
+  CmdPacket *cmd;     // command packet header
+  uint32_t  arg_num;  // number of args parsed
+  uint8_t   *arg_ptr; // pointer to ??
+} CmdRequest;
+
+typedef enum {
+  CMD_NULL = 0,
+  CMD_SYNC,           // synchronize and clear
+  CMD_RESP_V,         // response with a value
+  CMD_RESP_CB,        // response with a callback
+  CMD_WIFI_STATUS,    // get the current wifi status
+  CMD_CB_ADD,
+  CMD_CB_EVENTS,
+  CMD_GET_TIME,       // get current time in seconds since the unix epoch
+  CMD_GET_WIFI_INFO,	// query ip address info
+  CMD_SET_WIFI_INFO,	// set ip address info
+
+  CMD_MQTT_SETUP = 10,  // set-up callbacks
+  CMD_MQTT_PUBLISH,     // publish a message
+  CMD_MQTT_SUBSCRIBE,   // subscribe to a topic
+  CMD_MQTT_LWT,         // set the last-will-topic and messge
+  CMD_MQTT_GET_CLIENTID,
+
+  CMD_REST_SETUP = 20,  // set-up callbacks
+  CMD_REST_REQUEST,     // do REST request
+  CMD_REST_SETHEADER,	// define header
+
+  CMD_WEB_SETUP = 30,   // set-up WEB callback
+  CMD_WEB_DATA,         // WEB data from MCU
+
+  CMD_SOCKET_SETUP = 40, // set-up callbacks
+  CMD_SOCKET_SEND,       // send data over UDP socket
+
+  CMD_WIFI_GET_APCOUNT = 50,  // Query the number of networks / Access Points known
+  CMD_WIFI_GET_APNAME,        // Query the name (SSID) of an Access Point (AP)
+  CMD_WIFI_SELECT_SSID,       // Connect to a specific network
+  CMD_WIFI_SIGNAL_STRENGTH,   // Query RSSI
+  CMD_WIFI_GET_SSID,          // Query SSID currently connected to
+  CMD_WIFI_START_SCAN,        // Trigger a scan (takes a long time)
+
+} CmdName;
+
+typedef void (*cmdfunc_t)(CmdPacket *cmd);
+
+typedef struct {
+  CmdName   sc_name;     // name as CmdName enum
+  char      *sc_text;    // name as string
+  cmdfunc_t sc_function; // pointer to function
+} CmdList;
+
+// command dispatch table
+extern const CmdList commands[];
+
+#define CMD_CBNLEN 16
+typedef struct {
+  char name[CMD_CBNLEN];
+  uint32_t callback;
+} CmdCallback;
+
+// Used by slip protocol to cause parsing of a received packet
+void cmdParsePacket(uint8_t *buf, short len);
+
+// Return the info about a callback to the attached uC by name, these are callbacks that the
+// attached uC registers using the ADD_SENSOR command
+CmdCallback* cmdGetCbByName(char* name);
+
+// Add a callback
+uint32_t cmdAddCb(char *name, uint32_t callback);
+
+// Responses
+
+// Start a response
+void cmdResponseStart(uint16_t cmd, uint32_t value, uint16_t argc);
+// Adds data to a response
+void cmdResponseBody(const void* data, uint16_t len);
+// Ends a response
+void cmdResponseEnd();
+
+//void cmdResponse(uint16_t cmd, uint32_t callback, uint32_t value, uint16_t argc, CmdArg* args[]);
+
+// Requests
+
+// Fill out a CmdRequest struct given a CmdPacket
+void cmdRequest(CmdRequest *req, CmdPacket* cmd);
+// Return the number of arguments given a request
+uint32_t cmdGetArgc(CmdRequest *req);
+// Return the length of the next argument
+uint16_t cmdArgLen(CmdRequest *req);
+// Copy next arg from request into the data pointer, returns 0 on success, -1 on error
+int32_t cmdPopArg(CmdRequest *req, void *data, uint16_t len);
+// Skip next arg
+void cmdSkipArg(CmdRequest *req);
+
+#endif

+ 407 - 0
meta-id/cmd/handlers.c

@@ -0,0 +1,407 @@
+// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
+//
+// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
+
+#include "esp8266.h"
+#include "sntp.h"
+#include "cmd.h"
+#include "uart.h"
+#include <cgiwifi.h>
+#ifdef MQTT
+#include <mqtt_cmd.h>
+#endif
+#ifdef REST
+#include <rest.h>
+#endif
+#ifdef WEBSERVER
+#include <web-server.h>
+#endif
+#ifdef SOCKET
+#include <socket.h>
+#endif
+#include <ip_addr.h>
+#include "meta-id/cgi.h"
+
+#include "config.h"
+
+#ifdef CMD_DBG
+#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
+#else
+#define DBG(format, ...) do { } while(0)
+#endif
+
+static void cmdNull(CmdPacket *cmd);
+static void cmdSync(CmdPacket *cmd);
+static void cmdWifiStatus(CmdPacket *cmd);
+static void cmdGetTime(CmdPacket *cmd);
+static void cmdGetWifiInfo(CmdPacket *cmd);
+// static void cmdSetWifiInfo(CmdPacket *cmd);
+static void cmdAddCallback(CmdPacket *cmd);
+
+static void cmdWifiGetApCount(CmdPacket *cmd);
+static void cmdWifiGetApName(CmdPacket *cmd);
+static void cmdWifiSelectSSID(CmdPacket *cmd);
+static void cmdWifiSignalStrength(CmdPacket *cmd);
+static void cmdWifiQuerySSID(CmdPacket *cmd);
+static void cmdWifiStartScan(CmdPacket *cmd);
+
+void cmdMqttGetClientId(CmdPacket *cmd);
+
+// keep track of last status sent to uC so we can notify it when it changes
+static uint8_t lastWifiStatus = wifiIsDisconnected;
+// keep track of whether we have registered our cb handler with the wifi subsystem
+static bool wifiCbAdded = false;
+// keep track of whether we received a sync command from uC
+bool cmdInSync = false;
+
+// Command dispatch table for serial -> ESP commands
+const CmdList commands[] = {
+  {CMD_NULL,            "NULL",           cmdNull},        // no-op
+  {CMD_SYNC,            "SYNC",           cmdSync},        // synchronize
+  {CMD_WIFI_STATUS,     "WIFI_STATUS",    cmdWifiStatus},
+  {CMD_CB_ADD,          "ADD_CB",         cmdAddCallback},
+  {CMD_GET_TIME,        "GET_TIME",       cmdGetTime},
+  {CMD_GET_WIFI_INFO,   "GET_WIFI_INFO",  cmdGetWifiInfo},
+  // {CMD_SET_WIFI_INFO,   "SET_WIFI_INFO",  cmdSetWifiInfo},
+
+  {CMD_WIFI_GET_APCOUNT,	"WIFI_GET_APCOUNT",	cmdWifiGetApCount},
+  {CMD_WIFI_GET_APNAME,		"WIFI_GET_APNAME",	cmdWifiGetApName},
+  {CMD_WIFI_SELECT_SSID,	"WIFI_SELECT_SSID",	cmdWifiSelectSSID},
+  {CMD_WIFI_SIGNAL_STRENGTH,	"WIFI_SIGNAL_STRENGTH",	cmdWifiSignalStrength},
+  {CMD_WIFI_GET_SSID,		"WIFI_GET_SSID",	cmdWifiQuerySSID},
+  {CMD_WIFI_START_SCAN,		"WIFI_START_SCAN",	cmdWifiStartScan},
+
+#ifdef MQTT
+  {CMD_MQTT_SETUP,      "MQTT_SETUP",     MQTTCMD_Setup},
+  {CMD_MQTT_PUBLISH,    "MQTT_PUB",       MQTTCMD_Publish},
+  {CMD_MQTT_SUBSCRIBE , "MQTT_SUB",       MQTTCMD_Subscribe},
+  {CMD_MQTT_LWT,        "MQTT_LWT",       MQTTCMD_Lwt},
+  {CMD_MQTT_GET_CLIENTID,"MQTT_CLIENTID", cmdMqttGetClientId},
+#endif
+#ifdef REST
+  {CMD_REST_SETUP,      "REST_SETUP",     REST_Setup},
+  {CMD_REST_REQUEST,    "REST_REQ",       REST_Request},
+  {CMD_REST_SETHEADER,  "REST_SETHDR",    REST_SetHeader},
+#endif
+#ifdef WEBSERVER
+  {CMD_WEB_SETUP,       "WEB_SETUP",      WEB_Setup},
+  {CMD_WEB_DATA,        "WEB_DATA",       WEB_Data},
+#endif
+#ifdef SOCKET
+  {CMD_SOCKET_SETUP,    "SOCKET_SETUP",   SOCKET_Setup},
+  {CMD_SOCKET_SEND,     "SOCKET_SEND",    SOCKET_Send},
+#endif
+};
+
+//===== List of registered callbacks (to uC)
+
+// WifiCb plus 10 for other stuff
+#define MAX_CALLBACKS 12
+CmdCallback callbacks[MAX_CALLBACKS]; // cleared in cmdSync
+
+uint32_t ICACHE_FLASH_ATTR
+cmdAddCb(char* name, uint32_t cb) {
+  for (uint8_t i = 0; i < MAX_CALLBACKS; i++) {
+    //DBG("cmdAddCb: index %d name=%s cb=%p\n", i, callbacks[i].name,
+    //  (void *)callbacks[i].callback);
+    // find existing callback or add to the end
+    if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0 || callbacks[i].name[0] == '\0') {
+      os_strncpy(callbacks[i].name, name, sizeof(callbacks[i].name));
+      callbacks[i].name[CMD_CBNLEN-1] = 0; // strncpy doesn't null terminate
+      callbacks[i].callback = cb;
+      DBG("cmdAddCb: '%s'->0x%x added at %d\n", callbacks[i].name, cb, i);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+CmdCallback* ICACHE_FLASH_ATTR
+cmdGetCbByName(char* name) {
+  for (uint8_t i = 0; i < MAX_CALLBACKS; i++) {
+    //DBG("cmdGetCbByName: index %d name=%s cb=%p\n", i, callbacks[i].name,
+    //  (void *)callbacks[i].callback);
+    // if callback doesn't exist or it's null
+    if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0) {
+      DBG("cmdGetCbByName: cb %s found at index %d\n", name, i);
+      return &callbacks[i];
+    }
+  }
+  DBG("cmdGetCbByName: cb %s not found\n", name);
+  return 0;
+}
+
+//===== Wifi callback
+
+// Callback from wifi subsystem to notify us of status changes
+static void ICACHE_FLASH_ATTR
+cmdWifiCb(uint8_t wifiStatus) {
+  if (wifiStatus != lastWifiStatus){
+    DBG("cmdWifiCb: wifiStatus=%d\n", wifiStatus);
+    lastWifiStatus = wifiStatus;
+    CmdCallback *wifiCb = cmdGetCbByName("wifiCb");
+    if ((uint32_t)wifiCb->callback != -1) {
+      uint8_t status = wifiStatus == wifiGotIP ? 5 : 1;
+      cmdResponseStart(CMD_RESP_CB, (uint32_t)wifiCb->callback, 1);
+      cmdResponseBody((uint8_t*)&status, 1);
+      cmdResponseEnd();
+    }
+  }
+}
+
+//===== Command handlers
+
+// Command handler for Null command
+static void ICACHE_FLASH_ATTR
+cmdNull(CmdPacket *cmd) {
+}
+
+// Command handler for sync command
+static void ICACHE_FLASH_ATTR
+cmdSync(CmdPacket *cmd) {
+  CmdRequest req;
+  uart0_write_char(SLIP_END); // prefix with a SLIP END to ensure we get a clean start
+  cmdRequest(&req, cmd);
+  if(cmd->argc != 0 || cmd->value == 0) {
+    cmdResponseStart(CMD_RESP_V, 0, 0);
+    cmdResponseEnd();
+    return;
+  }
+
+  // clear callbacks table
+  os_memset(callbacks, 0, sizeof(callbacks));
+
+  // TODO: call other protocols back to tell them to reset
+
+  // register our callback with wifi subsystem
+  if (!wifiCbAdded) {
+    wifiAddStateChangeCb(cmdWifiCb);
+    wifiCbAdded = true;
+  }
+
+  // send OK response
+  cmdResponseStart(CMD_RESP_V, cmd->value, 0);
+  cmdResponseEnd();
+  cmdInSync = true;
+
+  // save the MCU's callback and trigger an initial callback
+  cmdAddCb("wifiCb", cmd->value);
+  lastWifiStatus = 0xff; // set to invalid value so we immediately send status cb in all cases
+  cmdWifiCb(wifiState);
+
+  return;
+}
+
+// Command handler for wifi status command
+static void ICACHE_FLASH_ATTR
+cmdWifiStatus(CmdPacket *cmd) {
+  cmdResponseStart(CMD_RESP_V, wifiState, 0);
+  cmdResponseEnd();
+  return;
+}
+
+// Command handler for time
+static void ICACHE_FLASH_ATTR
+cmdGetTime(CmdPacket *cmd) {
+  cmdResponseStart(CMD_RESP_V, sntp_get_current_timestamp(), 0);
+  cmdResponseEnd();
+  return;
+}
+
+// Command handler for IP information
+static void ICACHE_FLASH_ATTR
+cmdGetWifiInfo(CmdPacket *cmd) {
+  CmdRequest req;
+
+  cmdRequest(&req, cmd);
+  if(cmd->argc != 0 || cmd->value == 0) {
+    cmdResponseStart(CMD_RESP_V, 0, 0);
+    cmdResponseEnd();
+    return;
+  }
+
+  uint32_t callback = req.cmd->value;
+
+  struct ip_info info;
+  wifi_get_ip_info(0, &info);
+  uint8_t mac[6];
+  wifi_get_macaddr(0, mac);
+
+  cmdResponseStart(CMD_RESP_CB, callback, 4);
+  cmdResponseBody(&info.ip.addr, sizeof(info.ip.addr));
+  cmdResponseBody(&info.netmask.addr, sizeof(info.netmask.addr));
+  cmdResponseBody(&info.gw.addr, sizeof(info.gw.addr));
+  cmdResponseBody(mac, sizeof(mac));
+  cmdResponseEnd();
+}
+
+// Command handler to add a callback to the named-callbacks list, this is for a callback to the uC
+static void ICACHE_FLASH_ATTR
+cmdAddCallback(CmdPacket *cmd) {
+  CmdRequest req;
+  cmdRequest(&req, cmd);
+  if (cmd->argc != 1 || cmd->value == 0) return;
+
+  char name[16];
+  uint16_t len;
+
+  // get the callback name
+  len = cmdArgLen(&req);
+  if (len > 15) return; // max size of name is 15 characters
+  if (cmdPopArg(&req, (uint8_t *)name, len)) return;
+  name[len] = 0;
+  DBG("cmdAddCallback: name=%s\n", name);
+
+  cmdAddCb(name, cmd->value); // save the sensor callback
+}
+
+// Query the number of wifi access points
+static void ICACHE_FLASH_ATTR cmdWifiGetApCount(CmdPacket *cmd) {
+  int n = wifiGetApCount();
+  DBG("WifiGetApCount : %d\n", n);
+  cmdResponseStart(CMD_RESP_V, n, 0);
+  cmdResponseEnd();
+}
+
+// Query the name of a wifi access point
+static void ICACHE_FLASH_ATTR cmdWifiGetApName(CmdPacket *cmd) {
+  CmdRequest req;
+
+  cmdRequest(&req, cmd);
+
+  int argc = cmdGetArgc(&req);
+  DBG("cmdWifiGetApName: argc %d\n", argc);
+  if (argc != 1)
+    return;
+
+  uint16_t i;
+  cmdPopArg(&req, (uint8_t*)&i, 2);
+
+  uint32_t callback = req.cmd->value;
+
+  char myssid[33];
+  wifiGetApName(i, myssid);
+  myssid[32] = '\0';
+  DBG("wifiGetApName(%d) -> {%s}\n", i, myssid);
+
+  cmdResponseStart(CMD_RESP_CB, callback, 1);
+  cmdResponseBody(myssid, strlen(myssid)+1);
+  cmdResponseEnd();
+}
+
+/*
+ * Select a wireless network.
+ * This can be called in two ways :
+ * - with a pair of strings (SSID, password)
+ * - with a number and a string (index into network array, password)
+ */
+static void ICACHE_FLASH_ATTR cmdWifiSelectSSID(CmdPacket *cmd) {
+  CmdRequest req;
+  cmdRequest(&req, cmd);
+  int argc = cmdGetArgc(&req);
+  char ssid[33], pass[65];
+
+  if (argc != 2) return;
+
+  int len = cmdArgLen(&req);
+  if (len == 1) {
+    // Assume this is the index
+    uint8_t ix;
+    cmdPopArg(&req, &ix, 1);
+    wifiGetApName(ix, ssid);
+    ssid[32] = '\0';
+  } else {
+    // Longer than 1 byte: must be SSID
+    if (len > 32) return;
+    cmdPopArg(&req, ssid, len);
+    ssid[len] = 0;
+  }
+
+  // Extract password from message
+  len = cmdArgLen(&req);
+  if (len > 64) return;
+  cmdPopArg(&req, pass, len);
+  pass[len] = 0;
+
+  DBG("SelectSSID(%s,%s)", ssid, pass);
+  connectToNetwork(ssid, pass);
+}
+
+#if 0
+/*
+ * Once we're attached to some wireless network, choose not to pick up address from
+ * DHCP or so but set our own.
+ */
+static void ICACHE_FLASH_ATTR cmdSetWifiInfo(CmdPacket *cmd) {
+  DBG("SetWifiInfo()\n");
+}
+#endif
+
+static void ICACHE_FLASH_ATTR cmdWifiSignalStrength(CmdPacket *cmd) {
+  CmdRequest req;
+
+  cmdRequest(&req, cmd);
+
+  int argc = cmdGetArgc(&req);
+  if (argc != 1) {
+    DBG("cmdWifiSignalStrength: argc %d\n", argc);
+    return;
+  }
+
+  char x;
+  cmdPopArg(&req, (uint8_t*)&x, 1);
+  int i = x;
+  DBG("cmdWifiSignalStrength: argc %d, ", argc);
+  DBG("i %d\n", i);
+
+  int rssi = wifiSignalStrength(i);
+
+  cmdResponseStart(CMD_RESP_V, rssi, 0);
+  cmdResponseEnd();
+}
+
+//
+static void ICACHE_FLASH_ATTR cmdWifiQuerySSID(CmdPacket *cmd) {
+  CmdRequest req;
+  cmdRequest(&req, cmd);
+  uint32_t callback = req.cmd->value;
+
+  struct station_config conf;
+  bool res = wifi_station_get_config(&conf);
+  if (res) {
+  // #warning handle me
+  } else {
+  }
+
+  DBG("QuerySSID : %s\n", conf.ssid);
+
+  cmdResponseStart(CMD_RESP_CB, callback, 1);
+  cmdResponseBody(conf.ssid, strlen((char *)conf.ssid)+1);
+  cmdResponseEnd();
+}
+
+// Start scanning, API interface
+static void ICACHE_FLASH_ATTR cmdWifiStartScan(CmdPacket *cmd) {
+  // call a function that belongs in meta-id/cgiwifi.c due to variable access
+  wifiStartScan();
+}
+
+// Command handler for MQTT information
+void ICACHE_FLASH_ATTR cmdMqttGetClientId(CmdPacket *cmd) {
+  CmdRequest req;
+
+  cmdRequest(&req, cmd);
+  if(cmd->argc != 0 || cmd->value == 0) {
+    cmdResponseStart(CMD_RESP_V, 0, 0);
+    cmdResponseEnd();
+    return;
+  }
+
+  uint32_t callback = req.cmd->value;
+
+  cmdResponseStart(CMD_RESP_CB, callback, 1);
+  cmdResponseBody(flashConfig.mqtt_clientid, strlen(flashConfig.mqtt_clientid)+1);
+  cmdResponseEnd();
+
+  os_printf("MqttGetClientId : %s\n", flashConfig.mqtt_clientid);
+}

+ 4 - 0
meta-id/envvars.template

@@ -0,0 +1,4 @@
+export SDK_BASE=~/esp-open-sdk
+export XTENSA_TOOLS_ROOT=~/esp-open-sdk/xtensa-lx106-elf/xtensa-lx106-elf/bin/
+export PATH=$PATH:$XTENSA_TOOLS_ROOT
+

+ 293 - 0
meta-id/espfs/espfs.c

@@ -0,0 +1,293 @@
+/*
+This is a simple read-only implementation of a file system. It uses a block of data coming from the
+mkespfsimg tool, and can use that block to do abstracted operations on the files that are in there.
+It's written for use with httpd, but doesn't need to be used as such.
+*/
+
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * Jeroen Domburg <jeroen@spritesmods.com> 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 <esp8266.h>
+#else
+//Test build
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#define os_malloc malloc
+#define os_free free
+#define os_memcpy memcpy
+#define os_memset memset
+#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"
+
+EspFsContext espLinkCtxDef;
+EspFsContext userPageCtxDef;
+
+EspFsContext * espLinkCtx = &espLinkCtxDef;
+EspFsContext * userPageCtx = &userPageCtxDef;
+
+struct EspFsContext
+{
+	char*       data;
+	EspFsSource source;
+	uint8_t     valid;
+};
+
+struct EspFsFile {
+	EspFsContext *ctx;
+	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.
+*/
+
+//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, const char *src, int len) {
+	int x;
+	int w, b;
+	for (x=0; x<len; x++) {
+		b=((int)src&3);
+		w=*((int *)(src-b));
+		if (b==0) *dst=(w>>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
+
+void ICACHE_FLASH_ATTR memcpyFromFlash(char *dst, const char *src, int len)
+{
+	if( spi_flash_read( (int)src, (void *)dst, len ) != SPI_FLASH_RESULT_OK )
+		os_memset( dst, 0, len ); // if read was not successful, reply with zeroes
+}
+
+// memcpy on MEMORY/FLASH file systems
+void espfs_memcpy( EspFsContext * ctx, void * dest, const void * src, int count )
+{
+	if( ctx->source == ESPFS_MEMORY )
+		os_memcpy( dest, src, count );
+	else
+		memcpyFromFlash(dest, src, count);
+}
+
+// aligned memcpy on MEMORY/FLASH file systems
+void espfs_memcpyAligned( EspFsContext * ctx, void * dest, const void * src, int count )
+{
+	if( ctx->source == ESPFS_MEMORY )
+		memcpyAligned(dest, src, count);
+	else
+		memcpyFromFlash(dest, src, count);
+}
+
+// initializes an EspFs context
+EspFsInitResult ICACHE_FLASH_ATTR espFsInit(EspFsContext *ctx, void *flashAddress, EspFsSource source) {
+	ctx->valid = 0;
+	ctx->source = source;
+	// base address must be aligned to 4 bytes
+	if (((int)flashAddress & 3) != 0) {
+		return ESPFS_INIT_RESULT_BAD_ALIGN;
+	}
+
+	// check if there is valid header at address
+	EspFsHeader testHeader;
+	espfs_memcpy(ctx, &testHeader, flashAddress, sizeof(EspFsHeader));
+	if (testHeader.magic != ESPFS_MAGIC) {
+		return ESPFS_INIT_RESULT_NO_IMAGE;
+	}
+
+	ctx->data = (char *)flashAddress;
+	ctx->valid = 1;
+	return ESPFS_INIT_RESULT_OK;
+}
+
+// Returns flags of opened file.
+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;
+	espfs_memcpyAligned(fh->ctx, (char*)&flags, (char*)&fh->header->flags, 1);
+	return (int)flags;
+}
+
+// creates and initializes an iterator over the espfs file system
+void ICACHE_FLASH_ATTR espFsIteratorInit(EspFsContext *ctx, EspFsIterator *iterator)
+{
+	if( ctx->data == NULL )
+	{
+		iterator->ctx = NULL;
+		return;
+	}
+	iterator->ctx = ctx;
+	iterator->position = NULL;
+}
+
+// moves iterator to the next file on espfs
+// returns 1 if iterator move was successful, otherwise 0 (last file)
+// iterator->header and iterator->name will contain file information
+int ICACHE_FLASH_ATTR espFsIteratorNext(EspFsIterator *iterator)
+{
+	if( iterator->ctx == NULL )
+		return 0;
+	
+	char * position = iterator->position;
+	if( position == NULL )
+		position = iterator->ctx->data; // first node
+	else
+	{
+		// jump the iterator to the next file
+		
+		position+=sizeof(EspFsHeader) + iterator->header.nameLen+iterator->header.fileLenComp;
+		if ((int)position&3) position+=4-((int)position&3); //align to next 32bit val
+	}
+	
+	iterator->position = position;
+	EspFsHeader * hdr = &iterator->header;
+	espfs_memcpy(iterator->ctx, hdr, position, sizeof(EspFsHeader));
+	
+	if (hdr->magic!=ESPFS_MAGIC) {
+#ifdef ESPFS_DBG
+		os_printf("Magic mismatch. EspFS image broken.\n");
+#endif
+		return 0;
+	}
+	if (hdr->flags&FLAG_LASTFILE) {
+		//os_printf("End of image.\n");
+		iterator->ctx = NULL; // invalidate the iterator
+		return 0;
+	}
+	
+	position += sizeof(EspFsHeader);
+	
+	//Grab the name of the file.
+	espfs_memcpy(iterator->ctx, iterator->name, position, sizeof(iterator->name));
+	
+	return 1;
+}
+
+//Open a file and return a pointer to the file desc struct.
+EspFsFile ICACHE_FLASH_ATTR *espFsOpen(EspFsContext *ctx, char *fileName) {
+	EspFsIterator it;
+	espFsIteratorInit(ctx, &it);
+	if (it.ctx == NULL) {
+#ifdef ESPFS_DBG
+		os_printf("Call espFsInit first!\n");
+#endif
+		return NULL;
+	}
+	//Strip initial slashes
+	while(fileName[0]=='/') fileName++;
+	
+	//Search the file
+	while( espFsIteratorNext(&it) ) 
+	{
+		if (os_strcmp(it.name, fileName)==0) {
+			//Yay, this is the file we need!
+			EspFsFile * 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->ctx = ctx;
+			r->header=(EspFsHeader *)it.position;
+			r->decompressor=it.header.compression;
+			r->posComp=it.position + it.header.nameLen  + sizeof(EspFsHeader);
+			r->posStart=it.position + it.header.nameLen  + sizeof(EspFsHeader);
+			r->posDecomp=0;
+			if (it.header.compression==COMPRESS_NONE) {
+				r->decompData=NULL;
+			} else {
+#ifdef ESPFS_DBG
+				os_printf("Invalid compression: %d\n", h.compression);
+#endif
+				return NULL;
+			}
+			return r;
+		}
+	}
+	return NULL;
+}
+
+//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.
+	espfs_memcpyAligned(fh->ctx, (char*)&flen, (char*)&fh->header->fileLenComp, 4);
+	espfs_memcpyAligned(fh->ctx, (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);
+		espfs_memcpyAligned(fh->ctx, 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);
+}
+
+// checks if the file system is valid (detect if the content is an espfs image or random data)
+int ICACHE_FLASH_ATTR espFsIsValid(EspFsContext *ctx) {
+	return ctx->valid;
+}
+

+ 42 - 0
meta-id/espfs/espfs.h

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

+ 33 - 0
meta-id/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

+ 56 - 0
meta-id/espfs/mkespfsimage/Makefile

@@ -0,0 +1,56 @@
+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
+
+CC=gcc
+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

+ 310 - 0
meta-id/espfs/mkespfsimage/main.c

@@ -0,0 +1,310 @@
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "espfs.h"
+#ifdef __MINGW32__
+#include "mman-win32/mman.h"
+#else
+#include <sys/mman.h>
+#endif
+#ifdef __WIN32__
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#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 <zlib.h>
+#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<argc; x++) {
+		if (strcmp(argv[x], "-c")==0 && argc>=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;
+}
+

+ 48 - 0
meta-id/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

+ 11 - 0
meta-id/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

+ 157 - 0
meta-id/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"

+ 180 - 0
meta-id/espfs/mkespfsimage/mman-win32/mman.c

@@ -0,0 +1,180 @@
+
+#include <windows.h>
+#include <errno.h>
+#include <io.h>
+
+#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;
+}

+ 55 - 0
meta-id/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 <sys/types.h>
+
+#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_ */

+ 235 - 0
meta-id/espfs/mkespfsimage/mman-win32/test.c

@@ -0,0 +1,235 @@
+
+#include "mman.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#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;
+}

+ 7 - 0
meta-id/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

+ 5 - 0
meta-id/html/.directory

@@ -0,0 +1,5 @@
+[Dolphin]
+SortOrder=1
+Timestamp=2018,2,18,14,23,27
+Version=3
+ViewMode=1

BIN
meta-id/html/Lato-Embed.ttf


+ 275 - 0
meta-id/html/META.css

@@ -0,0 +1,275 @@
+@charset "utf-8";
+@font-face {
+  font-family: 'Lato';
+  src: url("/fonts/Lato-Embed.ttf");
+}
+
+@media only screen and (max-width: 500px) {
+    body {
+   margin-left: 5%;
+   margin-right: 5%;
+    }
+}
+
+@media only screen and (max-width: 1100px) {
+    body {
+   margin-left: 15%;
+   margin-right: 15%;
+    }
+}
+
+@media only screen and (max-width: 2000px) {
+    body {
+   margin-left: 30%;
+   margin-right: 30%;
+    }
+}
+
+body {
+  font-family: 'Lato', sans-serif;
+  color: #3e0694;
+  line-height: 155%;
+  margin-top: 10%; 
+  margin-bottom: 5%;
+}
+
+.headerlogo2 {
+  font-family: 'Lato', sans-serif;
+  font-weight: bold;
+  font-size: 400%;
+  color: #3e0694;
+}
+
+.menuwelcome {
+  font-family: 'Lato', sans-serif;
+  font-size: medium;
+  font-weight: bold;
+  margin-left: 9%;
+}
+
+body,
+td,
+th {
+  font-family: 'Lato', sans-serif;
+}
+
+
+.authorname {
+  font-family: 'Lato', sans-serif;
+  font-weight: 100;
+  font-size: 100%;
+  letter-spacing: 12px;
+}
+
+.black200percent {
+  font-family: 'Lato', sans-serif;
+  font-weight: bold;
+  font-size: 200%;
+  padding-right: 15%;
+  line-height: 125%;
+}
+
+.bold150percent {
+  font-family: 'Lato', sans-serif;
+  font-weight: bold;
+  font-size: 150%;
+  padding-right: 15%;
+  line-height: 145%;
+}
+
+.citation {
+  font-family: 'Lato', sans-serif;
+  font-size: 80%;
+  padding-right: 20%;
+  line-height: 1;
+}
+
+.chinese {
+  font-family: Arial, Helvetica, sans-serif;
+}
+
+.black200percentcapitalize {
+  font-family: 'Lato', sans-serif;
+  font-size: 200%;
+  font-weight: bold;
+  letter-spacing: 1px;
+  padding-right: 10%;
+  line-height: 125%;
+  text-transform: uppercase;
+}
+
+.footnoteintext {
+  font-family: 'Lato', sans-serif;
+  font-size: small;
+  font-weight: bold;
+}
+
+.footnotes {
+  font-family: 'Lato', sans-serif;
+  font-size: small;
+}
+
+.footnotebold {
+  font-family: 'Lato', sans-serif;
+  font-weight: bold;
+  font-size: small;
+}
+
+.footnoteitalic {
+  font-family: 'Lato', sans-serif;
+  font-size: small;
+  font-style: italic;
+}
+
+
+.italic {
+  font-family: 'Lato', sans-serif;
+  font-style: italic;
+}
+
+.bold {
+  font-family: 'Lato', sans-serif;
+  font-weight: bold;
+}
+
+.mediumbold {
+font-family: 'Lato', sans-serif;
+  font-weight: bold;
+  font-size: medium;
+}
+
+.loginbox {
+  display: inline-block;
+  width: 50%;
+  height: 2em;
+  border: 1px solid #3e0694;
+  font-family: 'Lato', sans-serif;
+  color: #3e0694;
+}
+
+.loginboxtext {
+  font-family: 'Lato', sans-serif;
+  vertical-align: -30%;
+}
+
+.submitboxtext2 {
+  font-family: 'Lato', sans-serif;
+  color: #FFF;
+  background-color: #3e0694;
+  display: inline;
+  border: solid 3px #3e0694;
+}
+
+
+
+body {
+  font-family: sans-serif;
+}
+
+a:link {
+  color: #3e0694;
+  text-decoration: none;
+}
+
+a:visited {
+  text-decoration: none;
+  color: #3e0694;
+}
+
+a:hover {
+  text-decoration: underline;
+  color: #3e0694;
+}
+
+a:active {
+  text-decoration: underline;
+  color: #000;
+}
+
+#notification {
+  position: absolute;
+  top: 0;
+  right:0;
+  width:50%;
+  z-index: 200;
+  font-size: 110%;
+  text-align: right;
+  background-color: #3e0694;
+  color:white;
+  padding: 0.1em 0.4em;
+}
+
+
+.config{
+	  border: none;
+}
+
+ /* CHECKMARK BOXES */
+ /* Customize the label (the container) */
+.container {
+  display: block;
+  position: relative;
+  padding-left: 35px;
+  margin-bottom: 12px;
+  cursor: pointer;
+  font-size: small;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+/* Hide the browser's default checkbox */
+.container input {
+  position: absolute;
+  opacity: 0;
+  cursor: pointer;
+}
+
+/* Create a custom checkbox */
+.checkmark {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 25px;
+  width: 25px;
+  border: solid 1px #3e0694;
+  background-color: #fff;
+}
+
+/* On mouse-over, add a grey background color */
+.container:hover input ~ .checkmark {
+  background-color: #3e0694;
+}
+
+/* When the checkbox is checked, add a blue background */
+.container input:checked ~ .checkmark {
+  background-color: #3e0694;
+}
+
+/* Create the checkmark/indicator (hidden when not checked) */
+.checkmark:after {
+  content: "";
+  position: absolute;
+  display: none;
+}
+
+/* Show the checkmark when checked */
+.container input:checked ~ .checkmark:after {
+  display: block;
+}
+
+/* Style the checkmark/indicator */
+.container .checkmark:after {
+  position: absolute;
+  top: 50%;
+  left: 10%;
+  transform: translateY(-50%);;
+  width: 80%;
+  height: 80%;
+  background-color: white;
+  border-radius: 50%;
+
+} 
+
+

File diff suppressed because it is too large
+ 41 - 0
meta-id/html/about.html


BIN
meta-id/html/attic/Lato-Black.ttf


BIN
meta-id/html/attic/Lato-BlackItalic.ttf


BIN
meta-id/html/attic/Lato-Bold.ttf


BIN
meta-id/html/attic/Lato-BoldItalic.ttf


File diff suppressed because it is too large
+ 15601 - 0
meta-id/html/attic/Lato-Embed.sfd


BIN
meta-id/html/attic/Lato-Hairline.ttf


BIN
meta-id/html/attic/Lato-HairlineItalic.ttf


BIN
meta-id/html/attic/Lato-Italic.ttf


BIN
meta-id/html/attic/Lato-Light.ttf


BIN
meta-id/html/attic/Lato-LightItalic.ttf


BIN
meta-id/html/attic/Lato-Regular.ttf


+ 133 - 0
meta-id/html/attic/console.html

@@ -0,0 +1,133 @@
+  <div id="main" class="flex-fill flex-vbox" style="max-height:100%">
+    <div class="header">
+      <h1>Microcontroller Console</h1>
+    </div>
+
+    <div class="content flex-fill flex-vbox">
+      <p>
+        <a id="reset-button" class="pure-button button-primary" href="#">Reset &#xb5;C</a>
+        &nbsp; <a id="clear-button" class="pure-button button-primary" href="#">Clear Log</a>
+        &nbsp; Baud:
+        <select id="baud-sel" class="pure-button" href="#">
+          <option value="460800">460800</option>
+          <option value="250000">250000</option>
+          <option value="230400">230400</option>
+          <option value="115200">115200</option>
+          <option value="57600">57600</option>
+          <option value="38400">38400</option>
+          <option value="19200">19200</option>
+          <option value="9600">9600</option>
+          <option value="4800">4800</option>
+          <option value="2400">2400</option>
+          <option value="1200">1200</option>
+          <option value="600">600</option>
+          <option value="300">300</option>
+        </select>
+        &nbsp; Fmt:
+        <select id="fmt-sel" class="pure-button" href="#">
+          <option value="8N1">8N1</option>
+          <option value="8E1">8E1</option>
+          <option value="8N2">8N2</option>
+          <option value="8E2">8E2</option>
+          <option value="7N1">7N1</option>
+          <option value="7E1">7E1</option>
+          <option value="7N2">7N2</option>
+          <option value="7E2">7E2</option>
+        </select>
+      </p>
+      <div class="pure-g">
+        <div class="pure-u-1-4"><legend><b>Console</b></legend></div>
+        <div class="pure-u-3-4"></div>
+      </div>
+      <pre class="console flex-fill" id="console">--- No Content ---</pre>
+      <div>
+        <div class="pure-g">
+          <div class="pure-u-1-4"><legend><b>Console entry</b></legend></div>
+          <div class="pure-u-2-4">
+            <legend>(ENTER to submit, ESC to clear)</legend>
+          </div>
+          <div class="pure-u-1-4">
+            <legend>Add:
+              <input type="checkbox" id="input-add-cr" checked class="inline"><label>CR(\r)</label>
+              <input type="checkbox" id="input-add-lf" checked class="inline"><label>LF(\n)</label>
+            </legend>
+          </div>
+        </div>
+        <div class="pure-g">
+          <div class="pure-u-1-1">
+            <span style="float:right; width:10px;"></span>
+            <input type="text" class="console-in" id="input-text" value="">
+          </div>
+        </div>
+        <div class="pure-g">
+          <div class="pure-u-1-4"><legend><b>History buffer</b></legend></div>
+          <div class="pure-u-2-4"><legend>(UP/DOWN arrows to select)</legend></div>
+          <div class="pure-u-1-4"></div>
+        </div>
+        <div class="pure-g">
+          <div class="pure-u-1-1"><select class="console-in" id="send-history" size="5"></select></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<script type="text/javascript">console_url = "/console/text"</script>
+<script src="console.js"></script>
+<script type="text/javascript">
+  onLoad(function() {
+    fetchText(100, true);
+
+    $("#reset-button").addEventListener("click", function(e) {
+      e.preventDefault();
+      var co = $("#console");
+      co.innerHTML = "";
+      ajaxSpin('POST', "/console/reset",
+        function(resp) { showNotification("uC reset"); co.textEnd = 0; },
+        function(s, st) { showWarning("Error resetting uC"); }
+      );
+    });
+
+    $("#clear-button").addEventListener("click", function(e) {
+        e.preventDefault();
+        var co = $("#console");
+        co.innerHTML = "";
+    });
+
+    ajaxJson('GET', "/console/baud",
+      function(data) { $("#baud-sel").value = data.rate; },
+      function(s, st) { showNotification(st); }
+    );
+
+    bnd($("#baud-sel"), "change", function(ev) {
+      ev.preventDefault();
+      var baud = $("#baud-sel").value;
+      ajaxSpin('POST', "/console/baud?rate="+baud,
+        function(resp) { showNotification("" + baud + " baud set"); },
+        function(s, st) { showWarning("Error setting baud rate: " + st); }
+      );
+    });
+
+    ajaxJson('GET', "/console/fmt",
+      function(data) { $("#fmt-sel").value = data.fmt; },
+      function(s, st) { showNotification(st); }
+    );
+
+    bnd($("#fmt-sel"), "change", function(ev) {
+      ev.preventDefault();
+      var fmt = $("#fmt-sel").value;
+      ajaxSpin('POST', "/console/fmt?fmt="+fmt,
+        function(resp) { showNotification("" + fmt + " format set"); },
+        function(s, st) { showWarning("Error setting format: " + st); }
+      );
+    });
+
+    consoleSendInit();
+
+    addClass($('html')[0], "height100");
+    addClass($('body')[0], "height100");
+    addClass($('#layout'), "height100");
+    addClass($('#layout'), "flex-vbox");
+  });
+</script>
+</body></html>

+ 152 - 0
meta-id/html/attic/console.js

@@ -0,0 +1,152 @@
+//===== Fetching console text
+
+function fetchText(delay, repeat) {
+  var el = $("#console");
+  if (el.textEnd == undefined) {
+    el.textEnd = 0;
+    el.innerHTML = "";
+  }
+  window.setTimeout(function() {
+    ajaxJson('GET', console_url + "?start=" + el.textEnd,
+      function(resp) {
+        var dly = updateText(resp);
+        if (repeat) fetchText(dly, repeat);
+      },
+      function() { retryLoad(repeat); });
+  }, delay);
+}
+
+function updateText(resp) {
+  var el = $("#console");
+
+  var delay = 3000;
+  if (resp != null && resp.len > 0) {
+//    console.log("updateText got", resp.len, "chars at", resp.start);
+    var isScrolledToBottom = el.scrollHeight - el.clientHeight <= el.scrollTop + 1;
+    //console.log("isScrolledToBottom="+isScrolledToBottom, "scrollHeight="+el.scrollHeight,
+    //            "clientHeight="+el.clientHeight, "scrollTop="+el.scrollTop,
+    //            "" + (el.scrollHeight - el.clientHeight) + "<=" + (el.scrollTop + 1));
+
+    // append the text
+    if (resp.start > el.textEnd) {
+      el.innerHTML = el.innerHTML.concat("\r\n<missing lines\r\n");
+    }
+    el.innerHTML = el.innerHTML.concat(resp.text
+       .replace(/&/g, '&amp;')
+       .replace(/</g, '&lt;')
+       .replace(/>/g, '&gt;')
+       .replace(/"/g, '&quot;'));
+    el.textEnd = resp.start + resp.len;
+    delay = 500;
+
+    // scroll to bottom
+    if(isScrolledToBottom) el.scrollTop = el.scrollHeight - el.clientHeight;
+  }
+  return delay;
+}
+
+function retryLoad(repeat) {
+  fetchText(1000, repeat);
+}
+
+//===== Text entry
+
+function consoleSendInit() {
+  var sendHistory = $("#send-history");
+  var inputText = $("#input-text");
+  var inputAddCr = $("#input-add-cr");
+  var inputAddLf = $("#input-add-lf");
+
+  function findHistory(text) {
+    for (var i = 0; i < sendHistory.children.length; i++) {
+      if (text == sendHistory.children[i].value) {
+        return i;
+      }
+    }
+    return null;
+  }
+
+  function loadHistory(idx) {
+    sendHistory.value = sendHistory.children[idx].value;
+    inputText.value = sendHistory.children[idx].value;
+  }
+
+  function navHistory(rel) {
+    var idx = findHistory(sendHistory.value) + rel;
+    if (idx < 0) {
+      idx = sendHistory.children.length - 1;
+    }
+    if (idx >= sendHistory.children.length) {
+      idx = 0;
+    }
+    loadHistory(idx);
+  }
+
+  sendHistory.addEventListener("change", function(e) {
+    inputText.value = sendHistory.value;
+  });
+
+  function pushHistory(text) {
+    var idx = findHistory(text);
+    if (idx !== null) {
+      loadHistory(idx);
+      return false;
+    }
+    var newOption = m('<option>'+
+      (text
+       .replace(/&/g, '&amp;')
+       .replace(/</g, '&lt;')
+       .replace(/>/g, '&gt;')
+       .replace(/"/g, '&quot;'))
+                     +'</option>');
+    newOption.value = text;
+    sendHistory.appendChild(newOption);
+    sendHistory.value = text;
+    for (; sendHistory.children.length > 15; ) {
+      sendHistory.removeChild(sendHistory.children[0]);
+    }
+    return true;
+  }
+
+  inputText.addEventListener("keydown", function(e) {
+    switch (e.keyCode) {
+      case 38: /* the up arrow key pressed */
+        e.preventDefault();
+        navHistory(-1);
+        break;
+      case 40: /* the down arrow key pressed */
+        e.preventDefault();
+        navHistory(+1);
+        break;
+      case 27: /* the escape key pressed */
+        e.preventDefault();
+        inputText.value = "";
+        sendHistory.value = "";
+        break;
+      case 13: /* the enter key pressed */
+        e.preventDefault();
+        var text = inputText.value;
+        if (inputAddCr.checked) text += '\r';
+        if (inputAddLf.checked) text += '\n';
+        pushHistory(inputText.value);
+        inputText.value = "";
+        ajaxSpin('POST', "/console/send?text=" + encodeURIComponent(text),
+          function(resp) { showNotification("Text sent"); },
+          function(s, st) { showWarning("Error sending text"); }
+        );
+        break;
+    }
+  });
+}
+
+//===== Log page
+
+function showDbgMode(mode) {
+  var btns = $('.dbg-btn');
+  for (var i=0; i < btns.length; i++) {
+    if (btns[i].id === "dbg-"+mode)
+      addClass(btns[i], "button-selected");
+    else
+      removeClass(btns[i], "button-selected");
+  }
+}

+ 43 - 0
meta-id/html/attic/flash.html

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

+ 33 - 0
meta-id/html/attic/flash.js

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

+ 161 - 0
meta-id/html/attic/home.html

@@ -0,0 +1,161 @@
+  <div id="main">
+    <div class="header">
+      <div><img src="favicon.ico" height="64"><span class="jl">JEELABS</span></div>
+      <h1 style="margin-top:0"><span class="esp">esp</span>-link</h1>
+      <h2 id="version"></h2>
+    </div>
+
+    <div class="content">
+      <div class="pure-g">
+        <!-- LEFT COLUMN -->
+        <div class="pure-u-1 pure-u-md-1-2">
+          <div class="card">
+            <h1>System overview</h1>
+            <div id="wifi-spinner" class="spinner spinner-small"></div>
+            <table id="wifi-table" class="pure-table pure-table-horizontal" hidden><tbody>
+              <tr><td class="popup-target">Hostname</td><td>
+                <div class="click-to-edit system-name">
+                  <span class="edit-off"></span>
+                  <input class="edit-on" maxlength=31 hidden></input>
+                  <div class="popup">Click to edit!<br>Hostname displayed in menu bar
+                    and used by DHCP and mDNS</div>
+                </div>
+              </td></tr>
+              <tr><td>Network SSID</td><td id="wifi-ssid"></td></tr>
+              <tr><td>WiFi status</td><td id="wifi-status"></td></tr>
+              <tr><td>WiFi address</td><td id="wifi-ip"></td></tr>
+              <tr><td>SLIP status</td><td class="system-slip"></td></tr>
+              <tr><td>MQTT status</td><td class="system-mqtt"></td></tr>
+              <tr><td>Serial baud</td><td class="system-baud"></td></tr>
+            </tbody></table>
+          </div>
+          <div class="card">
+            <h1>Info</h1>
+            <p style="margin-bottom:0;">The JeeLabs meta-id firmware bridges the ESP8266
+            serial port to WiFi and can
+            program microcontrollers over the serial port, in particular Arduinos, AVRs, and
+            NXP's LPC800 and other ARM processors. Typical avrdude command line to
+            program an Arduino:</p>
+            <div class="tt" style="font-size:100%;">
+              /home/arduino/hardware/tools/avrdude&nbsp;\<br>
+              &nbsp;&nbsp;-DV -patmega328p \<br>
+              &nbsp;&nbsp;-Pnet:meta-id.local:23 \<br>
+              &nbsp;&nbsp;-carduino -b115200 \<br>
+              &nbsp;&nbsp;-U flash:w:my_sketch.hex:i\<br>
+              &nbsp;&nbsp;-C /home/arduino/hardware/tools/avrdude.conf
+            </div>
+            <p>where <tt>-Pnet:meta-id.local:23</tt> tells avrdude to connect to port 23 of meta-id.
+            You can substitute the IP address of your meta-id for meta-id.local if necessary.
+            Please refer to
+            <a href="https://github.com/jeelabs/meta-id/blob/master/README.md">the online README</a>
+            for up-to-date help.</p>
+          </div>
+        </div>
+        <!-- RIGHT COLUMN -->
+        <div class="pure-u-1 pure-u-md-1-2">
+          <div class="card">
+            <h1>Pin assignment</h1>
+            <div id="pin-spinner" class="spinner spinner-small"></div>
+            <div id="pin-table" hidden>
+              <form action="#" id="pinform" class="pure-form pure-form-aligned form-narrow">
+                <div class="pure-control-group">
+                  <label for="pin-preset">Presets</label>
+                  <select id="pin-preset" class="pure-button">
+                    <option value="" selected disabled></option>
+                  </select>
+                </div>
+                <hr>
+                <div class="pure-control-group">
+                  <label for="pin-reset">Reset</label>
+                  <select id="pin-reset"></select>
+                  <div class="popup">Connect to &#xb5;C reset pin for programming and reset-&#xb5;C function</div>
+                </div>
+                <div class="pure-control-group">
+                  <label for="pin-isp">ISP/Flash</label>
+                  <select id="pin-isp"></select>
+                  <div class="popup">Second signal to program &#xb5;C.
+                    AVR:not used, esp8266:gpio2, ARM:ISP</div>
+                </div>
+                <div class="pure-control-group">
+                  <label for="pin-conn">Conn LED</label>
+                  <select id="pin-conn"></select>
+                  <div class="popup">LED to show WiFi connectivity</div>
+                </div>
+                <div class="pure-control-group">
+                  <label for="pin-ser">Serial LED</label>
+                  <select id="pin-ser"></select>
+                  <div class="popup">LED to show serial activity</div>
+                </div>
+                <div class="pure-control-group">
+                  <label for="pin-swap">UART pins</label>
+                  <select id="pin-swap" class="pure-button">
+                    <option value="0">normal</option>
+                    <option value="1">swapped</option>
+                  </select>
+                  <div class="popup">Swap UART0 pins to avoid ROM boot message.<br>Normal is
+                    TX on gpio1/TX0 and RX on gpio3/RX0, swapped is TX on gpio15 and RX on gpio13.
+                  </div>
+                </div>
+                <div class="pure-control-group">
+                  <label for="pin-rxpup" class="pure-checkbox">RX pull-up</label>
+                  <input id="pin-rxpup" type="checkbox">
+                  <div class="popup">Enable internal 40K pull-up on RX</div>
+                </div>
+                <button id="set-pins" type="submit" class="pure-button button-primary">Change!</button>
+              </form>
+            </div>
+          </div>
+          <div class="card">
+            <h1>System details</h1>
+            <div id="system-spinner" class="spinner spinner-small"></div>
+            <table id="system-table" class="pure-table pure-table-horizontal" hidden><tbody>
+            <tr><td>WiFi mode</td><td id="wifi-mode"></td></tr>
+            <tr><td>WiFi channel</td><td id="wifi-chan"></td></tr>
+            <tr><td>Flash chip ID</td><td>
+              <div>
+                <span class="system-id"></span>
+                <div class="popup pop-left">Common IDs: 4016=4MB, 4014=1MB, 4013=512KB</div>
+              </div>
+            </td></tr>
+            <tr><td>Flash size</td><td>
+              <div>
+                <span class="system-size"></span>
+                <div class="popup pop-left">Size configured into bootloader, must match chip size</div>
+              </div>
+            </td></tr>
+            <tr><td>Webpage size</td><td>
+              <div>
+                <span class="system-upload-size"></span>
+                <div class="popup pop-left">The maximal size of the custom web page a user can upload.</div>
+              </div>
+            </td></tr>
+            <tr><td>Current partition</td><td class="system-partition"></td></tr>
+            <tr><td colspan=2 class="popup-target">Description:<br>
+                <div class="click-to-edit system-description">
+                  <span class="edit-off" style="display:block; width:auto;"></span>
+                  <textarea class="edit-on" rows=3 maxlength=127 hidden> </textarea>
+                  <div class="popup">Click to edit!<br>A short description or memo for this meta-id
+                    module, 128 chars max</div>
+                </div>
+            </td></tr>
+            </tbody></table>
+          </div>
+        </div>
+      </div>
+      <div class="pure-g">
+      </div>
+    </div>
+  </div>
+</div>
+
+<script type="text/javascript">
+onLoad(function() {
+  makeAjaxInput("system", "description");
+  makeAjaxInput("system", "name");
+  fetchPins();
+  getWifiInfo();
+  getSystemInfo();
+  bnd($("#pinform"), "submit", setPins);
+});
+</script>
+</body></html>

+ 25 - 0
meta-id/html/attic/init_old.html

@@ -0,0 +1,25 @@
+<div id=menu>
+<ul>
+Meta
+</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+<h1>META
+</h1>
+<form action="/meta/userpass" id="passform" class="pure-form">
+<legend>Enter your personal password here to unlock more meta content </legend>
+<div class="form-horizontal">
+<label for="password" style="margin-right:1em">
+<input type="password" name="passwd" id="passwd"/>
+PASSWORD
+</label>
+</div>
+<button id="special-button" type="submit" class="pure-button button-primary">Set password!</button>
+</form>
+<hr/>
+<a href="/welcome.html">for now all the content is indexed on welcome.html</a>
+
+</div>
+</div>
+</body></html>

+ 60 - 0
meta-id/html/attic/log.html

@@ -0,0 +1,60 @@
+  <div id="main">
+    <div class="header">
+      <h1>Debug Log</h1>
+    </div>
+
+    <div class="content">
+      <p>The debug log shows the most recent characters printed by the meta-id software itself to
+      its own debug log.</p>
+      <div class="pure-g">
+        <p class="pure-u-1-4" style="vertical-align: baseline;width:40%">
+          <a id="refresh-button" class="pure-button button-primary" href="#">Refresh</a>
+          &nbsp;<a id="reset-button" class="dbg-btn pure-button button-primary" href="#">Reset meta-id</a>
+        </p>
+        <p class="pure-u-3-4" style="vertical-align: baseline;width:60%">
+          UART debug log:
+          <a id="dbg-auto" class="dbg-btn pure-button" href="#">auto</a>
+          <a id="dbg-off" class="dbg-btn pure-button" href="#">off</a>
+          <a id="dbg-on0" class="dbg-btn pure-button" href="#">on uart0</a>
+          <a id="dbg-on1" class="dbg-btn pure-button" href="#">on uart1</a>
+        </p>
+      </div>
+      <pre id="console" class="console" style="margin-top: 0px;"></pre>
+    </div>
+  </div>
+</div>
+
+<script type="text/javascript">console_url = "/log/text"</script>
+<script src="console.js"></script>
+<script type="text/javascript">
+  onLoad(function() {
+    fetchText(100, false);
+
+    $("#refresh-button").addEventListener("click", function(e) {
+      e.preventDefault();
+      fetchText(100, false);
+    });
+
+    $("#reset-button").addEventListener("click", function (e) {
+        e.preventDefault();
+        var co = $("#console");
+        co.innerHTML = "";
+        ajaxSpin('POST', "/log/reset",
+          function (resp) { showNotification("Resetting meta-id"); co.textEnd = 0; fetchText(2000, false); },
+          function (s, st) { showWarning("Error resetting meta-id"); }
+        );
+    });
+
+    ["auto", "off", "on0", "on1"].forEach(function(mode) {
+      bnd($('#dbg-'+mode), "click", function(el) {
+        ajaxJsonSpin('POST', "/log/dbg?mode="+mode,
+          function(data) { showNotification("UART mode " + data.mode); showDbgMode(data.mode); },
+          function(s, st) { showWarning("Error setting UART mode: " + st); }
+        );
+      });
+    });
+
+    ajaxJson('GET', "/log/dbg", function(data) { showDbgMode(data.mode); }, function() {});
+  });
+</script>
+</body></html>

+ 80 - 0
meta-id/html/attic/meta.html

@@ -0,0 +1,80 @@
+<div id=menu>
+	<ul>
+		<li>Meta</li>
+		<li><a href="text/text.html">Text</a></li>
+		<li><a href="wifi/wifiAp.html">Wifi AP</a></li>
+		<li><a href="wifi/wifiSta.html">Wifi Client</a></li>
+		<li><a href="web-server.html">Upload</a></li>
+	</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+            <h1>META
+            </h1>
+            <div id="spinner" class="spinner" hidden></div>
+<div id="notification" class="spinner" hidden></div>
+<div id="warning" class="spinner" hidden></div>
+<div id="meta-spinner" class="spinner" hidden></div>
+
+<!--            <div id="wav">
+              <div class="pure-form-stacked">
+				  <a href='meta.wav'>soundtest</a>
+				  <audio controls>
+					  <source src="meta.wav" type="audio/wav"/>
+					  no audio tag support in your browser !
+				  </audio>
+			  </div>
+			</div>-->
+            <div id="gpio">
+              <div class="pure-form-stacked">
+                <label>GPIO 0</label>
+                <div id="meta-gpio-00"></div>
+                <label>GPIO 1</label>
+                <div id="meta-gpio-01"></div>
+                <label>GPIO 2</label>
+                <div id="meta-gpio-02"></div>
+                <label>GPIO 3</label>
+                <div id="meta-gpio-03"></div>
+                <label>GPIO 4</label>
+                <div id="meta-gpio-04"></div>
+                <label>GPIO 5</label>
+                <div id="meta-gpio-05"></div>
+<!--                <label>GPIO 6</label>
+                <div id="meta-gpio-06"></div>
+                <label>GPIO 7</label>
+                <div id="meta-gpio-07"></div>
+                <label>GPIO 8</label>
+                <div id="meta-gpio-08"></div>-->
+                <label>GPIO 9</label>
+                <div id="meta-gpio-09"></div>
+                <label>GPIO 10</label>
+                <div id="meta-gpio-10"></div>
+<!--                <label>GPIO 11</label>
+                <div id="meta-gpio-11"></div>-->
+                <label>GPIO 12</label>
+                <div id="meta-gpio-12"></div>
+                <label>GPIO 13</label>
+                <div id="meta-gpio-13"></div>
+                <label>GPIO 14</label>
+                <div id="meta-gpio-14"></div>
+                <label>GPIO 15</label>
+                <div id="meta-gpio-15"></div>
+			<br/><br/>
+              </div>
+		</div>
+</div>
+<script src="meta.js">
+</script>
+<script type="text/javascript">
+function toggle(el){
+	del=$("#"+el);
+	if (del.visibility != 'hidden')
+		del.visibility != 'hidden';
+	else
+	del.visibility = 'visible';
+}
+onLoad(function() {
+  fetchMeta();
+});
+</script>
+</body></html>

+ 71 - 0
meta-id/html/attic/meta.js

@@ -0,0 +1,71 @@
+function displayMeta(data) {
+  Object.keys(data).forEach(function (v) {
+    el = $("#" + v);
+    if (el != null) {
+      if (el.nodeName === "DIV"){
+		var toouthigh=true;
+		var tooutlow=true;
+		var toin=true;
+			el.innerHTML = "BLANK";
+		if (data[v]==-2){
+			tooutlow=false;
+			el.innerHTML = "OUT LOW";
+			}
+		if (data[v]==-1){
+			toouthigh=false;
+			el.innerHTML = "OUT HIGH";
+			}
+		if (data[v]==0){
+			el.innerHTML = "not configured";
+			}
+		if (data[v]>0){
+			toin=false;
+			el.innerHTML = "IN "+data[v];
+			}
+		if(toin){
+			el.innerHTML+=" | <input type='button' value='TO INPUT' onclick='setMeta(\""+v.substr(-2)+"\",\"1\")'/>";
+		}
+		if(tooutlow){
+			el.innerHTML+=" | <input type='button' value='TO OUTPUT LOW' onclick='setMeta(\""+v.substr(-2)+"\",\"-2\")'/>";
+		}
+		if(toouthigh){
+			el.innerHTML+=" | <input type='button' value='TO OUTPUT HIGH' onclick='setMeta(\""+v.substr(-2)+"\",\"-1\")'/>";
+		}
+    }}});
+
+}
+
+function toggle(el){
+	if(!$('#'+el).hasAttribute("hidden")){
+  $("#"+el).setAttribute("hidden", "");}
+  else{$("#meta-form").removeAttribute("hidden");}
+}
+	
+function fetchMeta() {
+	console.log("fetchMeta !");
+  ajaxJson("GET", "/meta/gpio", displayMeta, function () {
+    window.setTimeout(fetchMeta, 1000);
+  });
+}
+
+function changeMetaStatus(e) {
+  e.preventDefault();
+  var v = document.querySelector('input[name="meta-status-topic"]').value;
+  ajaxSpin("POST", "/meta?meta-status-topic=" + v, function () {
+    showNotification("meta status settings updated");
+  }, function (s, st) {
+    showWarning("Error: " + st);
+    window.setTimeout(fetchMeta, 100);
+  });
+}
+
+function setMeta(name, v) {
+  ajaxSpin("POST", "/meta/gpio?num=" + name+"&v=" + v, function () {
+    var n = name.replace("-enable", "");
+    showNotification(n + " is now " + (v ? "enabled" : "disabled"));
+    window.setTimeout(fetchMeta, 100);
+  }, function () {
+    showWarning("Enable/disable failed");
+    window.setTimeout(fetchMeta, 100);
+  });
+}

+ 228 - 0
meta-id/html/attic/meta_old.css

@@ -0,0 +1,228 @@
+/* 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%;
+}
+
+.square-box { 
+    position: relative;
+	width:50%;
+	overflow:hidden;
+	margin:auto;
+  }
+
+.square-box:before { 
+	content:"";
+	display:block;
+	padding-top:100%;
+}
+
+.square-content{
+	position:absolute;
+	top:0;
+	left:0;
+	right:0;
+    border: 5px solid black;
+    width: 90%;
+    margin-left:5%;
+    margin-top:5%;
+	text-align:center;
+	max-height:90%;
+	overflow-y:scroll;
+}
+
+div#menu {
+    -ms-transform: rotate(-90deg); /* IE 9 */
+    -webkit-transform: rotate(-90deg); /* Safari */
+    transform: rotate(-90deg);
+	transform-origin: top right;
+    text-align:right;
+	top:0px;
+	width:1024px;
+	left:-1024px;
+	border-bottom:4px solid grey;
+	position:absolute;
+	padding:4px
+}
+
+div.layout{
+	margin-left:25%;
+	margin-right:25%;
+	min-height:1024px;
+	font-family:sans-serif;
+	font-weight:100%;
+}
+
+div.box{
+	height:50%;
+	margin-top:25%;
+	min-height:512px;
+	background-color:white;
+}
+DIV.container {
+    min-height: 10em;
+    display: table-cell;
+    vertical-align: middle 
+S}
+
+div#menu ul li a{
+	text-decoration:none;
+}
+
+div#menu ul li a:after{
+	content:" | ";
+}
+
+div#menu ul li{
+	text-transform:capitalize;
+	font-weight:bold;
+	font-size:x-large;
+	padding: 4px;
+    background-color: #fff;
+}
+
+li {
+    display: inline;
+    float: right;
+}
+
+
+a.greybg {
+    display: block;
+    padding: 8px;
+    background-color: #dddddd;
+}
+.square {
+    border: 5px solid black;
+    position: relative;
+    text-align: center;
+}
+.sixty {
+    width: 60%;
+    margin-left: 20%;
+    margin-top: 20%;
+}
+.fifty {
+    width: 50%;
+    margin-left: 20%;
+    margin-top: 20%;
+	border:4px solid black;
+}
+
+
+/*===== 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: vibration 1s infinite linear;
+  animation: vibration 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 vibration {
+  from {
+    -webkit-transform: height:110%;
+  }
+  to {
+    -webkit-transform: height:100%;
+  }
+}
+
+
+@keyframes vibration {
+  from {
+    -webkit-transform: height:110%;
+    transform: height:110%;
+  }
+  to {
+    -webkit-transform: height:100%;
+    transform: height:100%;
+  }
+}
+
+@keyframes rotation {
+  from {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  to {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+
+table{
+	margin:auto;
+}
+
+/* wifi AP selection form */
+#aps label div {
+  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%;
+}
+
+.lock-icon {
+  background-image: url("/wifi/icons.png");
+  background-color: transparent;
+  width: 32px;
+  height: 32px;
+  display: inline-block;
+}
+
+

+ 123 - 0
meta-id/html/attic/mqtt.html

@@ -0,0 +1,123 @@
+  <div id="main">
+<div id="spinner" class="spinner" hidden></div>
+<div id="notification" class="spinner" hidden></div>
+<div id="warning" class="spinner" hidden></div>
+    <div class="header">
+      <h1>REST &amp; MQTT</h1>
+    </div>
+
+    <div class="content">
+      <div class="pure-g">
+        <div class="pure-u-1"><div class="card">
+          <p>The REST &amp; MQTT support uses the SLIP protocol over the serial port to enable
+          the attached microcontroller to initiate outbound connections.
+          The REST support lets the uC initiate simple HTTP requests while the MQTT support
+          lets it communicate with an MQTT server bidirectionally at QoS 0 thru 2.</p>
+          <p>The MQTT support is in the form of a built-in client that connects to a server
+          using parameters set below and stored in meta-id's flash settings. This allows 
+          meta-id to take care of connection parameters and disconnect/reconnect operations.</p>
+          <p>The MQTT client also supports sending periodic status messages about meta-id itself,
+          including WiFi RSSI, and free heap memory.</p>
+          <div class="form-horizontal">
+            <input type="checkbox" name="slip-enable"/>
+            <label>Enable SLIP on serial port</label>
+          </div>
+        </div></div>
+      </div>
+      <div class="pure-g">
+        <div class="pure-u-1 pure-u-md-1-2">
+          <div class="card">
+            <h1>MQTT
+              <div id="mqtt-spinner" class="spinner spinner-small"></div>
+            </h1>
+            <form action="#" id="mqtt-form" class="pure-form" >
+              <div>
+                <input type="checkbox" name="mqtt-enable"/>
+                <label>Enable MQTT client</label>
+              </div>
+              <div>
+                <label>MQTT client state: </label>
+                <b id="mqtt-state"></b>
+              </div>
+              <br>
+              <legend>MQTT server settings</legend>
+              <div class="pure-form-stacked">
+                <label>Server hostname or IP</label>
+                <input type="text" name="mqtt-host"/>
+                <label>Server port</label>
+                <input type="text" name="mqtt-port"/>
+                <label>Client ID</label>
+                <input type="text" name="mqtt-client-id"/>
+                <label>Client Timeout (seconds)</label>
+                <input type="text" name="mqtt-timeout" />
+                <label>Keep Alive Interval (seconds)</label>
+                <input type="text" name="mqtt-keepalive" />
+                <label>Username</label>
+                <input type="text" name="mqtt-username"/>
+                <label>Password</label>
+                <input type="password" name="mqtt-password"/>
+              </div>
+              <button id="mqtt-button" type="submit" class="pure-button button-primary">
+                Update server settings!
+              </button>
+            </form>
+          </div>
+        </div>
+        <div class="pure-u-1 pure-u-md-1-2">
+          <div class="card">
+            <h1>Status reporting
+              <div id="mqtt-status-spinner" class="spinner spinner-small"></div>
+            </h1>
+            <form action="#" id="mqtt-status-form" class="pure-form" >
+              <div class="form-horizontal">
+                <input type="checkbox" name="mqtt-status-enable"/>
+                <label>Enable status reporting via MQTT</label>
+              </div>
+              <br>
+              <div class="pure-form-stacked">
+                <label>Status topic</label>
+                <input type="text" name="mqtt-status-topic"/>
+                Message: <tt id="mqtt-status-value"></tt>
+                <div class="popup">MQTT topic to which status message is sent</div>
+              </div>
+              <button id="mqtt-status-button" type="submit" class="pure-button button-primary">
+                Update status settings!
+              </button>
+            </form>
+          </div>
+          <div class="card">
+            <h1>Status reporting
+              <div id="mqtt-test-spinner" class="spinner spinner-small"></div>
+            </h1>
+            <form action="#" id="mqtt-test-form" class="pure-form" >
+              <div class="form-horizontal">
+                <input name="mqtt-test"/>
+                <label>Enable status reporting via MQTT</label>
+              </div>
+              <br>
+              <button id="mqtt-test-button" type="submit" class="pure-button button-primary">
+                Send !
+              </button>
+            </form>
+          </div>
+          <div class="card">
+            <h1>REST</h1>
+            <p>REST requests are enabled as soon as SLIP is enabled.
+            There are no REST-specific settings.</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<script src="mqtt.js"></script>
+<script type="text/javascript">
+onLoad(function() {
+  fetchMqtt();
+  bnd($("#mqtt-form"), "submit", changeMqtt);
+  bnd($("#mqtt-test-form"), "submit", changeMqttTest);
+  bnd($("#mqtt-status-form"), "submit", changeMqttStatus);
+});
+</script>
+</body></html>

+ 87 - 0
meta-id/html/attic/mqtt.js

@@ -0,0 +1,87 @@
+//===== MQTT cards
+
+function changeMqtt(e) {
+  e.preventDefault();
+  var url = "mqtt?1=1";
+  var i, inputs = document.querySelectorAll('#mqtt-form input');
+  for (i = 0; i < inputs.length; i++) {
+    if (inputs[i].type != "checkbox")
+      url += "&" + inputs[i].name + "=" + inputs[i].value;
+  };
+
+  hideWarning();
+  var cb = $("#mqtt-button");
+  addClass(cb, 'pure-button-disabled');
+  ajaxSpin("POST", url, function (resp) {
+    showNotification("MQTT updated");
+    removeClass(cb, 'pure-button-disabled');
+  }, function (s, st) {
+    showWarning("Error: " + st);
+    removeClass(cb, 'pure-button-disabled');
+    window.setTimeout(fetchMqtt, 100);
+  });
+}
+
+function displayMqtt(data) {
+  Object.keys(data).forEach(function (v) {
+    el = $("#" + v);
+    if (el != null) {
+      if (el.nodeName === "INPUT") el.value = data[v];
+      else el.innerHTML = data[v];
+      return;
+    }
+    el = document.querySelector('input[name="' + v + '"]');
+    if (el != null) {
+      if (el.type == "checkbox") el.checked = data[v] > 0;
+      else el.value = data[v];
+    }
+  });
+  $("#mqtt-spinner").setAttribute("hidden", "");
+  $("#mqtt-status-spinner").setAttribute("hidden", "");
+  $("#mqtt-form").removeAttribute("hidden");
+  $("#mqtt-status-form").removeAttribute("hidden");
+
+  var i, inputs = $("input");
+  for (i = 0; i < inputs.length; i++) {
+    if (inputs[i].type == "checkbox")
+      inputs[i].onclick = function () { setMqtt(this.name, this.checked) };
+  }
+}
+
+function fetchMqtt() {
+  ajaxJson("GET", "/mqtt", displayMqtt, function () {
+    window.setTimeout(fetchMqtt, 1000);
+  });
+}
+
+function changeMqttStatus(e) {
+  e.preventDefault();
+  var v = document.querySelector('input[name="mqtt-status-topic"]').value;
+  ajaxSpin("POST", "/mqtt?mqtt-status-topic=" + v, function () {
+    showNotification("MQTT status settings updated");
+  }, function (s, st) {
+    showWarning("Error: " + st);
+    window.setTimeout(fetchMqtt, 100);
+  });
+}
+
+function changeMqttTest(e) {
+  e.preventDefault();
+  var v = document.querySelector('input[name="mqtt-test-topic"]').value;
+  ajaxSpin("POST", "/mqtt?mqtt-test-topic=" + v, function () {
+    showNotification("MQTT test settings updated");
+  }, function (s, st) {
+    showWarning("Error: " + st);
+    window.setTimeout(fetchMqtt, 100);
+  });
+}
+
+function setMqtt(name, v) {
+  ajaxSpin("POST", "/mqtt?" + name + "=" + (v ? 1 : 0), function () {
+    var n = name.replace("-enable", "");
+    showNotification(n + " is now " + (v ? "enabled" : "disabled"));
+  }, function () {
+    showWarning("Enable/disable failed");
+    window.setTimeout(fetchMqtt, 100);
+  });
+}

+ 97 - 0
meta-id/html/attic/services.html

@@ -0,0 +1,97 @@
+  <div id="main">
+    <div class="header">
+        <h1>Services</h1>
+    </div>
+
+    <div class="content">
+      <div class="pure-g">
+        <div class="pure-u-1 pure-u-md-1-2">
+          <div class="card">
+            <h1>
+              Syslog
+              <div id="syslog-spinner" class="spinner spinner-small"></div>
+            </h1>
+            <form action="#" id="Syslog-form" class="pure-form" hidden>
+              <div class="pure-form-stacked">
+                <label>Syslog Host</label>
+                <input type="text" name="syslog_host" />
+                <div class="popup">Meta-id sends event/debug info to this syslog host 
+                  (hostname:port). Leave empty to disable syslog.</div>
+              </div>
+              <div class="pure-form-stacked">
+                <label>Min Heap</label>
+                <div>
+                  <input type="text" name="syslog_minheap" />
+                  <div class="popup">Stop sending syslog if free heap drops below this many bytes</div>
+                </div>
+                <div>
+                  <label>Filter</label>
+                  <select name="syslog_filter" href="#">
+                    <option value="0">EMERG</option>
+                    <option value="1">ALERT</option>
+                    <option value="2">CRIT</option>
+                    <option value="3">ERR</option>
+                    <option value="4">WARNING</option>
+                    <option value="5">NOTICE</option>
+                    <option value="6">INFO</option>
+                    <option value="7">DEBUG</option>
+                  </select>
+                  <div class="popup">Minimum severity to send</div>
+                </div>
+              </div>
+              <div>
+                <input type="checkbox" name="syslog_showtick" />
+                <label>Include meta-id millisecond ticker</label>
+              </div>
+              <div>
+                <input type="checkbox" name="syslog_showdate" />
+                <label>Include meta-id datetime</label>
+                <div class="popup">Some syslog servers rotate log if timestamp is in the past so disable to prevent this</div>
+              </div>
+              <button id="Syslog-button" type="submit" class="pure-button button-primary">
+                Update Syslog settings!
+              </button>
+            </form>
+          </div>
+        </div>
+        <div class="pure-u-1 pure-u-md-1-2">
+          <div class="card">
+            <h1>
+              SNTP
+              <div id="sntp-spinner" class="spinner spinner-small"></div>
+            </h1>
+            <form action="#" id="SNTP-form" class="pure-form" hidden>
+              <div class="pure-form-stacked">
+                <div>
+                  <label>SNTP Server</label>
+                  <input type="text" name="sntp_server" />
+                  <div class="popup">Simple Network Time Protocol server to query.
+                    Leave empty to disable SNTP</div>
+                </div>
+                <div>
+                  <label>Timezone Offset</label>
+                  <input type="text" name="timezone_offset" />
+                  <div class="popup">Offset hours to apply (no daylight savings support)</div>
+                </div>
+              </div>
+              <button id="SNTP-button" type="submit" class="pure-button button-primary">
+                Update SNTP settings!
+              </button>
+            </form>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<script src="services.js"></script>
+<script type="text/javascript">
+onLoad(function() {
+  fetchServices();
+  bnd($("#Syslog-form"), "submit", changeServices);
+  bnd($("#SNTP-form"), "submit", changeServices);
+  bnd($("#mDNS-form"), "submit", changeServices);
+});
+</script>
+</body></html>

+ 68 - 0
meta-id/html/attic/services.js

@@ -0,0 +1,68 @@
+function changeServices(e) {
+  e.preventDefault();
+  var url = "services/update?1=1";
+  var i, inputs = document.querySelectorAll("#" + e.target.id + " input,select");
+  for (i = 0; i < inputs.length; i++) {
+    if (inputs[i].type == "checkbox") {
+      if (inputs[i].name.slice(-6) == "enable")
+        continue;
+      var val = (inputs[i].checked) ? 1 : 0;
+      url += "&" + inputs[i].name + "=" + val;
+    }
+    else
+      url += "&" + inputs[i].name + "=" + inputs[i].value;
+  };
+
+  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");
+  }, function (s, st) {
+    showWarning("Error: " + st);
+    removeClass(cb, "pure-button-disabled");
+    window.setTimeout(fetchServices, 100);
+  });
+}
+
+function displayServices(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];
+    }
+  });
+
+  $("#syslog-spinner").setAttribute("hidden", "");
+  $("#sntp-spinner").setAttribute("hidden", "");
+
+  if (data.syslog_host !== undefined) {
+    $("#Syslog-form").removeAttribute("hidden");
+  } else {
+    // syslog disabled...
+    $("#Syslog-form").parentNode.setAttribute("hidden", "");
+  }
+  $("#SNTP-form").removeAttribute("hidden");
+
+  var i, inputs = $("input");
+}
+
+function fetchServices() {
+  ajaxJson("GET", "/services/info", displayServices, function () {
+    window.setTimeout(fetchServices, 1000);
+  });
+}

+ 484 - 0
meta-id/html/attic/ui.js

@@ -0,0 +1,484 @@
+//===== 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('<h1>Hello</h1>');
+ *   document.body.appendChild(el);
+ *
+ * Copyright (C) 2011 Jed Schmidt <http://jed.is> - 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 <http://jed.is> - 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 <http://jed.is>
+ * 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<l; i++) {
+    if (cls[i] === cl) cls.splice(i, 1);
+  }
+  el.className = cls.join(' ');
+  return cls.length != l
+}
+function toggleClass(el, cl) {
+  if (!removeClass(el, cl)) addClass(el, cl);
+}
+
+//===== AJAX
+
+function ajaxReq(method, url, ok_cb, err_cb, data) {
+  var xhr = j();
+  xhr.open(method, url, true);
+  var timeout = setTimeout(function() {
+    xhr.abort();
+    console.log("XHR abort:", method, url);
+    xhr.status = 599;
+    xhr.responseText = "request time-out";
+  }, 9000);
+  xhr.onreadystatechange = function() {
+    if (xhr.readyState != 4) { return; }
+    clearTimeout(timeout);
+    if (xhr.status >= 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(data);
+  } 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('<div id="spinner" class="spinner" hidden></div>'), o);
+  // notification boxes
+  l.insertBefore(m(
+    '<div id="messages"><div id="warning" hidden></div><div id="notification" hidden></div></div>'), o);
+  // menu hamburger button
+  l.insertBefore(m('<a href="#menu" id="menuLink" class="menu-link"><span></span></a>'), o);
+  // menu left-pane
+  var mm = m(
+   '<div id="menu">\
+      <div class="pure-menu">\
+        <a class="pure-menu-heading" href="https://github.com/jeelabs/meta-id">\
+        <img src="/favicon.ico" height="32">&nbsp;meta-id</a>\
+        <div class="pure-menu-heading system-name" style="padding: 0px 0.6em"></div>\
+        <ul id="menu-list" class="pure-menu-list"></ul>\
+      </div>\
+    </div>\
+    ');
+  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.length; i+=2) {
+        var href = data.menu[i+1];
+        html = html.concat(" <li class=\"pure-menu-item" +
+            (path === href ? " pure-menu-selected" : "") + "\">" +
+            "<a href=\"" + href + "\" class=\"pure-menu-link\">" +
+            data.menu[i] + "</a></li>");
+      }
+      $("#menu-list").innerHTML = html;
+
+      var v = $("#version");
+      if (v != null) { v.innerHTML = data.version; }
+
+      $('title')[0].innerHTML = data["name"];
+      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 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);
+    });
+  });
+}
+
+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;
+    }
+  });
+}
+
+//===== 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);
+}
+
+//===== GPIO Pin mux card
+
+var pinPresets = {
+  // array: reset, isp, conn, ser, swap, rxpup
+  "esp-01":       [  0, -1, 2, -1, 0, 1 ],
+  "esp-12":       [ 12, 14, 0,  2, 0, 1 ],
+  "esp-12 swap":  [  1,  3, 0,  2, 1, 1 ],
+  "esp-bridge":   [ 12, 13, 0, 14, 0, 0 ],
+  "wifi-link-12": [  1,  3, 0,  2, 1, 0 ],
+};
+
+function createPresets(sel) {
+  for (var p in pinPresets) {
+    var opt = m('<option value="' + p + '">' + p + '</option>');
+    sel.appendChild(opt);
+  }
+
+  function applyPreset(v) {
+    var pp = pinPresets[v];
+    if (pp === undefined) return pp;
+//    console.log("apply preset:", v, pp);
+    function setPP(k, v) { $("#pin-"+k).value = v; };
+    setPP("reset", pp[0]);
+    setPP("isp",   pp[1]);
+    setPP("conn",  pp[2]);
+    setPP("ser",   pp[3]);
+    setPP("swap",  pp[4]);
+    $("#pin-rxpup").checked = !!pp[5];
+    sel.value = 0;
+  };
+
+  bnd(sel, "change", function(ev) {
+    ev.preventDefault();
+    applyPreset(sel.value);
+  });
+}
+
+function displayPins(resp) {
+  function createSelectForPin(name, v) {
+    var sel = $("#pin-"+name);
+    addClass(sel, "pure-button");
+    sel.innerHTML = "";
+    [-1,0,1,2,3,4,5,12,13,14,15].forEach(function(i) {
+      var opt = document.createElement("option");
+      opt.value = i;
+      if (i >= 0) opt.innerHTML = "gpio"+i;
+      else opt.innerHTML = "disabled";
+      if (i===1) opt.innerHTML += "/TX0";
+      if (i===2) opt.innerHTML += "/TX1";
+      if (i===3) opt.innerHTML += "/RX0";
+      if (i==v) opt.selected = true;
+      sel.appendChild(opt);
+    });
+    var pup = $(".popup", sel.parentNode);
+    if (pup !== undefined) hidePopup(pup[0]);
+  };
+
+  createSelectForPin("reset", resp["reset"]);
+  createSelectForPin("isp", resp["isp"]);
+  createSelectForPin("conn", resp["conn"]);
+  createSelectForPin("ser", resp["ser"]);
+  $("#pin-swap").value = resp["swap"];
+  $("#pin-rxpup").checked = !!resp["rxpup"];
+  createPresets($("#pin-preset"));
+
+  $("#pin-spinner").setAttribute("hidden", "");
+  $("#pin-table").removeAttribute("hidden");
+}
+
+function fetchPins() {
+  ajaxJson("GET", "/pins", displayPins, function() {
+    window.setTimeout(fetchPins, 1000);
+  });
+}
+
+function setPins(ev) {
+  ev.preventDefault();
+  var url = "/pins";
+  var sep = "?";
+  ["reset", "isp", "conn", "ser", "swap"].forEach(function(p) {
+    url += sep + p + "=" + $("#pin-"+p).value;
+    sep = "&";
+  });
+  url += "&rxpup=" + ($("#pin-rxpup").checked ? "1" : "0");
+//  console.log("set pins: " + url);
+  ajaxSpin("POST", url, function() {
+    showNotification("Pin assignment changed");
+  }, function(status, errMsg) {
+    showWarning(errMsg);
+    window.setTimeout(fetchPins, 100);
+  });
+}

+ 21 - 0
meta-id/html/attic/user.html

@@ -0,0 +1,21 @@
+<div id=menu>
+<ul>
+Meta
+</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+<h1>USER
+</h1>
+<h2>you are authenticated, congratulations !</h2>
+<i> user content goes here</i>
+<ul>
+<li>print wifi information (AJAX)</li>
+<li>link to wifi configuration</li>
+<li>if internet connection send data via mqtt (AJAX)</li>
+</ul>
+<hr/>
+<a href="/welcome.html">for now all the content is indexed on welcome.html</a>
+</div>
+</div>
+</body></html>

+ 242 - 0
meta-id/html/attic/userpage.js

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

+ 59 - 0
meta-id/html/attic/web-server.html

@@ -0,0 +1,59 @@
+<div id=menu>
+	<ul>
+		<li>Upload</li>
+		<li><a href="/meta.html">Meta</a></li>
+		<li><a href="text/text.html">Text</a></li>
+		<li><a href="/wifi/wifiSta.html">Wifi Client</a></li>
+		<li><a href="/wifi/wifiAp.html">Wifi AP</a></li>
+	</ul>
+</div>
+<div class="square-box">
+<div class="square-content">  <div id="main">
+    <div class="header">
+      <h1>Web Server</h1>
+    </div>
+
+    <div class="content">
+      <p>User defined web pages can be uploaded to meta-id. This is useful if meta-id acts as a web server while MCU provides
+        the measurement data.</p>
+
+      <form method="post" action="web-server/upload" name="submit" enctype="multipart/form-data" onSubmit="return onSubmit()">
+        The custom web page to upload: <input type="file" name="webpage" multiple>
+        <input type="submit" name="submit" value="Submit">
+      </form>
+    <div id="fs">
+      </div>
+	<br/><br/>
+    </div>
+    
+  </div>
+  
+  <script>
+   var allowSubmit = true;
+
+	function displayList(data){
+	var tmp = false;
+    el = $("#fs");
+	el.innerHTML+="<h2>files on espfs</h2>";
+  Object.keys(data).forEach(function (v) {
+    if (el != null) {
+		if(tmp){
+			el.innerHTML+="<a href='"+data[v]+"'>"+tmp+"</a><br/>";
+			tmp=false;
+		}
+		else{
+			tmp=data[v];
+		}
+	}});
+	}
+	onLoad(function (){
+		  ajaxJson("GET", "/web-server/list", displayList);
+	});
+   function onSubmit() {
+      setTimeout(function() {
+                    window.location.reload();
+                 }, 1000);
+      return true;
+   }
+</script>
+</body></html>

+ 38 - 0
meta-id/html/attic/welcome-old.html

@@ -0,0 +1,38 @@
+<div id=menu>
+	<ul>
+		<li>Meta</li>
+		<li><a href="/wifi/wifiSta.html">Wifi</a></li>
+		<li><a href="/text/text.html">Text</a></li>
+		<li><a href="/meta.html">GPIO</a></li>
+	</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+<h1>WELCOME
+</h1>
+<i> public content goes here</i><br/><br/>
+<form><button id="special-button" type="submit" class="pure-button button-primary">send data</button></form>
+<br/><br/>
+
+<a href="http://x.ikujam.org/mqtt/"> see data</a>
+<br/><br/><div id="sendfeedback"></div>
+</div>
+</div>
+<script type="text/javascript">
+
+function displaySend(data){
+	$("#sendfeedback").innerHTML=data;
+	
+	}
+
+function submitData(e){  
+	e.preventDefault();
+	ajaxJson("GET", "/meta/sand", displaySend, function () {
+  });
+}
+onLoad(function() {
+  bnd($("#special-button"), "click", submitData);
+});
+</script>
+
+</body></html>

BIN
meta-id/html/favicon.ico


+ 15 - 0
meta-id/html/head-

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>META</title>
+<script src="/meta-ui.js"></script>
+<link href="META.css" rel=stylesheet type="text/css">
+<meta charset="utf-8">
+<style>
+body { 
+  font-family: "Lato", sans-serif;
+  color: #3e0694;
+}
+</style>
+</head>
+<body>

+ 43 - 0
meta-id/html/id.html

@@ -0,0 +1,43 @@
+<div id="notification" hidden></div><div id="spinner" hidden></div>
+
+<p class="headerlogo2"><a href="welcome.html" target="_self">META</a></p>
+<p><span class="menuwelcome">ID</span><br>
+<div class="config">
+<div id="state--1" hidden><button class="submitboxtext2" id="loginbox">login</button></div>
+<div id="state-0" hidden><form id="passform" class="pure-form">
+<p><br>
+Enter your personal password here to access wifi configuration
+<p><input class="loginbox" type="password" name="passwd" id="passwd"/></p>
+<input type="submit" class="submitboxtext2" value="submit"/>
+<div id="loginnotification" hidden></div>
+</form>  
+</div>
+<div id="state-1" hidden>login success</div>
+<div id="state-2" hidden><a href="/wifi.html"><i>wifi configuration</i></a> | <a href="/logout"><i>Logout</i></a></div>
+<div id="state-3" hidden>connecting</div>
+<div id="state-4" hidden>connected <a href="/wifi.html"><i>configuration</i></a> | <a href="/logout"><i>Logout</i></a></div>
+</div>
+
+<!--<div id="loginform">
+<h2 style="color:red">this SETS the user password !!</h2>
+<form action="/meta/userpass" id="passform" class="pure-form">
+<p><br>
+This sections refers to the connection of your edition to the network.<p>
+For accessing here please type your personal password.
+<p><input class="loginbox"type="password" name="passwd" id="passwd"/></p>
+<p>
+<input type="submit"  class="submitboxtext2"/>
+</p></form>  
+</div>-->
+
+
+<script type="text/javascript">
+onLoad(function() {
+  bnd($("#passform"), "submit", submitPass);
+  bnd($("#loginbox"), "click", function(e){stateMachine('{"state":0,"msg":"login"}')});
+  fetchMeta();
+  ajaxReq('GET', "/meta/state", stateMachine);
+});
+</script>
+</body>
+</html>

+ 11 - 0
meta-id/html/init.html

@@ -0,0 +1,11 @@
+<p class="headerlogo2"><a href="/" target="_self">META</a></p>
+<p><span class="menuwelcome">LOGIN</span><br>
+<p><br>
+<form action="/meta/userpass" id="passform" class="pure-form">
+Set your personal password here
+<p><input class="loginbox"type="password" name="passwd" id="passwd"/></p>
+<p><input type="submit"  class="submitboxtext2"/><p>
+</form>  
+</div>
+</body>
+</html>

BIN
meta-id/html/jl-400x110.png-


+ 669 - 0
meta-id/html/meta-ui.js

@@ -0,0 +1,669 @@
+//===== 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('<h1>Hello</h1>');
+ *   document.body.appendChild(el);
+ *
+ * Copyright (C) 2011 Jed Schmidt <http://jed.is> - 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 <http://jed.is> - 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 <http://jed.is>
+ * 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<l; i++) {
+    if (cls[i] === cl) cls.splice(i, 1);
+  }
+  el.className = cls.join(' ');
+  return cls.length != l
+}
+function toggleClass(el, cl) {
+  if (!removeClass(el, cl)) addClass(el, cl);
+}
+
+//===== AJAX
+
+function ajaxReq(method, url, ok_cb, err_cb, data) {
+  var xhr = j();
+  xhr.open(method, url, true);
+  var timeout = setTimeout(function() {
+    xhr.abort();
+    console.log("XHR abort:", method, url);
+    xhr.status = 599;
+    xhr.responseText = "request time-out";
+  }, 9000);
+  xhr.onreadystatechange = function() {
+    if (xhr.readyState != 4) { return; }
+    clearTimeout(timeout);
+    if (xhr.status >= 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(data);
+  } 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('<div id="spinner" class="spinner" hidden></div>'), o);
+  // notification boxes
+  l.insertBefore(m(
+    '<div id="messages"><div id="warning" hidden></div><div id="notification" hidden></div></div>'), o);
+  // menu hamburger button
+  l.insertBefore(m('<a href="#menu" id="menuLink" class="menu-link"><span></span></a>'), o);
+  // menu left-pane
+  var mm = m(
+   '<div id="menu">\
+      <div class="pure-menu">\
+        <a class="pure-menu-heading" href="https://github.com/jeelabs/meta-id">\
+        <img src="/favicon.ico" height="32">&nbsp;meta-id</a>\
+        <div class="pure-menu-heading system-name" style="padding: 0px 0.6em"></div>\
+        <ul id="menu-list" class="pure-menu-list"></ul>\
+      </div>\
+    </div>\
+    ');
+  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.length; i+=2) {
+        var href = data.menu[i+1];
+        html = html.concat(" <li class=\"pure-menu-item" +
+            (path === href ? " pure-menu-selected" : "") + "\">" +
+            "<a href=\"" + href + "\" class=\"pure-menu-link\">" +
+            data.menu[i] + "</a></li>");
+      }
+      $("#menu-list").innerHTML = html;
+
+      var v = $("#version");
+      if (v != null) { v.innerHTML = data.version; }
+
+      $('title')[0].innerHTML = data["name"];
+      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-table").removeAttribute("hidden");
+  currAp = data.ssid;
+}
+
+function getWifiInfo() {
+  ajaxJson('GET', "/wifi/info", showWifiInfo,
+      function(s, st) { window.setTimeout(getWifiInfo, 1000); });
+}
+
+//===== System info
+
+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);
+    });
+  });
+}
+
+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;
+    }
+  });
+}
+
+//===== Notifications
+
+function showWarning(text) {
+  var el = $("#notification");
+  el.innerHTML = text;
+  el.removeAttribute('hidden');
+  window.scrollTo(0, 0);
+}
+function hideWarning() {
+  el = $("#notification").setAttribute('hidden', '');
+}
+var notifTimeout = null;
+function showNotification(text) {
+	if(text=='')return;
+  var el = $("#notification");
+  el.innerHTML = text;
+  el.removeAttribute('hidden');
+  if (notifTimeout != null) clearTimeout(notifTimeout);
+  notifTimout = setTimeout(function() {
+      el.setAttribute('hidden', '');
+      notifTimout = null;
+    }, 4000);
+}
+
+function loginNotification(text) {
+	if(text=='')return;
+  var el = $("#loginnotification");
+  el.innerHTML = text;
+  el.removeAttribute('hidden');
+  if (notifTimeout != null) clearTimeout(notifTimeout);
+  notifTimout = setTimeout(function() {
+      el.setAttribute('hidden', '');
+      notifTimout = null;
+    }, 4000);
+}
+
+//===== GPIO Pin mux card
+
+var pinPresets = {
+  // array: reset, isp, conn, ser, swap, rxpup, txen
+  "esp-01":       [  0, -1, 2, -1, 0, 1, -1 ],
+  "esp-12":       [ 12, 14, 0,  2, 0, 1, -1 ],
+  "esp-12 swap":  [  1,  3, 0,  2, 1, 1, -1 ],
+  "esp-bridge":   [ 12, 13, 0, 14, 0, 0, -1 ],
+  "wifi-link-12": [  1,  3, 0,  2, 1, 0, -1 ],
+};
+
+function createPresets(sel) {
+  for (var p in pinPresets) {
+    var opt = m('<option value="' + p + '">' + p + '</option>');
+    sel.appendChild(opt);
+  }
+
+  function applyPreset(v) {
+    var pp = pinPresets[v];
+    if (pp === undefined) return pp;
+//    console.log("apply preset:", v, pp);
+    function setPP(k, v) { $("#pin-"+k).value = v; };
+    setPP("reset", pp[0]);
+    setPP("isp",   pp[1]);
+    setPP("conn",  pp[2]);
+    setPP("ser",   pp[3]);
+    setPP("swap",  pp[4]);
+    $("#pin-rxpup").checked = !!pp[5];
+    setPP("txen",  pp[6]);
+    sel.value = 0;
+  };
+
+  bnd(sel, "change", function(ev) {
+    ev.preventDefault();
+    applyPreset(sel.value);
+  });
+}
+
+function displayPins(resp) {
+  function createSelectForPin(name, v) {
+    var sel = $("#pin-"+name);
+    addClass(sel, "pure-button");
+    sel.innerHTML = "";
+    [-1,0,1,2,3,4,5,12,13,14,15].forEach(function(i) {
+      var opt = document.createElement("option");
+      opt.value = i;
+      if (i >= 0) opt.innerHTML = "gpio"+i;
+      else opt.innerHTML = "disabled";
+      if (i===1) opt.innerHTML += "/TX0";
+      if (i===2) opt.innerHTML += "/TX1";
+      if (i===3) opt.innerHTML += "/RX0";
+      if (i==v) opt.selected = true;
+      sel.appendChild(opt);
+    });
+    var pup = $(".popup", sel.parentNode);
+    if (pup !== undefined) hidePopup(pup[0]);
+  };
+
+  createSelectForPin("reset", resp["reset"]);
+  createSelectForPin("isp", resp["isp"]);
+  createSelectForPin("conn", resp["conn"]);
+  createSelectForPin("ser", resp["ser"]);
+  $("#pin-swap").value = resp["swap"];
+  $("#pin-rxpup").checked = !!resp["rxpup"];
+  createSelectForPin("txen", resp["txen"]);
+  createPresets($("#pin-preset"));
+
+  $("#pin-spinner").setAttribute("hidden", "");
+  $("#pin-table").removeAttribute("hidden");
+}
+
+function fetchPins() {
+  ajaxJson("GET", "/pins", displayPins, function() {
+    window.setTimeout(fetchPins, 1000);
+  });
+}
+
+function setPins(ev) {
+  ev.preventDefault();
+  var url = "/pins";
+  var sep = "?";
+  ["reset", "isp", "conn", "ser", "swap", "txen"].forEach(function(p) {
+    url += sep + p + "=" + $("#pin-"+p).value;
+    sep = "&";
+  });
+  url += "&rxpup=" + ($("#pin-rxpup").checked ? "1" : "0");
+//  console.log("set pins: " + url);
+  ajaxSpin("POST", url, function() {
+    showNotification("Pin assignment changed");
+  }, function(status, errMsg) {
+    showWarning(errMsg);
+    window.setTimeout(fetchPins, 100);
+  });
+}
+
+function loadWifiInfo(data){
+		showWifiInfo(data);
+		wifiInfo=data;
+		if(data != null){
+			if (data.status == "connecting") {
+				curState=1;
+			}
+			else if(data.status == "got IP address"){
+				showWifiInfo(data);
+				curState=2;
+			}
+			else{
+				curState=0;
+			}
+		}
+       stateMachine();
+}
+
+/**
+ * -1 : unknown, waiting for authentication
+ *  0 : login box // if no wifi config and no auth
+ *  1 : login success // if auth ok
+ *  2 : wifi config // if no wifi connection and on request
+ *  3 : connecting //                                                                                                                                                                                                                                                                                                                                                                                                 
+ *  4 : connected
+ **/
+var curState=-1;
+var lastState=-2
+var wifiInfo=null;
+
+
+function resetWifiAp(e) {
+  e.preventDefault();
+  showNotification("Ressetting configuration...");
+  var url = "/wifi/connect?essid=&passwd=";
+  hideWarning();
+  $("#reconnect").setAttribute("hidden", "");
+  $("#wifi-passwd").value = "";
+  var cb = $("#reset-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 resetting network: "+st);
+      cb.className = cn;
+      window.setTimeout(scanAPs, 1000);
+    });
+}
+
+function toggle(a){
+	del=document.getElementById(a);
+	if(del.hasAttribute("hidden")){
+		del.removeAttribute("hidden");
+	}
+	else{
+		del.setAttribute("hidden","");}
+}
+
+
+function restartSM(resp){
+	stateMachine(resp);
+}
+
+function stateMachine (resp) {
+  try { jrep = JSON.parse(resp); }
+  catch(err) {
+    console.log("JSON parse error: " + err + ". In: " + resp);
+    jrep['state']=-1;
+    jrep['msg']="not initialized";
+  }
+	curState=jrep['state'];
+	if(lastState!=curState){
+	showNotification(jrep['msg']);
+	if(jrep['auth'] == 0){
+		curState=-1
+	}
+		for(i=-1;i<5;i++){
+			del=$("#state-"+i);
+			del.setAttribute('hidden','');
+		}
+		del=$("#state-"+curState);
+		del.removeAttribute('hidden');
+	}
+	lastState=curState;
+	if(curState== 1 || curState == 3){
+		window.setTimeout(function (){ajaxReq('GET', "/meta/state", stateMachine)}, 2000);
+	}
+}
+
+function loginSuccess(resp){
+    loginNotification("Login success");
+	document.location.href="wifi.html";
+}
+
+function loginFail(s,st){
+    loginNotification("Login failed");
+	stateMachine(st);
+}
+
+function submitPass(e){
+	e.preventDefault();
+ajaxReq('GET', "/meta/auth?passwd="+$("#passwd").value,
+	loginSuccess,loginFail);
+}
+
+function displayMeta(data) {
+  Object.keys(data).forEach(function (v) {
+    el = $("#" + v);
+    if (el != null) {
+		console.log("have "+v+" : "+data[v]);
+		  if (data[v]==1)el.checked=false;
+		  if (data[v]==2)el.checked=true;
+		  
+    }});
+
+}
+
+function fetchMeta() {
+//	console.log("fetchMeta !");
+  ajaxJson("GET", "/meta/gpio", displayMeta, function () {
+    window.setTimeout(fetchMeta, 1000);
+  });
+}
+
+function changeMetaStatus(e) {
+  e.preventDefault();
+  var v = document.querySelector('input[name="meta-status-topic"]').value;
+  ajaxSpin("POST", "/meta?meta-status-topic=" + v, function () {
+    showNotification("meta status settings updated");
+  }, function (s, st) {
+    showWarning("Error: " + st);
+    window.setTimeout(fetchMeta, 100);
+  });
+}
+
+function setMeta(b,d){
+	ajaxReq("POST","/meta/gpio?num="+b+"&v="+(d?"-1":"-2"),
+	function(){fetchMeta();
+  }, function () {
+	fetchMeta();
+    showWarning("Enable/disable failed");
+  });
+}
+
+function ledControl05(e){
+setMeta("05",$('#meta-gpio-05').checked);
+}
+function ledControl12(e){
+setMeta("12",$('#meta-gpio-12').checked);
+}
+function ledControl13(e){
+setMeta("13",$('#meta-gpio-13').checked);
+}
+
+
+function  sendServer(e){
+	  e.preventDefault();
+  var v = document.querySelector('input[name="send-msg"]').value;
+  ajaxSpin("GET", "/meta/sand?msg=" + v, function () {
+    showNotification("Your message was sent !");
+  }, function (s, st) {
+    showWarning("Error: " + st);
+  });
+}
+  

+ 22 - 0
meta-id/html/start.html

@@ -0,0 +1,22 @@
+<div id=menu>
+<ul>
+Meta
+</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+<h1>META
+</h1>
+<form action="#" id="passform" class="pure-form">
+<legend>Enter your personal password here to unlock more meta content </legend>
+<div class="form-horizontal">
+<label for="password" style="margin-right:1em">
+<input type="password" name="password" id="password"/>
+PASSWORD
+</label>
+</div>
+<button id="special-button" type="submit" class="pure-button button-primary">Set password!</button>
+</form>
+</div>
+</div>
+</body></html>

File diff suppressed because it is too large
+ 101 - 0
meta-id/html/text/t1.html


File diff suppressed because it is too large
+ 66 - 0
meta-id/html/text/t2.html


File diff suppressed because it is too large
+ 54 - 0
meta-id/html/text/t3.html


File diff suppressed because it is too large
+ 107 - 0
meta-id/html/text/t4.html


File diff suppressed because it is too large
+ 73 - 0
meta-id/html/text/t5.html


+ 76 - 0
meta-id/html/text/t6.html

@@ -0,0 +1,76 @@
+<div id=menu>
+	<ul>
+		<li>Text</li>
+		<li><a href="/meta.html">@</a></li>
+		<li><a href="t1.html">Text 1</a></li>
+		<li><a href="t2.html">Text 2</a></li>
+		<li><a href="t3.html">Text 3</a></li>
+		<li><a href="t4.html">Text 4</a></li>
+		<li><a href="t5.html">Text 5</a></li>
+		<li><a href="t6.html">Text 6</a></li>
+	</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+<h1>Tracing the scream of clouds</h1>
+<b><i>Mario de Vega <br/>
+Víctor Mazón Gardoqui</i></b>
+<pre>
+We are loners constantly in touch.1 — Zygmunt Bauman
+META
+_ is a display in the form of a publication.
+_ is an asymmetrical refraction.
+_ is a proxy for the dissemination of information.
+_ is an open and peer-regulated model of pedagogy.
+_ is a platform that offers critical perspectives on the transformations imposed by techno­ scientific militaria.
+_ is an opaque guide to a world that requires more than five senses.
+_ exposes ambivalences of misleading information through the sharing of expectations.
+_ summarises an inquiry into the invisible.
+_ reveals hidden arrangements of spectra and their inevitable unraveling.
+_ explores the borderlines of printed media and the physical complexities of frequency modulation.
+_ approaches design as process.
+_ showcases reflections on congested and contested surfaces that unfold the vertical domination of our world.
+_ traces the exponential adulteration of norms, impositions of power, and methodologies
+for social control.
+_ confronts irregular proportions of power.
+_ analyses how manipulative extensions act to divide and isolate us.
+_ reflects on ruins made by human presence in a timeline of abuse and deceit.
+_ is a disagreement about a time fascinated by dull and obscure gadgets, highly sophisticated data traffic, and cloud computing.
+_ approaches tactical media as situational aesthetics.
+_ discusses and analyses new regimes of knowledge and information.
+_ is a gesture that alludes the value of community as a tool for social transformation, wagering on an inquiry into truths.
+_ is an organism that ingests, roars, and eructs the remains of human decay and hope.
+_ is a nervous turn that slaps itself. _ is a reflex flow.
+_ is phlegm.
+
+A background on batch production
+Shenzhen, China
+
+We dug in the underground and overground, the sub­world of motherboard replacements, bargains, cloning, Alipay transactions, weChat dependency, gender­divided spas, fried soja, and black market machinery to create a system that relies on a custom firmware flashed on
+a 32-bit micro-controller running at 160 MHz, resulting in 555 single units each outfitted with a unique »ID« based on the 4 last Bytes of its MAC address.
+Through a customized version of an ESP8266
+Wi-Fi module, META executes as a local
+or internet­based network, holding physical and virtual content on a low power micro­controller module. It provides a wireless internet spot as an open access point embedded into the spine
+of the compendium.
+A web interface displays the expanded archive, visualized by any TCP / IP browser, allowing the user to navigate through content that is dynamically pulled from the micro­ controller to the server and then to the rest
+of the users’ »nodes«.
+META is based on a Message Queue Telemetry
+Transport ( MQTT ) client from node to broker to node.
+
+An ID
+://R
+
+During the last 10 years as ://R, we have experimented with the theorization, design and production of electronic interfaces
+and speculative devices. Each of them identified with an alias. To complete this proposal,
+we identified this unit as ID. Each unit ( ID ), includes an Ultra Violet Light Emitting Diode controlled by an algorithm. A high luminous surface mount technology which due to
+its wavelength causes the UV ink on the book to fluoresce ( glow ), allowing the user to see the printed content.
+  These oscillations operate on a range of 390–398 nm, with 180 degrees directivity and up to 3 W of power. The lighting technique
+is capable of emitting a narrow spectrum of radiation (+/- 10 nm ) more accurately than the mercury lamps or fluorescent tubes used on currency inspection, forensic analysis, fluorescing minerals, gems and personal ID detector devices.
+  This compendium celebrates 10 years of independent research with an interest in tracing, exposing and sharing information. During
+this decade, together with researchers, technologists, theorists, designers and artists with shared interests in the observation and dissemination of knowledge, we have been immersed in the development of interfaces to provide extensions that allow the human senses to access hidden layers and affective qualities of vibrant matter.
+As a result, META questions a timeline
+of exhausted superlatives, superficial paradises of consumption, and vast proliferation of technological paraphernalia.
+</pre>
+</div>
+</div>
+</body></html>

File diff suppressed because it is too large
+ 383 - 0
meta-id/html/text/t7.html.toobig


+ 25 - 0
meta-id/html/text/t8.html.toobig

@@ -0,0 +1,25 @@
+<div id=menu>
+	<ul>
+		<li>Text 8</li>
+		<li><a href="/meta.html">@</a></li>
+		<li><a href="t1.html">Text 1</a></li>
+		<li><a href="t2.html">Text 2</a></li>
+		<li><a href="t3.html">Text 3</a></li>
+		<li><a href="t4.html">Text 4</a></li>
+		<li><a href="t5.html">Text 5</a></li>
+		<li><a href="t5.html">Text 5</a></li>
+		<li><a href="t6.html">Text 6</a></li>
+		<li><a href="t7.html">Text 7</a></li>
+	</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+            <h1>TEXT 8</h1>
+<p>Curabitur sagittis elementum felis at sodales. Nullam fermentum at urna quis accumsan. Nam bibendum leo nisi, lacinia molestie arcu consequat quis. Morbi at suscipit risus. </p>
+<p>Sed consequat eleifend metus, vitae malesuada eros elementum sed. Fusce risus felis, viverra et dignissim at, lobortis non est. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
+<p>Nullam sit amet consectetur lacus. Vestibulum neque lectus, egestas non cursus vitae, aliquam at magna. Etiam aliquam, justo et varius eleifend, dui lectus egestas nunc, vel malesuada nisl purus nec metus. </p>
+<p>In gravida turpis a pharetra commodo. Donec non turpis vulputate, faucibus metus a, consectetur nunc. Morbi tortor nisi, hendrerit sed laoreet eget, fermentum et justo. Suspendisse potenti.</p>
+</p>
+</div>
+</div>
+</body></html>

+ 34 - 0
meta-id/html/text/text.html

@@ -0,0 +1,34 @@
+<div id=menu>
+	<ul>
+		<li>Text</li>
+		<li><a href="/meta.html">@</a></li>
+		<li><a href="t1.html">Text 1</a></li>
+		<li><a href="t2.html">Text 2</a></li>
+		<li><a href="t3.html">Text 3</a></li>
+		<li><a href="t4.html">Text 4</a></li>
+		<li><a href="t5.html">Text 5</a></li>
+		<li><a href="t6.html">Text 6</a></li>
+		<li><a href="t7.html">Text 7</a></li>
+		<li><a href="t7.html">Text 7</a></li>
+		
+	</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+<a href=t1.html>Sandra Braman : Strange Attraction Trump and the Metabolic Consequences of Fake News</a><br/>
+<hr/>
+<a href=t2.html>Sentinel Nicolas; J. Bullot : Artists and the Concern with Truth</a><br/>
+<hr/>
+<a href=t3.html>Nishant Shah: The Need for Intent and Affect</a><br/>
+<hr/>
+<a href=t4.html>MAGDA HAVAS: The Science Policy Gap</a><br/>
+<hr/>
+<a href=t5.html>DANIELA SILVESTRIN: AN INTRODUCTION</a><br/>
+<hr/>
+<a href=t6.html>Mario de Vega ; Víctor Mazón Gardoqui: Tracing the scream of clouds</a><br/>
+<hr/>
+<a href=t7.html>Nina Janich: Of evidence and blind spots in scientific and public debates</a><br/>
+<hr/>
+</div>
+</div>
+</body></html>

File diff suppressed because it is too large
+ 283 - 0
meta-id/html/texts-braman.html


File diff suppressed because it is too large
+ 90 - 0
meta-id/html/texts-breke.html


File diff suppressed because it is too large
+ 220 - 0
meta-id/html/texts-bullot.html


File diff suppressed because it is too large
+ 57 - 0
meta-id/html/texts-daniela.html


File diff suppressed because it is too large
+ 314 - 0
meta-id/html/texts-havas.html


File diff suppressed because it is too large
+ 127 - 0
meta-id/html/texts-janich.html


File diff suppressed because it is too large
+ 63 - 0
meta-id/html/texts-kiesewetter.html


File diff suppressed because it is too large
+ 200 - 0
meta-id/html/texts-shah.html


+ 60 - 0
meta-id/html/texts-vega-mazon.html

@@ -0,0 +1,60 @@
+<p class="headerlogo2"><a href="welcome.html" target="_self">META</a></p>
+<p><span class="menuwelcome">TEXT</span><br>
+
+<p><br>
+ 
+<span class="authorname">Mario de Vega <br> Victor Mazon Gardoqui</span><br>
+<span class="black200percentcapitalize">Tracing the scream of clouds</span><br>
+<br>
+<br>
+We are loners constantly in touch.1 — Zygmunt Bauman  <br>
+<br>
+META<br>
+_ is a display in the form of a publication.<br>
+_ is an asymmetrical refraction.<br>
+_ is a proxy for the dissemination of information.<br>
+_ is an open and peer-regulated model of pedagogy.<br>
+_ is a platform that offers critical perspectives on the transformations imposed by techno­ scientific militaria.<br>
+_ is an opaque guide to a world that requires more than five senses.<br>
+_ exposes ambivalences of misleading information through the sharing of expectations.<br>
+_ summarises an inquiry into the invisible.<br>
+_ reveals hidden arrangements of spectra and their inevitable unraveling.<br>
+_ explores the borderlines of printed media and the physical complexities of frequency modulation.<br>
+_ approaches design as process.<br>
+_ showcases reflections on congested and contested surfaces that unfold the vertical domination of our world.<br>
+_ traces the exponential adulteration of norms, impositions of power, and methodologies
+for social control.<br>
+_ confronts irregular proportions of power.<br>
+_ analyses how manipulative extensions act to divide and isolate us.<br>
+_ reflects on ruins made by human presence in a timeline of abuse and deceit.<br>
+_ is a disagreement about a time fascinated by dull and obscure gadgets, highly sophisticated data traffic, and cloud computing.<br>
+_ approaches tactical media as situational aesthetics.<br>
+_ discusses and analyses new regimes of knowledge and information.<br>
+_ is a gesture that alludes the value of community as a tool for social transformation, wagering on an inquiry into truths.<br>
+_ is an organism that ingests, roars, and eructs the remains of human decay and hope.<br>
+_ is a nervous turn that slaps itself. _ is a reflex flow.<br>
+_ is phlegm.<br>
+
+A background on batch production<br>
+Shenzhen, China<br>
+We dug in the underground and overground, the sub­world of motherboard replacements, bargains, cloning, Alipay transactions, weChat dependency, gender­divided spas, fried soja, and black market machinery to create a system that relies on a custom firmware flashed on
+a 32-bit micro-controller running at 160 MHz, resulting in 555 single units each outfitted with a unique »ID« based on the 4 last Bytes of its MAC address.<br>
+Through a customized version of an ESP8266 Wi-Fi module, META executes as a local
+or internet­based network, holding physical and virtual content on a low power micro­controller module. It provides a wireless internet spot as an open access point embedded into the spine
+of the compendium.<br>
+A web interface displays the expanded archive, visualized by any TCP / IP browser, allowing the user to navigate through content that is dynamically pulled from the micro­ controller to the server and then to the rest
+of the users’ »nodes«.
+META is based on a Message Queue Telemetry
+Transport ( MQTT ) client from node to broker to node.<br>
+
+An ID<br>
+://R<br>
+During the last 10 years as ://R, we have experimented with the theorization, design and production of electronic interfaces and speculative devices. Each of them identified with an alias. To complete this proposal,
+we identified this unit as ID. Each unit ( ID ), includes an Ultra Violet Light Emitting Diode controlled by an algorithm. <br>
+A high luminous surface mount technology which due to its wavelength causes the UV ink on the book to fluoresce ( glow ), allowing the user to see the printed content.<br>
+  These oscillations operate on a range of 390–398 nm, with 180 degrees directivity and up to 3 W of power. The lighting technique is capable of emitting a narrow spectrum of radiation (+/- 10 nm ) more accurately than the mercury lamps or fluorescent tubes used on currency inspection, forensic analysis, fluorescing minerals, gems and personal ID detector devices.<br>
+  This compendium celebrates 10 years of independent research with an interest in tracing, exposing and sharing information. <br>
+  During this decade, together with researchers, technologists, theorists, designers and artists with shared interests in the observation and dissemination of knowledge, we have been immersed in the development of interfaces to provide extensions that allow the human senses to access hidden layers and affective qualities of vibrant matter.<br>
+As a result, META questions a timelineof exhausted superlatives, superficial paradises of consumption, and vast proliferation of technological paraphernalia.
+</body>
+</html>

+ 75 - 0
meta-id/html/texts.html

@@ -0,0 +1,75 @@
+<p class="headerlogo2"><a href="welcome.html" target="_self">META</a></p>
+<p><span class="menuwelcome">TEXT</span><br>
+
+<p><br>
+    <a href="texts-daniela.html" target="_self"><span class="authorname">Daniela Silvestrin</span><br>
+  <span class="black200percentcapitalize">An Introduction</span>
+  <br>
+  <br>
+  <br>
+
+  <a href="texts-breke.html" target="_self"><span class="authorname">Jaya Klara Brekke</span><br>
+  <span class="black200percentcapitalize">Desmembrando la máquina de la verdad </span><br>
+  <span class="black200percent">Una historia en dos partes</span></a>
+  <br>
+  <br>
+  <br>
+  
+  <a href="texts-braman.html" target="_self"><span class="authorname">Sandra Braman</span><br>
+  <span class="black200percentcapitalize">Strange Attraction</span><br>
+  <span class="black200percent">Trump and the Metabolic Consequences of Fake News</span></a>
+  <br> 
+  <br>
+  <br>
+  
+ <a href="texts-janich.html" target="_self"><span class="authorname">Nina Janich</span><br>
+ <span class="black200percentcapitalize">Evidencia y puntos ciegos en debates científicos y públicos</span><br>
+ <span class="black200percent">Una mirada desde el discurso lingüístico</span></a>
+ <br>
+ <br>
+ <br>
+ 
+ <a href="texts-shah.html" target="_self"><span class="authorname">Nishant Shah</span><br>
+ <span class="black200percentcapitalize">The Need for Intent and Affect</span><br>
+ <span class="black200percent">An Apology for Fake News in the Digital Era</span></a>
+ <br>
+<br>
+<br>
+  
+  
+  <a href="texts-savic.html" target="_self"><span class="authorname">Selena Savic</span><br>
+  <span class="black200percentcapitalize">Espacio de comunicación</span><br>
+  <span class="black200percent">¿Cómo abordar la arquitectura de las redes inalámbricas?</span></a>
+  <br>
+  <br>
+  <br>
+  
+  <a href="texts-havas.html" target="_self"><span class="authorname">Magda Havas</span><br>
+  <span class="black200percentcapitalize">The Science Policy Gap</span><br>
+  <span class="black200percent">Reflections on the Dance between Science and Policy with Emphasis on Electrosmog</span></a>
+  <br>
+  <br>
+  <br>
+  
+  
+  <a href="texts-kiesewetter.html" target="_self"><span class="authorname">Rebekka Kiesewetter</span><br>
+  <span class="black200percentcapitalize">Double Meta</span><br>
+  <span class="black200percent">Repensando la publicación</span></a>
+  <br>
+  <br>
+  <br>
+
+  
+  <a href="texts-bullot.html" target="_self"><span class="authorname">Nicolas J. Bullot</span><br>
+  <span class="black200percentcapitalize">Sentinel Artists and the Concern with Truth</span></a><br>
+  <br>
+  <br>
+  <br>
+
+  <a href="texts-vega-mazon.html" target="_self"><span class="authorname">De Vega & Mazón Gardoqui</span><br>
+  <span class="black200percentcapitalize">Tracing the scream of clouds</span><br>
+  <br>
+  <br>
+  <br>
+</body>
+</html>

+ 32 - 0
meta-id/html/welcome.html

@@ -0,0 +1,32 @@
+<p class="headerlogo2">META</p>
+<p><span class="menuwelcome"><a href="about.html" target="_self">ABOUT</a></span><br>
+   <span class="menuwelcome"><a href="id.html" target="_self">ID</a></span><br>
+<span class="menuwelcome"><a href="texts.html" target="_self">TEXT</a></span>
+<p>
+<p>
+<br>
+</p>
+<span p class="black200percent">Your password for administration purposes
+is set and you are now connected to META-ID.</span>
+
+<p>
+<br>
+</p>
+<div id="ledcontrol"> 
+
+<!--<label class="container"> <input type="checkbox" id="meta-gpio-05"  class="container"><span p class="mediumbold">LED on/off</span></input> <span class="checkmark"></span>-->
+<label class="container"> <input type="checkbox" id="meta-gpio-12"  class="container"><span p class="mediumbold">LED on/off</span></input> <span class="checkmark"></span>
+</label>
+</div>
+
+<script type="text/javascript">
+onLoad(function() {
+  //bnd($("#meta-gpio-05"), "click", ledControl05);
+  bnd($("#meta-gpio-12"), "click", ledControl12);
+});
+</script>
+
+
+
+</body>
+</html>

BIN
meta-id/html/wifi.gif


+ 147 - 0
meta-id/html/wifi.html

@@ -0,0 +1,147 @@
+<p class="headerlogo2"><a href="/" target="_self">META</a></p>
+<span class="menuwelcome">WiFi</span><br>
+    <div class="content">
+      <div class="pure-g">
+        <div class="pure-u-1 pure-u-md-1-2"><div class="card">
+          <h1><a href="#" id="wifi-state">WiFi State</a></h1>
+           <div id="statediv" hidden>
+         <table id="wifi-table" class="pure-table pure-table-horizontal" ><tbody>
+          <!--<tr><td>WiFi mode</td><td id="wifi-mode"></td></tr>-->
+          <tr><td>WiFi channel</td><td id="wifi-chan"></td></tr>
+          <tr><td>Configured network</td><td id="wifi-ssid"></td></tr>
+          <tr><td>WiFi status</td><td id="wifi-status"></td></tr>
+          <tr><td>WiFi address</td><td id="wifi-ip"></td></tr>
+          <tr><td>WiFi rssi</td><td id="wifi-rssi"></td></tr>
+          <tr><td>WiFi phy</td><td id="wifi-phy"></td></tr>
+          <tr><td>WiFi MAC</td><td id="wifi-mac"></td></tr>
+          <!--<tr><td colspan="2" id="wifi-warn"></td></tr>-->
+          </tbody> </table>
+        </div> </div>
+        
+        </div>
+        <div class="pure-u-1 pure-u-md-1-2"><div class="card">
+          <h1><a href="#" id="wifi-config">WiFi Configuration</a></h1>
+          <div id="wifidiv" hidden>
+			<div id="notification" hidden></div><div id="spinner" hidden></div>
+          <p id="reconnect" style="color: #600" hidden></p>
+          <form action="#" id="wifiform" class="pure-form pure-form-stacked" >
+            <legend>To connect to a WiFi network, please select one of the detected networks,
+               enter the password, and hit the connect button...</legend>
+            <button id="scan-button" type="submit" class="pure-button button-primary">Scan!</button>
+            <label>Network SSID</label>
+            <div id="aps">Scanning... <div class="spinner"></div></div>
+            <label for="opt-hiddenssid">
+              <input type="radio" name="essid" value="_hidden_ssid_" id="opt-hiddenssid">
+              <input type="text" id="hidden-ssid" value="" style="width:auto; display:inline-block; margin-left: 0.7em">
+            </label>
+            <label>WiFi password, if applicable:</label>
+            <input id="wifi-passwd" type="password" name="passwd" placeholder="password">
+            <button id="connect-button" type="submit" class="pure-button button-primary">Connect!</button>
+          </form>
+          <form action="#" id="specform" class="pure-form" >
+            <legend>Special settings, use with care!</legend>
+            <div class="form-horizontal">
+              <label for="dhcp-ron" style="margin-right:1em">
+                <input type="radio" name="dhcp" value="on" id="dhcp-ron"/>
+                DHCP</label>
+              <label for="dhcp-roff">
+                <input type="radio" name="dhcp" value="off" id="dhcp-roff"/>
+                Static IP</label>
+            </div>
+            <div id="dhcp-on" class="pure-form-stacked"></div>
+            <div id="dhcp-off" class="pure-form-stacked">
+              <label>Static IP address</label>
+              <input id="wifi-staticip" type="text" name="staticip"/>
+              <label>Netmask (for static IP)</label>
+              <input id="wifi-netmask" type="text" name="netmask"/>
+              <label>Gateway (for static IP)</label>
+              <input id="wifi-gateway" type="text" name="gateway"/>
+            </div>
+            <button id="special-button" type="submit" class="pure-button button-primary">Change!</button>
+          </form>
+		<br/><br/>
+        </div></div>
+        <div class="pure-u-1 pure-u-md-1-2"><div class="card">
+          <h1><a href="#" id="wifi-send">Send a message</a></h1>
+          <div id="senddiv" hidden>
+            <legend>Send data to server</legend>
+          <div class="card">
+            <form action="#" id="mqtt-form" class="pure-form" >
+              <div>
+                <input type="checkbox" name="mqtt-enable"/>
+                <label>Enable MQTT client</label>
+              </div>
+              <div>
+                <label>MQTT client state: </label>
+                <b id="mqtt-state"></b>
+              </div>
+              <br>
+              <button id="mqtt-button" type="submit" class="pure-button button-primary">
+                Update server settings!
+              </button>
+            </form>
+          </div>
+            <div class="form-horizontal">
+
+            <form action="#" id="mqtt-status-form" class="pure-form" >
+              <div class="form-horizontal">
+                <input type="checkbox" name="mqtt-status-enable"/>
+                <label>Enable status reporting via MQTT</label>
+              </div>
+              <br>
+              <div class="pure-form-stacked">
+                <label>Status topic</label>
+                <input type="text" name="mqtt-status-topic"/>
+                Message: <tt id="mqtt-status-value"></tt>
+                <div class="popup">MQTT topic to which status message is sent</div>
+              </div>
+              <button id="mqtt-status-button" type="submit" class="pure-button button-primary">
+                Update status settings!
+              </button>
+            </form>
+          </div>
+          <div class="card">
+            <form action="#" id="mqtt-test-form" class="pure-form" >
+              <div class="form-horizontal">
+                <input name="mqtt-test"/>
+                <label>Enable status reporting via MQTT</label>
+              </div>
+              <br>
+              <button id="mqtt-test-button" type="submit" class="pure-button button-primary">
+                Send !
+              </button>
+            </form>
+          </div>
+          </div>
+          </div>
+        <div class="pure-u-1 pure-u-md-1-2"><div class="card">
+          <h1><a href="/init.html" id="reset-pass">Reset Password</a></h1>
+        </div></div>
+        <div class="pure-u-1 pure-u-md-1-2"><div class="card">
+          <h1><a href="/logout">Logout</a>
+        </div></div>
+      </div>
+    </div>
+
+<script src="wifiSta.js"></script>
+<script type="text/javascript">
+onLoad(function() {
+  getWifiInfo();
+  fetchMqtt();
+  bnd($("#wifi-state"), "click", function(e){toggle("statediv")});
+  bnd($("#wifi-config"), "click", function(e){toggle("wifidiv")});
+  bnd($("#wifi-send"), "click", function(e){toggle("senddiv")});
+  bnd($("#wifiform"), "submit", changeWifiAp);
+  bnd($("#specform"), "submit", changeSpecial);
+  bnd($("#dhcp-ron"), "click", doDhcp);
+  bnd($("#dhcp-roff"), "click", doStatic);
+//  bnd($("#sendform"), "submit", sendServer);
+  bnd($("#scan-button"), "click", scanAPs);
+  bnd($("#mqtt-form"), "submit", changeMqtt);
+  bnd($("#mqtt-test-form"), "submit", changeMqttTest);
+  bnd($("#mqtt-status-form"), "submit", changeMqttStatus);
+
+  scanTimeout = window.setTimeout(scanAPs, 500);
+});
+</script>
+</body></html>

+ 118 - 0
meta-id/html/wifi/wifiAp.html

@@ -0,0 +1,118 @@
+<div id=menu>
+	<ul>
+		<li>Wifi AP</li>
+		<li><a href="/meta.html">Meta</a></li>
+		<li><a href="/text/text.html">Text</a></li>
+		<li><a href="/wifi/wifiSta.html">Wifi Client</a></li>
+		<li><a href="/web-server.html">Upload</a></li>
+	</ul>
+</div>
+<div class="square-box">
+<div class="square-content">
+    <div class="header">
+      <h1>WiFi Soft-AP Configuration</h1>
+    </div>
+
+    <div class="content">
+      <div class="pure-g">
+        <div class="pure-u-1 pure-u-md-1-2">
+	     <div class="card">
+          <h1>Soft-AP State</h1>
+          <div id="wifi-spinner" class="spinner">
+<div id="spinner" class="spinner" hidden></div>
+<div id="notification" class="spinner" hidden></div>
+<div id="warning" class="spinner" hidden></div>
+          </div>
+          <table id="wifi-table" class="pure-table pure-table-horizontal" hidden><tbody>
+          <tr><td>WiFi mode</td><td id="wifi-mode"></td></tr>
+          <tr><td>Soft-AP SSID</td><td id="wifi-apssid"></td></tr>
+          <tr><td>Soft-AP Password</td><td id="wifi-appass"></td></tr>
+          <tr><td>Soft-AP Channel</td><td id="wifi-apchan"></td></tr>
+		  <tr><td>Soft-AP Max Conn</td><td id="wifi-apmaxc"></td></tr>
+		  <tr><td>Soft-AP Hidden</td><td id="wifi-aphidd"></td></tr>
+          <tr><td>Soft-AP Beacon Int</td><td id="wifi-apbeac"></td></tr>
+          <tr><td>Soft-AP Auth Mode</td><td id="wifi-apauth"></td></tr>
+          <tr><td>Soft-AP MAC</td><td id="wifi-apmac"></td></tr>
+          <tr><td colspan="2" id="wifi-apwarn"></td></tr>
+          </tbody> </table>
+        </div><!-- card-->
+      </div><!-- pure-u-1 --> 
+        <div class="pure-u-1 pure-u-md-1-2">
+	      
+	      <div class="card">
+            <h1>Soft-AP Settings</h1>
+            <div id="AP_Settings-spinner" class="spinner"></div>
+            
+            <form action="#" id="AP_Settings-form" class="pure-form" hidden>
+              <legend>Soft-AP main settings, use with care!</legend>
+              
+              <div class="pure-form-stacked">
+                <label>Soft-AP SSID</label>
+                <input type="text" name="ap_ssid" />
+                <div class="popup">Change the name of your AP!</div>
+              </div>
+              
+              <div class="pure-form-stacked">
+                <label>Soft-AP Password</label>
+                <input type="text" name="ap_password" />
+                <div class="popup">Password must be at least 8 chars long!</div>
+              </div>
+              
+              <div class="pure-form-stacked">   
+                <label>Soft-AP Auth Mode</label>
+                <select name="ap_authmode" href="#">
+                  <option value="0">OPEN</option>
+                  <option value="1">WEP</option>
+                  <option value="2">WPA_PSK</option>
+                  <option value="3">WPA2_PSK</option>
+                  <option value="4">WPA_WPA2_PSK</option>
+                </select>
+                <div class="popup">Default WPA_WPA2_PSK</div>
+              </div>
+               
+              <div class="pure-form-stacked">
+                <label>Soft-AP Max Connections</label>
+                <input type="text" name="ap_maxconn" />
+                <div class="popup">Max 4 ( default 4 )</div>
+              </div>
+               
+              <div class="pure-form-stacked">
+                <label>Soft-AP Beacon Interval</label>
+                <input type="text" name="ap_beacon" />
+                <div class="popup">Between 100 - 60000 ms ( default 100ms )</div>
+              </div>
+               
+              <div class="form-horizontal">
+               <label><input type="checkbox" name="ap_hidden" />Soft-AP SSID hidden</label>
+               <div class="popup">Check this box to hide you Soft-AP SSID ( default Not Hidden )</div>
+              </div>
+              
+              <button id="AP_Settings-button" type="submit" class="pure-button button-primary">
+                Change Soft-AP settings!
+              </button>
+
+            </form>
+			<br/><br/>
+          </div>
+        </div><!-- pure-u-1 -->
+      </div><!-- pure-g -->
+    </div><!-- content -->
+  </div><!-- main -->
+</div><!-- layout -->
+</div><!-- layout -->
+</div><!-- layout -->
+
+<script type="text/javascript">
+</script>
+<script src="wifiAp.js"></script>
+<script type="text/javascript">
+onLoad(function() {
+  // Show info about AP	
+  getWifiInfo();
+  // Fetch actual settings
+  fetchApSettings();
+  // Wire-up form
+  bnd($("#AP_Settings-form"), "submit", changeApSettings);
+});
+</script>
+</body></html>

+ 85 - 0
meta-id/html/wifi/wifiAp.js

@@ -0,0 +1,85 @@
+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(/[^!-~]/g, "");
+      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);
+  });
+}

+ 0 - 0
meta-id/html/wifi/wifiSta.html


Some files were not shown because too many files changed in this diff