Browse Source

Merge branch 'dev' into softuart

softuart
Xose Pérez 5 years ago
parent
commit
8c7ac57679
89 changed files with 21876 additions and 4922 deletions
  1. +25
    -2
      .travis.yml
  2. +82
    -0
      CHANGELOG.md
  3. +50
    -37
      README.md
  4. +107
    -47
      code/build.sh
  5. +2
    -1
      code/eagle.flash.1m0m1s.ld
  6. +2
    -1
      code/eagle.flash.512k0m1s.ld
  7. +4
    -0
      code/espurna/button.ino
  8. +6
    -1
      code/espurna/config/arduino.h
  9. +5
    -0
      code/espurna/config/defaults.h
  10. +51
    -0
      code/espurna/config/dependencies.h
  11. +84
    -0
      code/espurna/config/general.h
  12. +203
    -5
      code/espurna/config/hardware.h
  13. +6
    -0
      code/espurna/config/progmem.h
  14. +106
    -61
      code/espurna/config/prototypes.h
  15. +72
    -1
      code/espurna/config/sensors.h
  16. +1
    -0
      code/espurna/config/types.h
  17. +1
    -2
      code/espurna/config/version.h
  18. BIN
      code/espurna/data/index.all.html.gz
  19. BIN
      code/espurna/data/index.html.gz
  20. BIN
      code/espurna/data/index.light.html.gz
  21. BIN
      code/espurna/data/index.rfbridge.html.gz
  22. BIN
      code/espurna/data/index.rfm69.html.gz
  23. BIN
      code/espurna/data/index.sensor.html.gz
  24. BIN
      code/espurna/data/index.small.html.gz
  25. +10
    -3
      code/espurna/espurna.ino
  26. +1
    -1
      code/espurna/fs_math.c
  27. +4
    -0
      code/espurna/led.ino
  28. +65
    -0
      code/espurna/libs/RFM69Wrap.h
  29. +22
    -10
      code/espurna/libs/pwm.h
  30. +2
    -2
      code/espurna/light.ino
  31. +62
    -2
      code/espurna/migrate.ino
  32. +1
    -1
      code/espurna/pwm.c
  33. +69
    -7
      code/espurna/relay.ino
  34. +15
    -14
      code/espurna/rfbridge.ino
  35. +280
    -0
      code/espurna/rfm69.ino
  36. +20
    -3
      code/espurna/sensor.ino
  37. +34
    -2
      code/espurna/sensors/AnalogSensor.h
  38. +2
    -2
      code/espurna/sensors/HLW8012Sensor.h
  39. +125
    -0
      code/espurna/sensors/NTCSensor.h
  40. +12
    -6
      code/espurna/sensors/PZEM004TSensor.h
  41. +23
    -9
      code/espurna/settings.ino
  42. +3044
    -0
      code/espurna/static/index.all.html.gz.h
  43. +0
    -3253
      code/espurna/static/index.html.gz.h
  44. +2960
    -0
      code/espurna/static/index.light.html.gz.h
  45. +2559
    -0
      code/espurna/static/index.rfbridge.html.gz.h
  46. +4030
    -0
      code/espurna/static/index.rfm69.html.gz.h
  47. +2598
    -0
      code/espurna/static/index.sensor.html.gz.h
  48. +2515
    -0
      code/espurna/static/index.small.html.gz.h
  49. +1
    -0
      code/espurna/utils.ino
  50. +55
    -15
      code/espurna/web.ino
  51. +10
    -0
      code/espurna/ws.ino
  52. +32
    -9
      code/extra_scripts.py
  53. +124
    -45
      code/gulpfile.js
  54. +89
    -2
      code/html/custom.css
  55. +260
    -42
      code/html/custom.js
  56. +157
    -14
      code/html/index.html
  57. +0
    -120
      code/html/vendor/checkboxes.css
  58. +0
    -366
      code/html/vendor/checkboxes.js
  59. +460
    -0
      code/html/vendor/datatables-1.10.16.css
  60. +178
    -0
      code/html/vendor/datatables-1.10.16.min.js
  61. BIN
      code/html/vendor/images/border-off.png
  62. BIN
      code/html/vendor/images/border-on.png
  63. BIN
      code/html/vendor/images/handle-center.png
  64. BIN
      code/html/vendor/images/handle-left.png
  65. BIN
      code/html/vendor/images/handle-right.png
  66. BIN
      code/html/vendor/images/label-off.png
  67. BIN
      code/html/vendor/images/label-on.png
  68. BIN
      code/html/vendor/images/sort_asc.png
  69. BIN
      code/html/vendor/images/sort_both.png
  70. BIN
      code/html/vendor/images/sort_desc.png
  71. +7
    -7
      code/ota.py
  72. +270
    -7
      code/package-lock.json
  73. +5
    -2
      code/package.json
  74. +963
    -818
      code/platformio.ini
  75. BIN
      images/devices/allnet-esp8266-up-relay.jpg
  76. BIN
      images/devices/bh-onofre.jpg
  77. BIN
      images/devices/blitzwolf-bw-shp2.jpg
  78. BIN
      images/devices/fornorm-power-strip.jpg
  79. BIN
      images/devices/generic-geiger-diy.png
  80. BIN
      images/devices/itead-sonoff-ifan02.jpg
  81. BIN
      images/devices/itead-sonoff-pow-r2.jpg
  82. BIN
      images/devices/itead-sonoff-s31.jpg
  83. BIN
      images/devices/luani-hvio.jpg
  84. BIN
      images/devices/neo-coolcam-wifi.jpg
  85. BIN
      images/devices/pilotak-esp-din.jpg
  86. BIN
      images/devices/tinkerman-rfm69gw.jpg
  87. BIN
      images/devices/vanzavanzu-smart-wifi-plug-mini.jpg
  88. BIN
      images/devices/zhilde-zld-eu55-w.jpg
  89. +5
    -2
      pre-commit

+ 25
- 2
.travis.yml View File

@ -4,12 +4,33 @@ python:
sudo: false
cache:
directories:
- "~/.npm"
- "~/.platformio"
- "$TRAVIS_BUILD_DIR/code/.piolibdeps"
- "$TRAVIS_BUILD_DIR/code/espurna/node_modules"
install:
- pip install -U platformio
- cd code ; npm install --only=dev ; cd ..
env:
global:
- BUILDER_TOTAL_THREADS=4
script:
- cd code && ./build.sh && cd ..
- cd code && ./build.sh -p && cd ..
stages:
- name: Test
- name: Release
if: tag IS present AND branch = master
jobs:
include:
- stage: Test
script: cd code && ./build.sh travis01
- script: cd code && ./build.sh travis02
- script: cd code && ./build.sh travis03
- stage: Release
env: BUILDER_THREAD=0
- env: BUILDER_THREAD=1
- env: BUILDER_THREAD=2
- env: BUILDER_THREAD=3
before_deploy:
- mv firmware/*/espurna-*.bin firmware/
deploy:
@ -20,8 +41,10 @@ deploy:
file: firmware/espurna-*.bin
skip_cleanup: true
on:
all_branches: true
tags: true
branch: master
repo: xoseperez/espurna
condition: $TRAVIS_BUILD_STAGE_NAME = Release
notifications:
pushover:
api_key:


+ 82
- 0
CHANGELOG.md View File

@ -3,6 +3,88 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.13.1] 2018-07-10
### Fixed
- Build issues with Arduino IDE ([#975](https://github.com/xoseperez/espurna/issues/975))
- Right web interface image for with RF Bridge
- Full web interface image if light and sensor together ([#981](https://github.com/xoseperez/espurna/issues/981))
- Some devices still not using DOUT flash mode
- Crash on loading malformed configuration file
- Mismatch between memory size and layout size for some boards (this might require reflashing)
- Wrong settings report after factory reset
- Memory leak in JustWifi library
- New buttons not rendering right in Safari ([#1028](https://github.com/xoseperez/espurna/issues/1028))
### Added
- Support for RFM69GW board (see http://tinkerman.cat/rfm69-wifi-gateway/)
- Support for Sonoff IFAN02
- Support for NTC sensors ([#1001](https://github.com/xoseperez/espurna/issues/1001))
- Support for single-pin latched relays ([#1039](https://github.com/xoseperez/espurna/issues/1039))
- Check binary flash mode in web upgrade
- Sampling to AnalogSensor
- Parallel builds in Travis (thanks to @lobradov)
### Changed
- Reworked platformio.ini, build.sh files (thanks to @gn0st1c and @mcspr)
## [1.13.0] 2018-06-22
### Fixed
- Fixed PZEM004T compilation issues, working when using hardware serial ([#837](https://github.com/xoseperez/espurna/issues/837))
- Fixed per channel state on/off for lights ([#830](https://github.com/xoseperez/espurna/issues/830))
- Fixed overflow in CSE7766 energy calculation ([#856](https://github.com/xoseperez/espurna/issues/856))
- Fixed On MQTT disconnect in web UI ([#845](https://github.com/xoseperez/espurna/issues/845))
- Check valid hostnames ([#874](https://github.com/xoseperez/espurna/issues/874), [#879](https://github.com/xoseperez/espurna/issues/879))
- Fix Sonoff POW R2 configuration
- Fixed InfluxDB sensor by id ([#882](https://github.com/xoseperez/espurna/issues/882))
- Fix build when disabling WEB_SUPPORT ([#923](https://github.com/xoseperez/espurna/issues/923))
- Fix calibration error in EmonSensor ([#876](https://github.com/xoseperez/espurna/issues/876))
- Fix telnet and web debug responsiveness ([#896](https://github.com/xoseperez/espurna/issues/896))
- Use double quotes in JSON for non-numeric values ([#929](https://github.com/xoseperez/espurna/issues/929))
- Support connections over HTTPS via proxy ([#937](https://github.com/xoseperez/espurna/issues/937))
### Added
- EEPROM sector rotation using EEPROM_Rotate library
- Code filtering when building web UI images
- Added pulsing a relay via MQTT and REST API ([#896](https://github.com/xoseperez/espurna/issues/896), [#902](https://github.com/xoseperez/espurna/issues/902))
- Support for WPS (not available in pre-built binaries)
- Support for Smart Config (not available in pre-built binaries)
- Support for CCT lights (thanks to @Skaronator)
- Allow RELAYx_DELAY_ON/OFF also for none GPIO relay types (thanks to @zafrirron)
- Added relay status to Domoticz on MQTT connection ([#872](https://github.com/xoseperez/espurna/issues/872))
- Added configurable UART-to-MQTT terminator
- Added telnet link to web UI
- Reload terminal command to force all modules to reload settings from config ([#816](https://github.com/xoseperez/espurna/issues/816))
- Added security headers to each HTTP response (thanks to @ITNerdBox)
- Customized GET terminal command (thanks to @mcspr)
- More RC codes supported on TX for RF Bridge (thanks to @wildwiz)
- Support for BL0937 power monitoring chip with unmodified HLW8012 library ([#737](https://github.com/xoseperez/espurna/issues/737))
- Enable CORS
- Support for Allnet ESP8266 UP Relay (thanks to @bajo)
- Support for Tonbux Mosquito Killer (thanks to @gn0st1c)
- Support for Neo Coolcam NAS-WR01W WiFi Smart Power Plug
- Support for TYWE3S-based Estink WiFi Power Strip (thanks to @sandman, [#852](https://github.com/xoseperez/espurna/issues/852))
- Support for Pilotak ESP DIN V1
- Support for DIY Geiger counter (thanks to @Trickx)
- Support for HomeCube / Blitzwolf BW-SHP2
* Support for Vanzavanzu Smart Wifi Plug Mini
- Support for Bruno Horta's OnOfre board
### Changed
- Updated PlatformIO to use Core 3.5.3
- Updated to JustWifi 2.0
- CSS optimizations ([#870](https://github.com/xoseperez/espurna/issues/870), [#871](https://github.com/xoseperez/espurna/issues/871))
- Several changes in OTA Manager
- Better memory layout info
- MQTT keep alive time increased to 300s
- Using ticket-based authentication for WS
- Refactor module and sensor listings ([#896](https://github.com/xoseperez/espurna/issues/896))
- Using alternative math methods to save ~8Kb with lights
- Simpligying mired/kelvin methods
- Changed web UI checkboxes with pure CSS versions
### Deprecated
- {identifier} place-holder in MQTT base topic
## [1.12.6] 2018-05-02
### Fixed
- Check NTP_SUPPORT for sensors (thanks to @mcspr)


+ 50
- 37
README.md View File

@ -3,10 +3,11 @@
ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smart switches, lights and sensors.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-1.13.0c-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-softuart-orange.svg)](https://github.org/xoseperez/espurna/tree/softuart/)
[![version](https://img.shields.io/badge/version-1.13.1-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-softuart-orange.svg)](https://github.com/xoseperez/espurna/tree/softuart/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=softuart)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/softuart.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
<br />
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest)
[![gitter](https://img.shields.io/gitter/room/tinkermant-cat/espurna.svg)](https://gitter.im/tinkerman-cat/espurna)
@ -29,6 +30,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Scans for strongest network if more than one defined (also available in web UI)
* Handles correctly multiple AP with the same SSID
* Defaults to AP mode (also available after double clicking the main button)
* Support for **WPS** and **Smart Config** (not available in default builds)
* Network visibility
* Supports mDNS (service reporting and metadata) both server mode and client mode (.local name resolution)
* Supports NetBIOS, LLMNR and Netbios (when built with Arduino Core >= 2.4.0) and SSDP (experimental)
@ -39,6 +41,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Support for **relay synchronization** (all equal, only one ON, one and only on ON)
* Support for **MQTT groups** to sync switches between devices
* Support for **delayed ON/OFF**
* Support for **latched relays**
* **MQTT** enabled
* **SSL/TLS support** (not on regular builds, see [#64](https://github.com/xoseperez/espurna/issues/64))
* Switch on/off and toggle relays, group topics (sync relays between different devices)
@ -67,6 +70,8 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Environment
* **DHT11 / DHT22 / DHT21 / AM2301 / Itead's SI7021**
* **BMP280** and **BME280** temperature, humidity (BME280) and pressure sensor by Bosch
* **TMP35** and **TMP36** analog temperature sensors
* **NTC** temperature sensors
* **SI7021** temperature and humidity sensor
* **SHT3X** temperature and humidity sensor over I2C (Wemos shield)
* **AM2320** temperature and humidity sensor over I2C
@ -76,17 +81,21 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* **PMSX003/PMS5003T/ST** dust sensors
* **BH1750** luminosity sensor
* **GUVAS12SD** UV sensor
* **GEIGER COUNTER** by RH Electronics
* Power monitoring
* **HLW8012** using the [HLW8012 Library](https://bitbucket.org/xoseperez/hlw8012) (Sonoff POW)
* Non-invasive **current sensor** using **internal ADC** or **ADC121** or **ADS1115**
* **ECH1560** power monitor chip
* **CSE7766** and **CSE7759B** power monitor chips
* **HJL-01** and **BL0937** power monitor chips
* Non-invasive **current sensor** using **internal ADC** or **ADC712** or **ADC121** or **ADS1115**
* **V9261F** power monitor chip
* **PZEM0004T** power monitor board
* Raw analog and digital sensors
* Simple pulse counter
* Support for (almost) any UART based sensor via the **UART-to-MQTT module**
* Support for different units (Fahrenheit or Celsius, Watts or Kilowatts, Joules or kWh)
* Support for LED lights
* MY92XX-based light bulbs and PWM LED strips (dimmers) up to 5 channels (RGB, cold white and warm white, for instance)
* Support for CCT lights
* RGB and HSV color codes supported
* Manage channels individually
* Temperature color supported (in [mired](https://en.wikipedia.org/wiki/Mired) and [kelvin](https://en.wikipedia.org/wiki/Color_temperature)) via MQTT / REST API
@ -96,7 +105,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Option to have separate switches for each channel
* Support for simple 433MHz RF receivers
* Support for UART-to-MQTT bidirectional bridge
* Fast asynchronous **HTTP Server**
* Fast asynchronous **HTTP Server** and cool **Web User Interface**
* Configurable port
* Basic authentication
* Web-based configuration
@ -107,7 +116,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Upgrade firmware from the web interface
* Works great behind a [secured reverse proxy](http://tinkerman.cat/secure-remote-access-to-your-iot-devices/)
* **REST API** (enable/disable from web interface)
* GET and PUT relay status
* GET and PUT relay status (including pulses)
* Change light color (for supported hardware)
* GET sensor data (power, current, voltage, temperature and humidity) depending on the available hardware
* Works great behind a secured reverse proxy
@ -136,6 +145,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Long click (>1 second) to reboot device (only main button)
* Extra long click (>10 seconds) to go back to factory settings (only main button)
* Specific definitions for touch button devices (ESPurna Switch, Sonoff Touch & T1)
* Configuration stored in different sectors to prevent data loosing and corruption
## Notices
@ -198,55 +208,58 @@ Here is the list of supported hardware. For more information please refer to the
||||
|---|---|---|
|![Tinkerman Espurna H](images/devices/tinkerman-espurna-h.jpg)|||
|**Tinkerman ESPurna H**|||
|![Itead Sonoff RF Bridge](images/devices/itead-sonoff-rfbridge.jpg)|||
|**Itead Sonoff RF Bridge**|||
|![Itead Sonoff Basic](images/devices/itead-sonoff-basic.jpg)|![Itead Sonoff RF](images/devices/itead-sonoff-rf.jpg)|![Itead Sonoff Dual/Dual R2](images/devices/itead-sonoff-dual.jpg)|
|**Itead Sonoff Basic**|**Itead Sonoff RF**|**Itead Sonoff Dual/Dual R2**|
|![Itead Sonoff POW](images/devices/itead-sonoff-pow.jpg)|![Itead Sonoff TH10/TH16](images/devices/itead-sonoff-th.jpg)|![Electrodragon WiFi IOT](images/devices/electrodragon-wifi-iot.jpg)|
|**Itead Sonoff POW**|**Itead Sonoff TH10/TH16**|**Electrodragon WiFi IOT**|
|![Itead Sonoff 4CH](images/devices/itead-sonoff-4ch.jpg)|![Itead Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/openenergymonitor-mqtt-relay.jpg)|
|**Itead Sonoff 4CH**|**Itead Sonoff 4CH Pro**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**|
|![Itead S20](images/devices/itead-s20.jpg)|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|![Power meters based on V9261F and ECH1560](images/devices/generic-v9261f.jpg)|
|**Itead S20**|**WorkChoice EcoPlug**|**Power meters based on V9261F and ECH1560**|
|![Tinkerman Espurna H](images/devices/tinkerman-espurna-h.jpg)|![Tinkerman RFM69GW](images/devices/tinkerman-rfm69gw.jpg)||
|**Tinkerman ESPurna H**|**Tinkerman RFM69GW**||
|![Itead Sonoff RF Bridge](images/devices/itead-sonoff-rfbridge.jpg)|![Itead Sonoff RF](images/devices/itead-sonoff-rf.jpg)|![Itead Sonoff 4CH](images/devices/itead-sonoff-4ch.jpg)|
|**Itead Sonoff RF Bridge**|**Itead Sonoff RF**|**Itead Sonoff 4CH**|
|![Itead Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)|||
|**Itead Sonoff 4CH Pro**|||
|![Itead Sonoff S31](images/devices/itead-sonoff-s31.jpg)|![BlitzWolf BW-SPP2](images/devices/blitzwolf-bw-shp2.jpg)|![Power meters based on V9261F](images/devices/generic-v9261f.jpg)|
|**Itead Sonoff S31**|**Blitzwolf BW-SHP2<br />(also by HomeCube, Coosa, Goosund)**|**Power meters based on V9261F**|
|![Itead Sonoff POW](images/devices/itead-sonoff-pow.jpg)|![Itead Sonoff POW](images/devices/itead-sonoff-pow-r2.jpg)|![Vanzavanzu Smart WiFi Plug Mini](images/devices/vanzavanzu-smart-wifi-plug-mini.jpg)|
|**Itead Sonoff POW**|**Itead Sonoff POW R2**|**Vanzavanzu Smart WiFi Plug Mini**|
|![Itead Sonoff Basic](images/devices/itead-sonoff-basic.jpg)|![Itead Sonoff Dual/Dual R2](images/devices/itead-sonoff-dual.jpg)|![Itead Sonoff TH10/TH16](images/devices/itead-sonoff-th.jpg)|
|**Itead Sonoff Basic**|**Itead Sonoff Dual/Dual R2**|**Itead Sonoff TH10/TH16**|
|![Electrodragon WiFi IOT](images/devices/electrodragon-wifi-iot.jpg)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/openenergymonitor-mqtt-relay.jpg)||
|**Electrodragon WiFi IOT**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**||
|![Itead S20](images/devices/itead-s20.jpg)|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|![Neo Coolcam NAS WR01W](images/devices/neo-coolcam-wifi.jpg)|
|**Itead S20**|**WorkChoice EcoPlug**|**Neo Coolcam NAS WR01W**|
|![Schuko Wifi Plug](images/devices/schuko-wifi-plug.jpg)|![KMC 70011](images/devices/kmc-70011.jpg)|![Xenon SM-PW702U](images/devices/xenon-sm-pw702u.jpg)|
|**Schuko Wifi Plug**|**KMC 70011**|**Xenon SM-PW702U**|
|![Maxcio W-US002S](images/devices/maxcio-w-us002s.jpg)|![HEYGO HY02](images/devices/heygo-hy02.jpg)|![YiDian XS-SSA05](images/devices/yidian-xs-ssa05.jpg)|
|**Maxcio W-US002S**|**HEYGO HY02**|**YiDian XS-SSA05**|
|![WiOn 50055](images/devices/wion-50055.jpg)|![LINGAN SWA1](images/devices/lingan-swa1.jpg)|![Tonbux PowerStrip02](images/devices/tonbux-powerstrip02.jpg)|
|**WiOn 50055**|**LINGAN SWA1**|**Tonbux PowerStrip02**
|![WiOn 50055](images/devices/wion-50055.jpg)|![LINGAN SWA1](images/devices/lingan-swa1.jpg)||
|**WiOn 50055**|**LINGAN SWA1**||
|![Tonbux PowerStrip02](images/devices/tonbux-powerstrip02.jpg)|![ForNorm Power Strip](images/devices/fornorm-power-strip.jpg)|![Zhilde ZLD-EU55-W](images/devices/zhilde-zld-eu55-w.jpg)|
|**Tonbux PowerStrip02**|**Fornorm Power Strip**|**Zhilde ZLD-EU55-W**|
|![Itead Sonoff Touch](images/devices/itead-sonoff-touch.jpg)|![Itead Sonoff T1](images/devices/itead-sonoff-t1.jpg)|![YJZK 2-gang switch](images/devices/yjzk-2gang-switch.jpg)|
|**Itead Sonoff Touch**|**Itead Sonoff T1**|**YJZK 2-gang switch**|
|![Itead Slampher](images/devices/itead-slampher.jpg)|||
|**Itead Slampher**|||
|![Itead Sonoff B1](images/devices/itead-sonoff-b1.jpg)|![AI-Thinker Wifi Light / Noduino OpenLight](images/devices/aithinker-ai-light.jpg)|![Authometion LYT8266](images/devices/authometion-lyt8266.jpg)|
|**Itead Sonoff B1**|**AI-Thinker Wifi Light / Noduino OpenLight**|**Authometion LYT8266**|
|![Arilux E27](images/devices/arilux-e27.jpg)|![Itead Sonoff LED](images/devices/itead-sonoff-led.jpg)|![Itead BN-SZ01](images/devices/itead-bn-sz01.jpg)|
|**Arilux E27**|**Itead Sonoff LED**|**Itead BN-SZ01**|
|![Itead Slampher](images/devices/itead-slampher.jpg)|![Arilux E27](images/devices/arilux-e27.jpg)|![Itead Sonoff B1](images/devices/itead-sonoff-b1.jpg)|
|**Itead Slampher**|**Arilux E27**|**Itead Sonoff B1**|
|![AI-Thinker Wifi Light / Noduino OpenLight](images/devices/aithinker-ai-light.jpg)|![Authometion LYT8266](images/devices/authometion-lyt8266.jpg)||
|**AI-Thinker Wifi Light / Noduino OpenLight**|**Authometion LYT8266**||
|![Itead Sonoff LED](images/devices/itead-sonoff-led.jpg)|![Itead BN-SZ01](images/devices/itead-bn-sz01.jpg)|![InterMitTech QuinLED 2.6](images/devices/intermittech-quinled-2.6.jpg)|
|**Itead Sonoff LED**|**Itead BN-SZ01**|**InterMitTech QuinLED 2.6**|
|![Arilux AL-LC01 (RGB)](images/devices/arilux-al-lc01.jpg)|![Arilux AL-LC02 (RGBW)](images/devices/arilux-al-lc02.jpg)|![Arilux AL-LC06 (RGBWWCW)](images/devices/arilux-al-lc06.jpg)|
|**Arilux AL-LC01 (RGB)**|**Arilux AL-LC02 (RGBW)**|**Arilux AL-LC06 (RGBWWCW)**|
|![Arilux AL-LC11 (RGBWWW) & RF](images/devices/arilux-al-lc11.jpg)|![MagicHome LED Controller (1.0 and 2.x)](images/devices/magichome-led-controller.jpg)|![Huacanxing H801/802](images/devices/huacanxing-h801.jpg)|
|**Arilux AL-LC11 (RGBWWW) & RF**|**MagicHome LED Controller (1.0/2.x)**|**Huacanxing H801/802**|
|![InterMitTech QuinLED 2.6](images/devices/intermittech-quinled-2.6.jpg)||
|**InterMitTech QuinLED 2.6**||
|![Itead Sonoff SV](images/devices/itead-sonoff-sv.jpg)|![Itead 1CH Inching](images/devices/itead-1ch-inching.jpg)|![Itead Motor Clockwise/Anticlockwise](images/devices/itead-motor.jpg)|
|**Itead Sonoff SV**|**Itead 1CH Inching**|**Itead Motor Clockwise/Anticlockwise**|
|![Jan Goedeke Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.jpg)|![Jorge García Wifi + Relays Board Kit](images/devices/jorgegarcia-wifi-relays.jpg)|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|
|**Jan Goedeke Wifi Relay (NO/NC)**|**Jorge García Wifi + Relays Board Kit**|**EXS Wifi Relay v3.1**|
|![ManCaveMade ESP-Live](images/devices/mancavemade-esp-live.jpg)|![Wemos D1 Mini Relay Shield](images/devices/wemos-d1-mini-relayshield.jpg)|![Witty Cloud](images/devices/witty-cloud.jpg)|
|**ManCaveMade ESP-Live**|**Wemos D1 Mini Relay Shield**|**Witty Cloud**|
|![IKE ESPike](images/devices/ike-espike.jpg)||![Arniex Swifitch](images/devices/arniex-swifitch.jpg)|
|**IKE ESPike**|**STM_RELAY**|**Arniex Swifitch**|
|![IKE ESPike](images/devices/ike-espike.jpg)|![Pilotak ESP DIN](images/devices/pilotak-esp-din.jpg)|![Arniex Swifitch](images/devices/arniex-swifitch.jpg)|
|**IKE ESPike**|**Pilotak ESP DIN|**Arniex Swifitch**|
|![Heltec Touch Relay](images/devices/heltec-touch-relay.jpg)|![Generic Relay v4.0](images/devices/generic-relay-40.jpg)|![Generic RGBLed v1.0](images/devices/generic-rgbled-10.jpg)|
|**Heltec Touch Relay**|**Generic Relay v4.0**|**Generic RGBLed v1.0**|
|![Generic DHT11 v1.0](images/devices/generic-dht11-10.jpg)|![Generic DS18B20 v1.0](images/devices/generic-ds18b20-10.jpg)||
|**Generic DHT11 v1.0**|**Generic DS18B20 v1.0**||
|![Tonbux Mosquito Killer](images/devices/tonbux-mosquito-killer.jpg)|||
|**Tonbux Mosquito Killer**|||
**Other supported boards:**
IteadStudio Sonoff S31, IteadStudio Sonoff POW R2, Zhilde ZLD-EU55-W, Luani HVIO
|![Generic DHT11 v1.0](images/devices/generic-dht11-10.jpg)|![Generic DS18B20 v1.0](images/devices/generic-ds18b20-10.jpg)|![Bruno Horta's OnOfre](images/devices/bh-onofre.jpg)|
|**Generic DHT11 v1.0**|**Generic DS18B20 v1.0**|**Bruno Horta's OnOfre**|
|![Allnet ESP8266-UP-Relay](images/devices/allnet-esp8266-up-relay.jpg)|![RH Electronics Geiger Counter](images/devices/generic-geiger-diy.png)|![Luani HVIO](images/devices/luani-hvio.jpg)|
|**Allnet ESP8266-UP-Relay**|**RH Electronics Geiger Counter**|**Luani HVIO**|
|![Tonbux Mosquito Killer](images/devices/tonbux-mosquito-killer.jpg)|![Itead Sonoff IFAN02](images/devices/itead-sonoff-ifan02.jpg)||
|**Tonbux Mosquito Killer**|**Itead Sonoff IFAN02**|||
**Other supported boards (beta):**
KMC 4 Outlet, Gosund WS1, Smart Dual Plug, MakerFocus Intelligent Module LM33 for Lamps


+ 107
- 47
code/build.sh View File

@ -1,73 +1,133 @@
#!/bin/bash
set -e
# Welcome
echo "--------------------------------------------------------------"
echo "ESPURNA FIRMWARE BUILDER"
# Script settings
version=$(grep APP_VERSION espurna/config/version.h | awk '{print $3}' | sed 's/"//g')
(command -v git && git rev-parse --is-inside-work-tree) 2>&1>/dev/null
if [ $? -eq 0 ]; then
git_revision=$(git rev-parse --short HEAD)
git_version=$(git describe --tags)
else
git_revision=
git_version=$version
fi
par_build=0
par_thread=${BUILDER_THREAD:-0}
par_total_threads=${BUILDER_TOTAL_THREADS:-4}
if [ ${par_thread} -ne ${par_thread} -o \
${par_total_threads} -ne ${par_total_threads} ]; then
echo "Parallel threads should be a number."
exit
fi
if [ ${par_thread} -ge ${par_total_threads} ]; then
echo "Current thread is greater than total threads. Doesn't make sense"
exit
fi
# Available environments
travis=$(grep env: platformio.ini | grep travis | sed 's/\[env://' | sed 's/\]/ /' | sort)
available=$(grep env: platformio.ini | grep -v ota | grep -v ssl | grep -v travis | sed 's/\[env://' | sed 's/\]/ /' | sort)
# Parameters
environments=$@
if [ "$environments" == "list" ]; then
# Build tools settings
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DAPP_REVISION='\"$git_revision\"'"
# Functions
print_available() {
echo "--------------------------------------------------------------"
echo "Available environments:"
for environment in $available; do
echo "* $environment"
done
exit
fi
# Environments to build
if [ $# -eq 0 ]; then
}
environments=$available
print_environments() {
echo "--------------------------------------------------------------"
echo "Current environments:"
for environment in $environments; do
echo "* $environment"
done
}
# Hook to build travis test envs
if [[ "${TRAVIS_BRANCH}" != "" ]]; then
re='^[0-9]+\.[0-9]+\.[0-9]+$'
if ! [[ ${TRAVIS_BRANCH} =~ $re ]]; then
environments=$travis
fi
set_default_environments() {
# Hook to build in parallel when using travis
if [[ "${TRAVIS_BUILD_STAGE_NAME}" = "Release" ]] && [ ${par_build} ]; then
environments=$(echo ${available} | \
awk -v par_thread=${par_thread} -v par_total_threads=${par_total_threads} \
'{ for (i = 1; i <= NF; i++) if (++j % par_total_threads == par_thread ) print $i; }')
return
fi
fi
# Only build travisN
if [[ "${TRAVIS_BUILD_STAGE_NAME}" = "Test" ]]; then
environments=$travis
return
fi
# Get current version
version=$(grep APP_VERSION espurna/config/version.h | awk '{print $3}' | sed 's/"//g')
echo "Building for version $version"
# Fallback to all available environments
environments=$available
}
# Create output folder
mkdir -p firmware
build_webui() {
# Build system uses gulpscript.js to build web interface
if [ ! -e node_modules/gulp/bin/gulp.js ]; then
echo "--------------------------------------------------------------"
echo "Installing dependencies..."
npm install --only=dev
fi
if [ ! -e node_modules/gulp/bin/gulp.js ]; then
# Recreate web interface (espurna/data/index.html.*.gz.h)
echo "--------------------------------------------------------------"
echo "Installing dependencies..."
npm install --only=dev
fi
echo "Building web interface..."
node node_modules/gulp/bin/gulp.js || exit
}
echo "--------------------------------------------------------------"
echo "Get revision..."
revision=$(git rev-parse HEAD)
revision=${revision:0:7}
cp espurna/config/version.h espurna/config/version.h.original
sed -i -e "s/APP_REVISION \".*\"/APP_REVISION \"$revision\"/g" espurna/config/version.h
build_environments() {
echo "--------------------------------------------------------------"
echo "Building firmware images..."
mkdir -p ../firmware/espurna-$version
# Recreate web interface
echo "--------------------------------------------------------------"
echo "Building web interface..."
node node_modules/gulp/bin/gulp.js || exit
for environment in $environments; do
echo -n "* espurna-$version-$environment.bin --- "
platformio run --silent --environment $environment || exit 1
stat -c %s .pioenvs/$environment/firmware.bin
[[ "${TRAVIS_BUILD_STAGE_NAME}" = "Test" ]] || \
mv .pioenvs/$environment/firmware.bin ../firmware/espurna-$version/espurna-$version-$environment.bin
done
echo "--------------------------------------------------------------"
}
# Build all the required firmware images
echo "--------------------------------------------------------------"
echo "Building firmware images..."
mkdir -p ../firmware/espurna-$version
for environment in $environments; do
echo "* espurna-$version-$environment.bin"
platformio run --silent --environment $environment || exit 1
mv .pioenvs/$environment/firmware.bin ../firmware/espurna-$version/espurna-$version-$environment.bin
# Parameters
while getopts "lp" opt; do
case $opt in
l)
print_available
exit
;;
p)
par_build=1
;;
esac
done
shift $((OPTIND-1))
# Welcome
echo "--------------------------------------------------------------"
echo "ESPURNA FIRMWARE BUILDER"
echo "Building for version ${git_version}"
# Environments to build
environments=$@
if [ $# -eq 0 ]; then
set_default_environments
fi
if [[ "${CI}" = true ]]; then
print_environments
fi
mv espurna/config/version.h.original espurna/config/version.h
build_webui
build_environments

+ 2
- 1
code/eagle.flash.1m0m1s.ld View File

@ -1,6 +1,7 @@
/* Flash Split for 1M chips */
/* sketch 999KB */
/* eeprom 20KB */
/* eeprom 4KB */
/* reserved 16KB */
MEMORY
{


+ 2
- 1
code/eagle.flash.512k0m1s.ld View File

@ -1,6 +1,7 @@
/* Flash Split for 512K chips */
/* sketch 487KB */
/* eeprom 20KB */
/* eeprom 4KB */
/* reserved 16KB */
MEMORY
{


+ 4
- 0
code/espurna/button.ino View File

@ -10,6 +10,8 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
// BUTTON
// -----------------------------------------------------------------------------
#if BUTTON_SUPPORT
#include <DebounceEvent.h>
#include <vector>
@ -266,3 +268,5 @@ void buttonLoop() {
#endif
}
#endif // BUTTON_SUPPORT

+ 6
- 1
code/espurna/config/arduino.h View File

@ -11,6 +11,7 @@
//#define WEMOS_D1_MINI_RELAYSHIELD
//#define TINKERMAN_ESPURNA_H06
//#define TINKERMAN_ESPURNA_H08
//#define TINKERMAN_RFM69GW
//#define ITEAD_SONOFF_BASIC
//#define ITEAD_SONOFF_RF
//#define ITEAD_SONOFF_TH
@ -82,9 +83,12 @@
//#define LUANI_HVIO
//#define ALLNET_4DUINO_IOT_WLAN_RELAIS
//#define TONBUX_MOSQUITO_KILLER
//#define NEO_COOLCAM_POWER_PLUG_WIFI
//#define NEO_COOLCAM_NAS_WR01W
//#define ESTINK_WIFI_POWER_STRIP
//#define PILOTAK_ESP_DIN_V1
//#define BLITZWOLF_BWSHP2
//#define BH_ONOFRE
//#define ITEAD_SONOFF_IFAN02
//--------------------------------------------------------------------------------
// Features (values below are non-default values)
@ -139,6 +143,7 @@
//#define HCSR04_SUPPORT 1
//#define HLW8012_SUPPORT 1
//#define MHZ19_SUPPORT 1
//#define NTC_SUPPORT 1
//#define PMSX003_SUPPORT 1
//#define PZEM004T_SUPPORT 1
//#define SHT3X_I2C_SUPPORT 1


+ 5
- 0
code/espurna/config/defaults.h View File

@ -438,3 +438,8 @@
#ifndef LIGHT_PROVIDER
#define LIGHT_PROVIDER LIGHT_PROVIDER_NONE
#endif
// App revision, populated by the build script
#ifndef APP_REVISION
#define APP_REVISION ""
#endif

+ 51
- 0
code/espurna/config/dependencies.h View File

@ -48,3 +48,54 @@
#undef NTP_SUPPORT
#define NTP_SUPPORT 1 // Scheduler needs NTP
#endif
// -----------------------------------------------------------------------------
// WEB UI IMAGE
// -----------------------------------------------------------------------------
#define WEBUI_IMAGE_SMALL 0
#define WEBUI_IMAGE_LIGHT 1
#define WEBUI_IMAGE_SENSOR 2
#define WEBUI_IMAGE_RFBRIDGE 4
#define WEBUI_IMAGE_RFM69 8
#define WEBUI_IMAGE_FULL 15
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#ifdef WEBUI_IMAGE
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#else
#define WEBUI_IMAGE WEBUI_IMAGE_LIGHT
#endif
#endif
#if SENSOR_SUPPORT == 1
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_SENSOR
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#if defined(ITEAD_SONOFF_RFBRIDGE)
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_RFBRIDGE
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#if RFM69_SUPPORT == 1
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_RFM69
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_SMALL
#endif

+ 84
- 0
code/espurna/config/general.h View File

@ -201,6 +201,10 @@
// BUTTON
//------------------------------------------------------------------------------
#ifndef BUTTON_SUPPORT
#define BUTTON_SUPPORT 1
#endif
#ifndef BUTTON_DEBOUNCE_DELAY
#define BUTTON_DEBOUNCE_DELAY 50 // Debounce delay (ms)
#endif
@ -217,6 +221,14 @@
#define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click
#endif
//------------------------------------------------------------------------------
// LED
//------------------------------------------------------------------------------
#ifndef LED_SUPPORT
#define LED_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// RELAY
//------------------------------------------------------------------------------
@ -377,6 +389,15 @@
#define WEB_PORT 80 // HTTP port
#endif
// Defining a WEB_REMOTE_DOMAIN will enable Cross-Origin Resource Sharing (CORS)
// so you will be able to login to this device from another domain. This will allow
// you to manage all ESPurna devices in your local network from a unique installation
// of the web UI. This installation could be in a local server (a Raspberry Pi, for instance)
// or in the Internet. Since the WebUI is just one compressed file with HTML, CSS and JS
// there are no special requirements. Any static web server will do (NGinx, Apache, Lighttpd,...).
// The only requirement is that the resource must be available under this domain.
#define WEB_REMOTE_DOMAIN "http://tinkerman.cat"
// -----------------------------------------------------------------------------
// WEBSOCKETS
// -----------------------------------------------------------------------------
@ -685,6 +706,7 @@
#define MQTT_TOPIC_LOADAVG "loadavg"
#define MQTT_TOPIC_BOARD "board"
#define MQTT_TOPIC_PULSE "pulse"
#define MQTT_TOPIC_SPEED "speed"
// Light module
#define MQTT_TOPIC_CHANNEL "channel"
@ -1202,3 +1224,65 @@
#define RF_DEBOUNCE 500
#define RF_LEARN_TIMEOUT 60000
//--------------------------------------------------------------------------------
// Custom RFM69 to MQTT bridge
// Check http://tinkerman.cat/rfm69-wifi-gateway/
// Enable support by passing RFM69_SUPPORT=1 build flag
//--------------------------------------------------------------------------------
#ifndef RFM69_SUPPORT
#define RFM69_SUPPORT 0
#endif
#ifndef RFM69_MAX_TOPICS
#define RFM69_MAX_TOPICS 50
#endif
#ifndef RFM69_DEFAULT_TOPIC
#define RFM69_DEFAULT_TOPIC "/rfm69gw/{node}/{key}"
#endif
#ifndef RFM69_NODE_ID
#define RFM69_NODE_ID 2
#endif
#ifndef RFM69_GATEWAY_ID
#define RFM69_GATEWAY_ID 2
#endif
#ifndef RFM69_NETWORK_ID
#define RFM69_NETWORK_ID 164
#endif
#ifndef RFM69_PROMISCUOUS
#define RFM69_PROMISCUOUS 1
#endif
#ifndef RFM69_PROMISCUOUS_SENDS
#define RFM69_PROMISCUOUS_SENDS 0
#endif
#ifndef RFM69_FREQUENCY
#define RFM69_FREQUENCY RF69_868MHZ
#endif
#ifndef RFM69_ENCRYPTKEY
#define RFM69_ENCRYPTKEY "fibonacci0123456"
#endif
#ifndef RFM69_CS_PIN
#define RFM69_CS_PIN SS
#endif
#ifndef RFM69_IRQ_PIN
#define RFM69_IRQ_PIN 5
#endif
#ifndef RFM69_RESET_PIN
#define RFM69_RESET_PIN 7
#endif
#ifndef RFM69_IS_RFM69HW
#define RFM69_IS_RFM69HW 0
#endif

+ 203
- 5
code/espurna/config/hardware.h View File

@ -37,9 +37,11 @@
// Disable non-core modules
#define ALEXA_SUPPORT 0
#define BROKER_SUPPORT 0
#define BUTTON_SUPPORT 0
#define DOMOTICZ_SUPPORT 0
#define HOMEASSISTANT_SUPPORT 0
#define I2C_SUPPORT 0
#define MDNS_SERVER_SUPPORT 0
#define MQTT_SUPPORT 0
#define NTP_SUPPORT 0
#define SCHEDULER_SUPPORT 0
@ -47,6 +49,14 @@
#define THINGSPEAK_SUPPORT 0
#define WEB_SUPPORT 0
// Extra light-weight image
//#define DEBUG_SERIAL_SUPPORT 0
//#define DEBUG_TELNET_SUPPORT 0
//#define DEBUG_WEB_SUPPORT 0
//#define LED_SUPPORT 0
//#define TELNET_SUPPORT 0
//#define TERMINAL_SUPPORT 0
// -----------------------------------------------------------------------------
// Development boards
// -----------------------------------------------------------------------------
@ -196,6 +206,30 @@
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_INVERSE
// Check http://tinkerman.cat/rfm69-wifi-gateway/
#elif defined(TINKERMAN_RFM69GW)
// Info
#define MANUFACTURER "TINKERMAN"
#define DEVICE "RFM69GW"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// RFM69GW
#define RFM69_SUPPORT 1
// Disable non-core modules
#define ALEXA_SUPPORT 0
#define DOMOTICZ_SUPPORT 0
#define HOMEASSISTANT_SUPPORT 0
#define I2C_SUPPORT 0
#define SCHEDULER_SUPPORT 0
#define SENSOR_SUPPORT 0
#define THINGSPEAK_SUPPORT 0
// -----------------------------------------------------------------------------
// Itead Studio boards
// -----------------------------------------------------------------------------
@ -846,6 +880,36 @@
#define CSE7766_SUPPORT 1
#define CSE7766_PIN 1
#elif defined(ITEAD_SONOFF_IFAN02)
// Info
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_IFAN02"
// These are virtual buttons triggered by the remote
#define BUTTON1_PIN 0
#define BUTTON2_PIN 9
#define BUTTON3_PIN 10
#define BUTTON4_PIN 14
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON4_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// Relays
#define RELAY1_PIN 12
#define RELAY2_PIN 5
#define RELAY3_PIN 4
#define RELAY4_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_TYPE RELAY_TYPE_NORMAL
#define RELAY3_TYPE RELAY_TYPE_NORMAL
#define RELAY4_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// YJZK
// -----------------------------------------------------------------------------
@ -1827,7 +1891,7 @@
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON3_PIN 14
#define BUTTON3_RELAY 2
#define BUTTON3_RELAY 3
#define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define RELAY1_PIN 4
@ -2164,15 +2228,16 @@
#define LED4_RELAY 1
// -----------------------------------------------------------------------------
// NEO Coolcam Power Plug
// NEO Coolcam NAS-WR01W Wifi Smart Power Plug
// https://es.aliexpress.com/item/-/32854589733.html?spm=a219c.12010608.0.0.6d084e68xX0y5N
// https://www.fasttech.com/product/9649426-neo-coolcam-nas-wr01w-wifi-smart-power-plug-eu
// -----------------------------------------------------------------------------
#elif defined(NEO_COOLCAM_POWER_PLUG_WIFI)
#elif defined(NEO_COOLCAM_NAS_WR01W)
// Info
#define MANUFACTURER "NEO_COOLCAM"
#define DEVICE "POWER_PLUG_WIFI"
#define DEVICE "NAS_WR01W"
// Buttons
#define BUTTON1_PIN 13
@ -2240,6 +2305,125 @@
#define LED3_RELAY 3
#define LED4_RELAY 4
// -----------------------------------------------------------------------------
// Bruno Horta's OnOfre
// https://www.bhonofre.pt/
// https://github.com/brunohorta82/BH_OnOfre/
// -----------------------------------------------------------------------------
#elif defined(BH_ONOFRE)
// Info
#define MANUFACTURER "BH"
#define DEVICE "ONOFRE"
// Buttons
#define BUTTON1_PIN 12
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP
#define BUTTON1_RELAY 1
#define BUTTON2_PIN 13
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP
#define BUTTON2_RELAY 2
// Relays
#define RELAY1_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_PIN 5
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// -----------------------------------------------------------------------------
// Several boards under different names uing a power chip labelled BL0937 or HJL-01
// * Blitzwolf (https://www.amazon.es/Inteligente-Temporización-Dispositivos-Cualquier-BlitzWolf/dp/B07BMQP142)
// * HomeCube (https://www.amazon.de/Steckdose-Homecube-intelligente-Verbrauchsanzeige-funktioniert/dp/B076Q2LKHG)
// * Coosa (https://www.amazon.com/COOSA-Monitoring-Function-Campatible-Assiatant/dp/B0788W9TDR)
// * Goosund (http://www.gosund.com/?m=content&c=index&a=show&catid=6&id=5)
// * Ablue (https://www.amazon.de/Intelligente-Steckdose-Ablue-Funktioniert-Assistant/dp/B076DRFRZC)
// -----------------------------------------------------------------------------
#elif defined(BLITZWOLF_BWSHP2)
// Info
#define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHP2"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#define LED2_PIN 0
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_FINDME
#define LED2_RELAY 1
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 14
#define HLW8012_CF_PIN 5
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 25740
#define HLW8012_VOLTAGE_RATIO 313400
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// VANZAVANZU Smart Outlet Socket (based on BL0937 or HJL-01)
// https://www.amazon.com/Smart-Plug-Wifi-Mini-VANZAVANZU/dp/B078PHD6S5
// -----------------------------------------------------------------------------
#elif defined(VANZAVANZU_SMART_WIFI_PLUG_MINI)
// Info
#define MANUFACTURER "VANZAVANZU"
#define DEVICE "SMART_WIFI_PLUG_MINI"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#define LED2_PIN 0
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_FINDME
#define LED2_RELAY 1
// Disable UART noise
#define DEBUG_SERIAL_SUPPORT 0
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 3
#define HLW8012_CF1_PIN 14
#define HLW8012_CF_PIN 5
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 25740
#define HLW8012_VOLTAGE_RATIO 313400
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// TEST boards (do not use!!)
// -----------------------------------------------------------------------------
@ -2282,6 +2466,8 @@
#define EMON_ADS1X15_SUPPORT 1
#define SHT3X_I2C_SUPPORT 1
#define SI7021_SUPPORT 1
#define PMSX003_SUPPORT 1
#define SENSEAIR_SUPPORT 1
// A bit of lights - pin 5
@ -2362,7 +2548,7 @@
#define MHZ19_TX_PIN 10
// PZEM004T
#define PZEM004T_SUPPORT 0 // not working?
#define PZEM004T_SUPPORT 1
#define PZEM004T_RX_PIN 11
#define PZEM004T_TX_PIN 12
@ -2374,6 +2560,13 @@
#define GUVAS12SD_SUPPORT 1
#define GUVAS12SD_PIN 14
// Test non-default modules
#define MDNS_CLIENT_SUPPORT 1
#define NOFUSS_SUPPORT 1
#define UART_MQTT_SUPPORT 1
#define INFLUXDB_SUPPORT 1
#define IR_SUPPORT 1
#elif defined(TRAVIS03)
// Relay provider light/my92XX
@ -2397,6 +2590,11 @@
#define EMON_ANALOG_SUPPORT 1
#endif
// Test non-default modules
#define LLMNR_SUPPORT 1
#define NETBIOS_SUPPORT 1
#define SSDP_SUPPORT 1
#endif
// -----------------------------------------------------------------------------


+ 6
- 0
code/espurna/config/progmem.h View File

@ -82,6 +82,9 @@ PROGMEM const char espurna_modules[] =
#if RF_SUPPORT
"RF "
#endif
#if RFM69_SUPPORT
"RFM69 "
#endif
#if SCHEDULER_SUPPORT
"SCHEDULER "
#endif
@ -172,6 +175,9 @@ PROGMEM const char espurna_sensors[] =
#if MHZ19_SUPPORT
"MHZ19 "
#endif
#if NTC_SUPPORT
"NTC "
#endif
#if PMSX003_SUPPORT
"PMSX003 "
#endif


+ 106
- 61
code/espurna/config/prototypes.h View File

@ -7,73 +7,51 @@ extern "C" {
#include "user_interface.h"
}
// -----------------------------------------------------------------------------
// EEPROM_ROTATE
// -----------------------------------------------------------------------------
#include <EEPROM_Rotate.h>
EEPROM_Rotate EEPROMr;
// -----------------------------------------------------------------------------
// WebServer
// -----------------------------------------------------------------------------
#include <ESPAsyncWebServer.h>
AsyncWebServer * webServer();
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
typedef std::function<void(char *, size_t)> api_get_callback_f;
typedef std::function<void(const char *)> api_put_callback_f;
void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn = NULL);
#if WEB_SUPPORT
typedef std::function<void(char *, size_t)> api_get_callback_f;
typedef std::function<void(const char *)> api_put_callback_f;
void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn = NULL);
#else
#define api_get_callback_f void *
#define api_put_callback_f void *
#endif
// -----------------------------------------------------------------------------
// WebSockets
// Broker
// -----------------------------------------------------------------------------
typedef std::function<void(JsonObject&)> ws_on_send_callback_f;
void wsOnSendRegister(ws_on_send_callback_f callback);
void wsSend(ws_on_send_callback_f sender);
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
void wsOnActionRegister(ws_on_action_callback_f callback);
typedef std::function<void(void)> ws_on_after_parse_callback_f;
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback);
typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f;
void wsOnReceiveRegister(ws_on_receive_callback_f callback);
#if BROKER_SUPPORT
void brokerRegister(void (*)(const char *, unsigned char, const char *));
#endif
// -----------------------------------------------------------------------------
// WIFI
// Debug
// -----------------------------------------------------------------------------
#include "JustWifi.h"
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback);
void debugSend(const char * format, ...);
void debugSend_P(PGM_P format, ...);
// -----------------------------------------------------------------------------
// MQTT
// Domoticz
// -----------------------------------------------------------------------------
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
void mqttRegister(mqtt_callback_f callback);
String mqttMagnitude(char * topic);
#if DOMOTICZ_SUPPORT
template<typename T> void domoticzSend(const char * key, T value);
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue);
#endif
// -----------------------------------------------------------------------------
// Broker
// EEPROM_ROTATE
// -----------------------------------------------------------------------------
void brokerRegister(void (*)(const char *, unsigned char, const char *));
#include <EEPROM_Rotate.h>
EEPROM_Rotate EEPROMr;
// -----------------------------------------------------------------------------
// Settings
// GPIO
// -----------------------------------------------------------------------------
#include <Embedis.h>
template<typename T> bool setSetting(const String& key, T value);
template<typename T> bool setSetting(const String& key, unsigned int index, T value);
template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
void settingsGetJson(JsonObject& data);
bool settingsRestoreJson(JsonObject& data);
void settingsRegisterCommand(const String& name, void (*call)(Embedis*));
void settingsInject(void *data, size_t len);
Stream & settingsSerial();
bool gpioValid(unsigned char gpio);
bool gpioGetLock(unsigned char gpio);
bool gpioReleaseLock(unsigned char gpio);
// -----------------------------------------------------------------------------
// I2C
@ -101,23 +79,47 @@ int16_t i2c_read_int16_le(uint8_t address, uint8_t reg);
void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len);
// -----------------------------------------------------------------------------
// GPIO
// MQTT
// -----------------------------------------------------------------------------
bool gpioValid(unsigned char gpio);
bool gpioGetLock(unsigned char gpio);
bool gpioReleaseLock(unsigned char gpio);
#if MQTT_SUPPORT
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
void mqttRegister(mqtt_callback_f callback);
String mqttMagnitude(char * topic);
#else
#define mqtt_callback_f void *
#endif
// -----------------------------------------------------------------------------
// Debug
// OTA
// -----------------------------------------------------------------------------
void debugSend(const char * format, ...);
void debugSend_P(PGM_P format, ...);
#include "ESPAsyncTCP.h"
// -----------------------------------------------------------------------------
// Domoticz
// RFM69
// -----------------------------------------------------------------------------
template<typename T> void domoticzSend(const char * key, T value);
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue);
typedef struct {
unsigned long messageID;
unsigned char packetID;
unsigned char senderID;
unsigned char targetID;
char * key;
char * value;
int16_t rssi;
} packet_t;
// -----------------------------------------------------------------------------
// Settings
// -----------------------------------------------------------------------------
#include <Embedis.h>
template<typename T> bool setSetting(const String& key, T value);
template<typename T> bool setSetting(const String& key, unsigned int index, T value);
template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
void settingsGetJson(JsonObject& data);
bool settingsRestoreJson(JsonObject& data);
void settingsRegisterCommand(const String& name, void (*call)(Embedis*));
void settingsInject(void *data, size_t len);
Stream & settingsSerial();
// -----------------------------------------------------------------------------
// Utils
@ -125,5 +127,48 @@ template<typename T> void domoticzSend(const char * key, T nvalue, const char *
char * ltrim(char * s);
void nice_delay(unsigned long ms);
#define ARRAYINIT(type, name, ...) \
type name[] = {__VA_ARGS__};
#define ARRAYINIT(type, name, ...) type name[] = {__VA_ARGS__};
// -----------------------------------------------------------------------------
// WebServer
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
#include <ESPAsyncWebServer.h>
AsyncWebServer * webServer();
#else
#define AsyncWebServerRequest void
#define ArRequestHandlerFunction void
#define AsyncWebSocketClient void
#define AsyncWebSocket void
#define AwsEventType void *
#endif
// -----------------------------------------------------------------------------
// WebSockets
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
typedef std::function<void(JsonObject&)> ws_on_send_callback_f;
void wsOnSendRegister(ws_on_send_callback_f callback);
void wsSend(ws_on_send_callback_f sender);
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
void wsOnActionRegister(ws_on_action_callback_f callback);
typedef std::function<void(void)> ws_on_after_parse_callback_f;
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback);
typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f;
void wsOnReceiveRegister(ws_on_receive_callback_f callback);
#else
#define ws_on_send_callback_f void *
#define ws_on_action_callback_f void *
#define ws_on_after_parse_callback_f void *
#define ws_on_receive_callback_f void *
#endif
// -----------------------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------------------
#include "JustWifi.h"
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback);

+ 72
- 1
code/espurna/config/sensors.h View File

@ -79,6 +79,14 @@
#define ANALOG_SUPPORT 0
#endif
#ifndef ANALOG_SAMPLES
#define ANALOG_SAMPLES 10 // Number of samples
#endif
#ifndef ANALOG_DELAY
#define ANALOG_DELAY 0 // Delay between samples in micros
#endif
//------------------------------------------------------------------------------
// BH1750
// Enable support by passing BH1750_SUPPORT=1 build flag
@ -383,7 +391,27 @@
#define HLW8012_VOLTAGE_R_DOWN ( 1000 ) // Downstream voltage resistor
#endif
#ifndef HLW8012_CURRENT_RATIO
#define HLW8012_CURRENT_RATIO 0 // Set to 0 to use factory defaults
#endif
#ifndef HLW8012_VOLTAGE_RATIO
#define HLW8012_VOLTAGE_RATIO 0 // Set to 0 to use factory defaults
#endif
#ifndef HLW8012_POWER_RATIO
#define HLW8012_POWER_RATIO 0 // Set to 0 to use factory defaults
#endif
#ifndef HLW8012_USE_INTERRUPTS
#define HLW8012_USE_INTERRUPTS 1 // Use interrupts to trap HLW8012 signals
#endif
#ifndef HLW8012_INTERRUPT_ON
#define HLW8012_INTERRUPT_ON CHANGE // When to trigger the interrupt
// Use CHANGE for HLW8012
// Use FALLING for BL0937 / HJL0
#endif
//------------------------------------------------------------------------------
// MHZ19 CO2 sensor
@ -402,6 +430,43 @@
#define MHZ19_TX_PIN 15
#endif
//------------------------------------------------------------------------------
// NTC sensor
// Enable support by passing NTC_SUPPORT=1 build flag
//--------------------------------------------------------------------------------
#ifndef NTC_SUPPORT
#define NTC_SUPPORT 0
#endif
#ifndef NTC_SAMPLES
#define NTC_SAMPLES 10 // Number of samples
#endif
#ifndef NTC_DELAY
#define NTC_DELAY 0 // Delay between samples in micros
#endif
#ifndef NTC_R_UP
#define NTC_R_UP 0 // Resistor upstream, set to 0 if none
#endif
#ifndef NTC_R_DOWN
#define NTC_R_DOWN 10000 // Resistor downstream, set to 0 if none
#endif
#ifndef NTC_T0
#define NTC_T0 298.15 // 25 Celsius
#endif
#ifndef NTC_R0
#define NTC_R0 10000 // Resistance at T0
#endif
#ifndef NTC_BETA
#define NTC_BETA 3977 // Beta coeficient
#endif
//------------------------------------------------------------------------------
// SenseAir CO2 sensor
// Enable support by passing SENSEAIR_SUPPORT=1 build flag
@ -469,7 +534,7 @@
#endif
#ifndef PZEM004T_HW_PORT
#define PZEM004T_HW_PORT Serial1 // Hardware serial port (if PZEM004T_USE_SOFT == 0)
#define PZEM004T_HW_PORT Serial // Hardware serial port (if PZEM004T_USE_SOFT == 0)
#endif
//------------------------------------------------------------------------------
@ -561,6 +626,7 @@
HCSR04_SUPPORT || \
HLW8012_SUPPORT || \
MHZ19_SUPPORT || \
NTC_SUPPORT || \
SENSEAIR_SUPPORT || \
PMSX003_SUPPORT || \
PZEM004T_SUPPORT || \
@ -691,6 +757,11 @@
#include "../sensors/MHZ19Sensor.h"
#endif
#if NTC_SUPPORT
#include "../sensors/AnalogSensor.h"
#include "../sensors/NTCSensor.h"
#endif
#if SENSEAIR_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/SenseAirSensor.h"


+ 1
- 0
code/espurna/config/types.h View File

@ -268,6 +268,7 @@
#define SENSOR_HCSR04_ID 0x23
#define SENSOR_SENSEAIR_ID 0x24
#define SENSOR_GEIGER_ID 0x25
#define SENSOR_NTC_ID 0x26
//--------------------------------------------------------------------------------
// Magnitudes


+ 1
- 2
code/espurna/config/version.h View File

@ -1,6 +1,5 @@
#define APP_NAME "ESPURNA"
#define APP_VERSION "1.13.0c"
#define APP_REVISION "db84006"
#define APP_VERSION "1.13.1"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"
#define CFG_VERSION 3

BIN
code/espurna/data/index.all.html.gz View File


BIN
code/espurna/data/index.html.gz View File


BIN
code/espurna/data/index.light.html.gz View File


BIN
code/espurna/data/index.rfbridge.html.gz View File


BIN
code/espurna/data/index.rfm69.html.gz View File


BIN
code/espurna/data/index.sensor.html.gz View File


BIN
code/espurna/data/index.small.html.gz View File


+ 10
- 3
code/espurna/espurna.ino View File

@ -49,7 +49,7 @@ void setup() {
// Init EEPROM
eepromSetup();
// Init Serial, SPIFFS and system check
systemSetup();
@ -99,8 +99,12 @@ void setup() {
#endif
relaySetup();
buttonSetup();
ledSetup();
#if BUTTON_SUPPORT
buttonSetup();
#endif
#if LED_SUPPORT
ledSetup();
#endif
#if MQTT_SUPPORT
mqttSetup();
#endif
@ -140,6 +144,9 @@ void setup() {
#if THINGSPEAK_SUPPORT
tspkSetup();
#endif
#if RFM69_SUPPORT
rfm69Setup();
#endif
#if RF_SUPPORT
rfSetup();
#endif


code/espurna/libs/fs_math.c → code/espurna/fs_math.c View File

@ -6,7 +6,7 @@
/* BEGIN fs_math.c */
#include "fs_math.h"
#include "libs/fs_math.h"
#include <float.h>
/*

+ 4
- 0
code/espurna/led.ino View File

@ -10,6 +10,8 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
// LED
// -----------------------------------------------------------------------------
#if LED_SUPPORT
typedef struct {
unsigned char pin;
bool reverse;
@ -290,3 +292,5 @@ void ledLoop() {
_led_update = false;
}
#endif LED_SUPPORT

+ 65
- 0
code/espurna/libs/RFM69Wrap.h View File

@ -0,0 +1,65 @@
/*
RFM69Wrap
RFM69 by Felix Ruso (http://LowPowerLab.com/contact) wrapper for ESP8266
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#pragma once
#include <RFM69.h>
#include <RFM69_ATC.h>
#include <SPI.h>
class RFM69Wrap: public RFM69_ATC {
public:
RFM69Wrap(uint8_t slaveSelectPin=RF69_SPI_CS, uint8_t interruptPin=RF69_IRQ_PIN, bool isRFM69HW=false, uint8_t interruptNum=0):
RFM69_ATC(slaveSelectPin, interruptPin, isRFM69HW, interruptNum) {};
protected:
// overriding SPI_CLOCK for ESP8266
void select() {
noInterrupts();
#if defined (SPCR) && defined (SPSR)
// save current SPI settings
_SPCR = SPCR;
_SPSR = SPSR;
#endif
// set RFM69 SPI settings
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
#if defined(__arm__)
SPI.setClockDivider(SPI_CLOCK_DIV16);
#elif defined(ARDUINO_ARCH_ESP8266)
SPI.setClockDivider(SPI_CLOCK_DIV2); // speeding it up for the ESP8266
#else
SPI.setClockDivider(SPI_CLOCK_DIV4);
#endif
digitalWrite(_slaveSelectPin, LOW);
}
};

+ 22
- 10
code/espurna/libs/pwm.h View File

@ -1,15 +1,28 @@
/*
* Copyright (C) 2016 Stefan Brüns <stefan.bruens@rwth-aachen.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __PWM_H__
#define __PWM_H__
/*pwm.h: function and macro definition of PWM API , driver level */
/*user_light.h: user interface for light API, user level*/
/*user_light_adj: API for color changing and lighting effects, user level*/
/*NOTE!! : DO NOT CHANGE THIS FILE*/
/*SUPPORT UP TO 8 PWM CHANNEL*/
//#define PWM_CHANNEL_NUM_MAX 8
/*SUPPORT UP TO 8 PWM CHANNEL*/
#ifndef PWM_CHANNEL_NUM_MAX
#define PWM_CHANNEL_NUM_MAX 8
#endif
struct pwm_param {
uint32 period;
@ -17,7 +30,6 @@ struct pwm_param {
uint32 duty[PWM_CHANNEL_NUM_MAX]; //PWM_CHANNEL<=8
};
/* pwm_init should be called only once, for now */
void pwm_init(uint32 period, uint32 *duty,uint32 pwm_channel_num,uint32 (*pin_info_list)[3]);
void pwm_start(void);


+ 2
- 2
code/espurna/light.ino View File

@ -282,14 +282,14 @@ void _fromHSV(const char * hsv) {
void _fromKelvin(unsigned long kelvin) {
if (!_light_has_color) return;
_light_mireds = constrain(round(1000000UL / kelvin), LIGHT_MIN_MIREDS, LIGHT_MAX_MIREDS);
if (_light_use_cct) {
_setRGBInputValue(LIGHT_MAX_VALUE, LIGHT_MAX_VALUE, LIGHT_MAX_VALUE);
return;
}
_light_mireds = constrain(round(1000000UL / kelvin), LIGHT_MIN_MIREDS, LIGHT_MAX_MIREDS);
// Calculate colors
kelvin /= 100;
unsigned int red = (kelvin <= 66)


+ 62
- 2
code/espurna/migrate.ino View File

@ -953,7 +953,7 @@ void migrate() {
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(NEO_COOLCAM_POWER_PLUG_WIFI)
#elif defined(NEO_COOLCAM_NAS_WR01W)
setSetting("board", 75);
setSetting("ledGPIO", 0, 4);
@ -977,7 +977,7 @@ void migrate() {
#elif defined(ESTINK_WIFI_POWER_STRIP)
setSetting("board", 76);
setSetting("board", 77);
setSetting("btnGPIO", 0, 16);
setSetting("btnRelay", 0, 3);
setSetting("ledGPIO", 0, 0);
@ -1004,6 +1004,66 @@ void migrate() {
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
#elif defined(BH_ONOFRE)
setSetting("board", 78);
setSetting("btnGPIO", 0, 12);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 0, 1);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(BLITZWOLF_BWSHP2)
setSetting("board", 79);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 0);
setSetting("ledLogic", 1, 1);
setSetting("ledMode", 1, LED_MODE_FINDME);
setSetting("ledRelay", 1, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 14);
setSetting("cfGPIO", 5);
setSetting("pwrRatioC", 25740);
setSetting("pwrRatioV", 313400);
setSetting("pwrRatioP", 3414290);
setSetting("hlwSelC", LOW);
setSetting("hlwIntM", FALLING);
#elif defined(TINKERMAN_RFM69GW)
setSetting("board", 80);
setSetting("btnGPIO", 0, 0);
#elif defined(ITEAD_SONOFF_IFAN02)
setSetting("board", 81);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 9);
setSetting("btnGPIO", 2, 10);
setSetting("btnGPIO", 3, 14);
setSetting("ledGPIO", 1, 13);
setSetting("ledLogic", 1, 1);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 5);
setSetting("relayGPIO", 2, 4);
setSetting("relayGPIO", 3, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
#else
// Allow users to define new settings without migration config


code/espurna/libs/pwm.c → code/espurna/pwm.c View File

@ -43,9 +43,9 @@
#endif
#include <c_types.h>
#include <pwm.h>
#include <eagle_soc.h>
#include <ets_sys.h>
#include "libs/pwm.h"
// from SDK hw_timer.c
#define TIMER1_DIVIDE_BY_16 0x0004

+ 69
- 7
code/espurna/relay.ino View File

@ -121,15 +121,15 @@ void _relayProviderStatus(unsigned char id, bool status) {
} else if (_relays[id].type == RELAY_TYPE_LATCHED || _relays[id].type == RELAY_TYPE_LATCHED_INVERSE) {
bool pulse = RELAY_TYPE_LATCHED ? HIGH : LOW;
digitalWrite(_relays[id].pin, !pulse);
digitalWrite(_relays[id].reset_pin, !pulse);
if (status) {
if (GPIO_NONE != _relays[id].reset_pin) digitalWrite(_relays[id].reset_pin, !pulse);
if (status || (GPIO_NONE == _relays[id].reset_pin)) {
digitalWrite(_relays[id].pin, pulse);
} else {
digitalWrite(_relays[id].reset_pin, pulse);
}
nice_delay(RELAY_LATCHING_PULSE);
digitalWrite(_relays[id].pin, !pulse);
digitalWrite(_relays[id].reset_pin, !pulse);
if (GPIO_NONE != _relays[id].reset_pin) digitalWrite(_relays[id].reset_pin, !pulse);
}
#endif
@ -194,7 +194,9 @@ void _relayProcess(bool mode) {
#endif
// Flag relay-based LEDs to update status
ledUpdate(true);
#if LED_SUPPORT
ledUpdate(true);
#endif
_relays[id].report = false;
_relays[id].group_report = false;
@ -203,6 +205,34 @@ void _relayProcess(bool mode) {
}
#if defined(ITEAD_SONOFF_IFAN02)
unsigned char _relay_ifan02_speeds[] = {0, 1, 3, 5};
unsigned char getSpeed() {
unsigned char speed =
(_relays[1].target_status ? 1 : 0) +
(_relays[2].target_status ? 2 : 0) +
(_relays[3].target_status ? 4 : 0);
for (unsigned char i=0; i<4; i++) {
if (_relay_ifan02_speeds[i] == speed) return i;
}
return 0;
}
void setSpeed(unsigned char speed) {
if ((0 <= speed) & (speed <= 3)) {
if (getSpeed() == speed) return;
unsigned char states = _relay_ifan02_speeds[speed];
for (unsigned char i=0; i<3; i++) {
relayStatus(i+1, states & 1 == 1);
states >>= 1;
}
}
}
#endif
// -----------------------------------------------------------------------------
// RELAY
// -----------------------------------------------------------------------------
@ -255,8 +285,8 @@ bool relayStatus(unsigned char id, bool status, bool report, bool group_report)
} else {
unsigned int current_time = millis();
unsigned int fw_end = _relays[id].fw_start + 1000 * RELAY_FLOOD_WINDOW;
unsigned long current_time = millis();
unsigned long fw_end = _relays[id].fw_start + 1000 * RELAY_FLOOD_WINDOW;
unsigned long delay = status ? _relays[id].delay_on : _relays[id].delay_off;
_relays[id].fw_count++;
@ -488,7 +518,7 @@ void _relayBoot() {
void _relayConfigure() {
for (unsigned int i=0; i<_relays.size(); i++) {
pinMode(_relays[i].pin, OUTPUT);
if (_relays[i].type == RELAY_TYPE_LATCHED || _relays[i].type == RELAY_TYPE_LATCHED_INVERSE) {
if (GPIO_NONE != _relays[i].reset_pin) {
pinMode(_relays[i].reset_pin, OUTPUT);
}
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
@ -652,6 +682,19 @@ void relaySetupAPI() {
}
);
#if defined(ITEAD_SONOFF_IFAN02)
apiRegister(MQTT_TOPIC_SPEED,
[relayID](char * buffer, size_t len) {
snprintf(buffer, len, "%u", getSpeed());
},
[relayID](const char * payload) {
setSpeed(atoi(payload));
}
);
#endif
}
}
@ -684,6 +727,14 @@ void relayMQTT(unsigned char id) {
mqttSendRaw(t.c_str(), status ? "1" : "0");
}
}
// Send speed for IFAN02
#if defined (ITEAD_SONOFF_IFAN02)
char buffer[5];
snprintf(buffer, sizeof(buffer), "%u", getSpeed());
mqttSend(MQTT_TOPIC_SPEED, buffer);
#endif
}
void relayMQTT() {
@ -729,6 +780,10 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
snprintf_P(pulse_topic, sizeof(pulse_topic), PSTR("%s/+"), MQTT_TOPIC_PULSE);
mqttSubscribe(pulse_topic);
#if defined(ITEAD_SONOFF_IFAN02)
mqttSubscribe(MQTT_TOPIC_SPEED);
#endif
// Subscribe to group topics
for (unsigned int i=0; i < _relays.size(); i++) {
String t = getSetting("mqttGroup", i, "");
@ -808,6 +863,13 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
}
}
// Itead Sonoff IFAN02
#if defined (ITEAD_SONOFF_IFAN02)
if (t.startsWith(MQTT_TOPIC_SPEED)) {
setSpeed(atoi(payload));
}
#endif
}
if (type == MQTT_DISCONNECT_EVENT) {


+ 15
- 14
code/espurna/rfbridge.ino View File

@ -297,25 +297,13 @@ void _rfbDecode() {
#endif
}
unsigned char id;
unsigned char status;
bool matched = false;
if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
_rfbAck();
_rfbToChar(&_uartbuf[1], buffer);
/* Look for the code, possibly replacing the code with the exact learned one on match
* we want to do this on learn too to be sure that the learned code is the same if it
* is equivalent
*/
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received message '%s'\n"), buffer);
matched = _rfbMatch(buffer, id, status, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Matched message '%s'\n"), buffer);
#if MQTT_SUPPORT
mqttSend(MQTT_TOPIC_RFIN, buffer);
#endif
}
if (action == RF_CODE_LEARN_OK) {
@ -333,8 +321,17 @@ void _rfbDecode() {
}
if (action == RF_CODE_RFIN) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer);
/* Look for the code, possibly replacing the code with the exact learned one on match
* we want to do this on learn too to be sure that the learned code is the same if it
* is equivalent
*/
unsigned char id;
unsigned char status;
bool matched = _rfbMatch(buffer, id, status, buffer);
if (matched) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Matched message '%s'\n"), buffer);
_rfbin = true;
if (status == 2) {
relayToggle(id);
@ -343,6 +340,10 @@ void _rfbDecode() {
}
}
#if MQTT_SUPPORT
mqttSend(MQTT_TOPIC_RFIN, buffer);
#endif
}
}


+ 280
- 0
code/espurna/rfm69.ino View File

@ -0,0 +1,280 @@
/*
RFM69 MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if RFM69_SUPPORT
#include "libs/RFM69Wrap.h"
#define RFM69_PACKET_SEPARATOR ':'
// -----------------------------------------------------------------------------
// Locals
// -----------------------------------------------------------------------------
RFM69Wrap * _rfm69_radio;
struct _node_t {
unsigned long count = 0;
unsigned long missing = 0;
unsigned long duplicates = 0;
unsigned char lastPacketID = 0;
};
_node_t _rfm69_node_info[255];
unsigned char _rfm69_node_count;
unsigned long _rfm69_packet_count;
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
void _rfm69WebSocketOnSend(JsonObject& root) {
root["rfm69Visible"] = 1;
root["rfm69Topic"] = getSetting("rfm69Topic", RFM69_DEFAULT_TOPIC);
root["packetCount"] = _rfm69_packet_count;
root["nodeCount"] = _rfm69_node_count;
JsonArray& mappings = root.createNestedArray("mapping");
for (unsigned char i=0; i<RFM69_MAX_TOPICS; i++) {
unsigned char node = getSetting("node", i, 0).toInt();
if (0 == node) break;
JsonObject& mapping = mappings.createNestedObject();
mapping["node"] = node;
mapping["key"] = getSetting("key", i, "");
mapping["topic"] = getSetting("topic", i, "");
}
}
bool _rfm69WebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "rfm69", 5) == 0) return true;
if (strncmp(key, "node", 4) == 0) return true;
if (strncmp(key, "key", 3) == 0) return true;
if (strncmp(key, "topic", 5) == 0) return true;
return false;
}
void _rfm69WebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "clear-counts") == 0) _rfm69Clear();
}
#endif // WEB_SUPPORT
void _rfm69CleanNodes(unsigned char num) {
// Look for the last defined node
int i = 0;
while (i < num) {
if (getSetting("node", i, 0).toInt() == 0) break;
if (getSetting("key", i, "").length() == 0) break;
if (getSetting("topic", i, "").length() == 0) break;
++i;
}
// Delete all other settings
while (i < WIFI_MAX_NETWORKS) {
delSetting("node", i);
delSetting("key", i);
delSetting("topic", i);
++i;
}
}
void _rfm69Configure() {
_rfm69CleanNodes(RFM69_MAX_TOPICS);
}
// -----------------------------------------------------------------------------
// Radio
// -----------------------------------------------------------------------------
void _rfm69Debug(const char * level, packet_t * data) {
DEBUG_MSG_P(
PSTR("[RFM69] %s: messageID:%05d senderID:%03d targetID:%03d packetID:%03d rssi:%-04d key:%s value:%s\n"),
level,
data->messageID,
data->senderID,
data->targetID,
data->packetID,
data->rssi,
data->key,
data->value
);
}
void _rfm69Process(packet_t * data) {
// Count seen nodes and packets
if (_rfm69_node_info[data->senderID].count == 0) ++_rfm69_node_count;
++_rfm69_packet_count;
// Detect duplicates and missing packets
// packetID==0 means device is not sending packetID info
if (data->packetID > 0) {
if (_rfm69_node_info[data->senderID].count > 0) {
unsigned char gap = data->packetID - _rfm69_node_info[data->senderID].lastPacketID;
if (gap == 0) {
_rfm69_node_info[data->senderID].duplicates = _rfm69_node_info[data->senderID].duplicates + 1;
//_rfm69Debug("DUP", data);
return;
}
if ((gap > 1) && (data->packetID > 1)) {
_rfm69_node_info[data->senderID].missing = _rfm69_node_info[data->senderID].missing + gap - 1;
DEBUG_MSG_P(PSTR("[RFM69] %u missing packets detected\n"), gap - 1);
}
}
}
_rfm69Debug("OK ", data);
_rfm69_node_info[data->senderID].lastPacketID = data->packetID;
_rfm69_node_info[data->senderID].count = _rfm69_node_info[data->senderID].count + 1;
// Send info to websocket clients
{
char buffer[200];
snprintf_P(
buffer,
sizeof(buffer) - 1,
PSTR("{\"nodeCount\": %d, \"packetCount\": %lu, \"packet\": {\"senderID\": %u, \"targetID\": %u, \"packetID\": %u, \"key\": \"%s\", \"value\": \"%s\", \"rssi\": %d, \"duplicates\": %d, \"missing\": %d}}"),
_rfm69_node_count, _rfm69_packet_count,
data->senderID, data->targetID, data->packetID, data->key, data->value, data->rssi,
_rfm69_node_info[data->senderID].duplicates , _rfm69_node_info[data->senderID].missing);
wsSend(buffer);
}
// If we are the target of the message, forward it via MQTT, otherwise quit
if (!RFM69_PROMISCUOUS_SENDS && (RFM69_GATEWAY_ID != data->targetID)) return;
// Try to find a matching mapping
for (unsigned int i=0; i<RFM69_MAX_TOPICS; i++) {
unsigned char node = getSetting("node", i, 0).toInt();
if (0 == node) break;
if ((node == data->senderID) && (getSetting("key", i, "").equals(data->key))) {
mqttSendRaw((char *) getSetting("topic", i, "").c_str(), (char *) String(data->value).c_str());
return;
}
}
// Mapping not found, use default topic
String topic = getSetting("rfm69Topic", RFM69_DEFAULT_TOPIC);
if (topic.length() > 0) {
topic.replace("{node}", String(data->senderID));
topic.replace("{key}", String(data->key));
mqttSendRaw((char *) topic.c_str(), (char *) String(data->value).c_str());
}
}
void _rfm69Loop() {
if (_rfm69_radio->receiveDone()) {
uint8_t senderID = _rfm69_radio->SENDERID;
uint8_t targetID = _rfm69_radio->TARGETID;
int16_t rssi = _rfm69_radio->RSSI;
uint8_t length = _rfm69_radio->DATALEN;
char buffer[length + 1];
strncpy(buffer, (const char *) _rfm69_radio->DATA, length);
buffer[length] = 0;
// Do not send ACKs in promiscuous mode,
// we want to listen without being heard
if (!RFM69_PROMISCUOUS) {
if (_rfm69_radio->ACKRequested()) _rfm69_radio->sendACK();
}
uint8_t parts = 1;
for (uint8_t i=0; i<length; i++) {
if (buffer[i] == RFM69_PACKET_SEPARATOR) ++parts;
}
if (parts > 1) {
char sep[2] = {RFM69_PACKET_SEPARATOR, 0};
uint8_t packetID = 0;
char * key = strtok(buffer, sep);
char * value = strtok(NULL, sep);
if (parts > 2) {
char * packet = strtok(NULL, sep);
packetID = atoi(packet);
}
packet_t message;
message.messageID = ++_rfm69_packet_count;
message.packetID = packetID;
message.senderID = senderID;
message.targetID = targetID;
message.key = key;
message.value = value;
message.rssi = rssi;
_rfm69Process(&message);
}
}
}
void _rfm69Clear() {
for(unsigned int i=0; i<255; i++) {
_rfm69_node_info[i].duplicates = 0;
_rfm69_node_info[i].missing = 0;
}
_rfm69_node_count = 0;
_rfm69_packet_count = 0;
}
// -----------------------------------------------------------------------------
// RFM69
// -----------------------------------------------------------------------------
void rfm69Setup() {
delay(10);
_rfm69Configure();
_rfm69_radio = new RFM69Wrap(RFM69_CS_PIN, RFM69_IRQ_PIN, RFM69_IS_RFM69HW, digitalPinToInterrupt(RFM69_IRQ_PIN));
_rfm69_radio->initialize(RFM69_FREQUENCY, RFM69_NODE_ID, RFM69_NETWORK_ID);
_rfm69_radio->encrypt(RFM69_ENCRYPTKEY);
_rfm69_radio->promiscuous(RFM69_PROMISCUOUS);
if (RFM69_IS_RFM69HW) _rfm69_radio->setHighPower();
DEBUG_MSG_P(PSTR("[RFM69] Worning at %u MHz\n"), RFM69_FREQUENCY == RF69_433MHZ ? 433 : RFM69_FREQUENCY == RF69_868MHZ ? 868 : 915);
DEBUG_MSG_P(PSTR("[RFM69] Node %u\n"), RFM69_NODE_ID);
DEBUG_MSG_P(PSTR("[RFM69] Network %u\n"), RFM69_NETWORK_ID);
DEBUG_MSG_P(PSTR("[RFM69] Promiscuous mode %s\n"), RFM69_PROMISCUOUS ? "ON" : "OFF");
DEBUG_MSG_P(PSTR("[RFM69] Auto Transmission Control (ATC) enabled\n"));
#if WEB_SUPPORT
wsOnSendRegister(_rfm69WebSocketOnSend);
wsOnReceiveRegister(_rfm69WebSocketOnReceive);
wsOnAfterParseRegister(_rfm69Configure);
wsOnActionRegister(_rfm69WebSocketOnAction);
#endif
// Register loop
espurnaRegisterLoop(_rfm69Loop);
}
#endif // RFM69_SUPPORT

+ 20
- 3
code/espurna/sensor.ino View File

@ -183,6 +183,7 @@ void _sensorWebSocketStart(JsonObject& root) {
#if PZEM004T_SUPPORT
if (sensor->getID() == SENSOR_PZEM004T_ID) {
root["pzemVisible"] = 1;
root["pwrVisible"] = 1;
}
#endif
@ -323,6 +324,8 @@ void _sensorLoad() {
#if ANALOG_SUPPORT
{
AnalogSensor * sensor = new AnalogSensor();
sensor->setSamples(ANALOG_SAMPLES);
sensor->setDelay(ANALOG_DELAY);
_sensors.push_back(sensor);
}
#endif
@ -499,6 +502,20 @@ void _sensorLoad() {
}
#endif
#if NTC_SUPPORT
{
NTCSensor * sensor = new NTCSensor();
sensor->setSamples(NTC_SAMPLES);
sensor->setDelay(NTC_DELAY);
sensor->setUpstreamResistor(NTC_R_UP);
sensor->setDownstreamResistor(NTC_R_DOWN);
sensor->setBeta(NTC_BETA);
sensor->setR0(NTC_R0);
sensor->setT0(NTC_T0);
_sensors.push_back(sensor);
}
#endif
#if SENSEAIR_SUPPORT
{
SenseAirSensor * sensor = new SenseAirSensor();
@ -643,13 +660,13 @@ void _sensorInit() {
double value;
value = getSetting("pwrRatioC", 0).toFloat();
value = getSetting("pwrRatioC", HLW8012_CURRENT_RATIO).toFloat();
if (value > 0) sensor->setCurrentRatio(value);
value = getSetting("pwrRatioV", 0).toFloat();
value = getSetting("pwrRatioV", HLW8012_VOLTAGE_RATIO).toFloat();
if (value > 0) sensor->setVoltageRatio(value);
value = getSetting("pwrRatioP", 0).toFloat();
value = getSetting("pwrRatioP", HLW8012_POWER_RATIO).toFloat();
if (value > 0) sensor->setPowerRatio(value);
}


+ 34
- 2
code/espurna/sensors/AnalogSensor.h View File

@ -3,7 +3,7 @@
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ANALOG_SUPPORT
#if SENSOR_SUPPORT && (ANALOG_SUPPORT || NTC_SUPPORT)
#pragma once
@ -27,6 +27,24 @@ class AnalogSensor : public BaseSensor {
_sensor_id = SENSOR_ANALOG_ID;
}
void setSamples(unsigned int samples) {
if (_samples > 0) _samples = samples;
}
void setDelay(unsigned long micros) {
_micros = micros;
}
// ---------------------------------------------------------------------
unsigned int getSamples() {
return _samples;
}
unsigned long getDelay() {
return _micros;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -60,10 +78,24 @@ class AnalogSensor : public BaseSensor {
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return analogRead(0);
if (index == 0) return _read();
return 0;
}
protected:
unsigned int _read() {
if (1 == _samples) return analogRead(0);
unsigned long sum = 0;
for (unsigned int i=0; i<_samples; i++) {
if (i>0) delayMicroseconds(_micros);
sum += analogRead(0);
}
return sum / _samples;
}
unsigned int _samples = 1;
unsigned long _micros = 0;
};


+ 2
- 2
code/espurna/sensors/HLW8012Sensor.h View File

@ -234,13 +234,13 @@ class HLW8012Sensor : public BaseSensor {
if (_interrupt_cf != _cf) {
if (_interrupt_cf != GPIO_NONE) _detach(_interrupt_cf);
_attach(this, _cf, CHANGE);
_attach(this, _cf, HLW8012_INTERRUPT_ON);
_interrupt_cf = _cf;
}
if (_interrupt_cf1 != _cf1) {
if (_interrupt_cf1 != GPIO_NONE) _detach(_interrupt_cf1);
_attach(this, _cf1, CHANGE);
_attach(this, _cf1, HLW8012_INTERRUPT_ON);
_interrupt_cf1 = _cf1;
}


+ 125
- 0
code/espurna/sensors/NTCSensor.h View File

@ -0,0 +1,125 @@
// -----------------------------------------------------------------------------
// NTC Sensor (maps to a NTCSensor)
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && NTC_SUPPORT
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "AnalogSensor.h"
extern "C" {
#include "../libs/fs_math.h"
}
class NTCSensor : public AnalogSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
NTCSensor(): AnalogSensor() {
_count = 1;
_sensor_id = SENSOR_NTC_ID;
}
void setBeta(unsigned long beta) {
if (beta > 0) _beta = beta;
}
void setUpstreamResistor(unsigned long resistance) {
_resistance_up = resistance;
if (_resistance_up > 0) _resistance_down = 0;
}
void setDownstreamResistor(unsigned long resistance) {
_resistance_down = resistance;
if (_resistance_down > 0) _resistance_up = 0;
}
void setR0(unsigned long resistance) {
if (resistance > 0) _R0 = resistance;
}
void setT0(double temperature) {
if (temperature > 0) _T0 = temperature;
}
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Descriptive name of the sensor
String description() {
return String("NTC @ TOUT");
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
double temperature = 0;
if (index == 0) {
// sampled reading
double read = _read();
// Ru = (1023/c - 1) * Rd
double resistance;
double alpha = (1023.0 / read) - 1;
if (_resistance_down > 0) {
resistance = _resistance_down * alpha;
} else if (0 == alpha) {
resistance = _R0;
} else {
resistance = _resistance_up / alpha;
}
// 1/T = 1/T0 + 1/B * ln(R/R0)
temperature = fs_log(resistance / _R0);
temperature = (1.0 / _T0) + (temperature / _beta);
temperature = 1.0 / temperature - 273.15;
}
return temperature;
}
protected:
unsigned long _beta = NTC_BETA;
unsigned long _resistance_up = NTC_R_UP;
unsigned long _resistance_down = NTC_R_DOWN;
unsigned long _R0 = NTC_R0;
double _T0 = NTC_T0;
};
#endif // SENSOR_SUPPORT && NTC_SUPPORT

+ 12
- 6
code/espurna/sensors/PZEM004TSensor.h View File

@ -84,7 +84,11 @@ class PZEM004TSensor : public BaseSensor {
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
if (_serial) {
snprintf(buffer, sizeof(buffer), "PZEM004T @ HwSerial");
} else {
snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
}
return String(buffer);
}
@ -109,11 +113,13 @@ class PZEM004TSensor : public BaseSensor {
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _pzem->current(_ip);
if (index == 1) return _pzem->voltage(_ip);
if (index == 2) return _pzem->power(_ip);
if (index == 3) return _pzem->energy(_ip);
return 0;
double response = 0;
if (index == 0) response = _pzem->current(_ip);
if (index == 1) response = _pzem->voltage(_ip);
if (index == 2) response = _pzem->power(_ip);
if (index == 3) response = _pzem->energy(_ip) * 3600;
if (response < 0) response = 0;
return response;
}
protected:


+ 23
- 9
code/espurna/settings.ino View File

@ -31,17 +31,19 @@ bool _settings_save = false;
unsigned long settingsSize() {
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROMr.read(pos)) {
if (0xFF == len) break;
pos = pos - len - 2;
}
return SPI_FLASH_SEC_SIZE - pos;
return SPI_FLASH_SEC_SIZE - pos + EEPROM_DATA_END;
}
// -----------------------------------------------------------------------------
unsigned int _settingsKeyCount() {
unsigned int settingsKeyCount() {
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROMr.read(pos)) {
if (0xFF == len) break;
pos = pos - len - 2;
len = EEPROMr.read(pos);
pos = pos - len - 2;
@ -50,13 +52,14 @@ unsigned int _settingsKeyCount() {
return count;
}
String _settingsKeyName(unsigned int index) {
String settingsKeyName(unsigned int index) {
String s;
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROMr.read(pos)) {
if (0xFF == len) break;
pos = pos - len - 2;
if (count == index) {
s.reserve(len);
@ -80,11 +83,11 @@ std::vector<String> _settingsKeys() {
std::vector<String> keys;
//unsigned int size = settingsKeyCount();
unsigned int size = _settingsKeyCount();
unsigned int size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
//String key = settingsKeyName(i);
String key = _settingsKeyName(i);
String key = settingsKeyName(i);
bool inserted = false;
for (unsigned char j=0; j<keys.size(); j++) {
@ -277,7 +280,7 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
@ -375,19 +378,30 @@ size_t settingsMaxSize() {
bool settingsRestoreJson(JsonObject& data) {
// Check this is an ESPurna configuration file (must have "app":"ESPURNA")
const char* app = data["app"];
if (strcmp(app, APP_NAME) != 0) return false;
if (!app || strcmp(app, APP_NAME) != 0) {
DEBUG_MSG_P(PSTR("[SETTING] Wrong or missing 'app' key\n"));
return false;
}
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROMr.write(i, 0xFF);
// Clear settings
bool is_backup = data["backup"];
if (is_backup) {
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROMr.write(i, 0xFF);
}
}
// Dump settings to memory buffer
for (auto element : data) {
if (strcmp(element.key, "app") == 0) continue;
if (strcmp(element.key, "version") == 0) continue;
if (strcmp(element.key, "backup") == 0) continue;
setSetting(element.key, element.value.as<char*>());
}
// Persist to EEPROM
saveSettings();
DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n"));


+ 3044
- 0
code/espurna/static/index.all.html.gz.h
File diff suppressed because it is too large
View File


+ 0
- 3253
code/espurna/static/index.html.gz.h
File diff suppressed because it is too large
View File


+ 2960
- 0
code/espurna/static/index.light.html.gz.h
File diff suppressed because it is too large
View File


+ 2559
- 0
code/espurna/static/index.rfbridge.html.gz.h
File diff suppressed because it is too large
View File


+ 4030
- 0
code/espurna/static/index.rfm69.html.gz.h
File diff suppressed because it is too large
View File


+ 2598
- 0
code/espurna/static/index.sensor.html.gz.h
File diff suppressed because it is too large
View File


+ 2515
- 0
code/espurna/static/index.small.html.gz.h
File diff suppressed because it is too large
View File


+ 1
- 0
code/espurna/utils.ino View File

@ -316,6 +316,7 @@ void info() {
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("[INIT] SENSORS: %s\n"), getEspurnaSensors().c_str());
#endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("[INIT] WEBUI IMAGE CODE: %u\n"), WEBUI_IMAGE);
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------


+ 55
- 15
code/espurna/web.ino View File

@ -16,7 +16,21 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
#if WEB_EMBEDDED
#include "static/index.html.gz.h"
#if WEBUI_IMAGE == WEBUI_IMAGE_SMALL
#include "static/index.small.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_LIGHT
#include "static/index.light.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_SENSOR
#include "static/index.sensor.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_RFBRIDGE
#include "static/index.rfbridge.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_RFM69
#include "static/index.rfm69.html.gz.h"
#elif WEBUI_IMAGE == WEBUI_IMAGE_FULL
#include "static/index.all.html.gz.h"
#endif
#endif // WEB_EMBEDDED
#if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED
@ -40,12 +54,9 @@ void _onReset(AsyncWebServerRequest *request) {
request->send(200);
}
void _onGetConfig(AsyncWebServerRequest *request) {
void _onDiscover(AsyncWebServerRequest *request) {
webLog(request);
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
AsyncResponseStream *response = request->beginResponseStream("text/json");
@ -53,9 +64,22 @@ void _onGetConfig(AsyncWebServerRequest *request) {
JsonObject &root = jsonBuffer.createObject();
root["app"] = APP_NAME;
root["version"] = APP_VERSION;
settingsGetJson(root);
root.prettyPrintTo(*response);
jsonBuffer.clear();
root["hostname"] = getSetting("hostname");
root["device"] = getBoardName();
root.printTo(*response);
request->send(response);
}
void _onGetConfig(AsyncWebServerRequest *request) {
webLog(request);
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
AsyncResponseStream *response = request->beginResponseStream("text/json");
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str());
@ -63,6 +87,21 @@ void _onGetConfig(AsyncWebServerRequest *request) {
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
response->printf("{\n\"app\": \"%s\"", APP_NAME);
response->printf(",\n\"version\": \"%s\"", APP_VERSION);
response->printf(",\n\"backup\": \"1\"");
response->printf(",\n\"timestamp\": \"%s\"", ntpDateTime().c_str());
// Write the keys line by line (not sorted)
unsigned long count = settingsKeyCount();
for (unsigned int i=0; i<count; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
response->printf(",\n\"%s\": \"%s\"", key.c_str(), value.c_str());
}
response->printf("\n}");
request->send(response);
}
@ -139,12 +178,12 @@ void _onHome(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [max](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
// Get the chunk based on the index and maxLen
size_t len = index_html_gz_len - index;
size_t len = webui_image_len - index;
if (len > maxLen) len = maxLen;
if (len > max) len = max;
if (len > 0) memcpy_P(buffer, index_html_gz + index, len);
if (len > 0) memcpy_P(buffer, webui_image + index, len);
DEBUG_MSG_P(PSTR("[WEB] Sending %d%%%% (max chunk size: %4d)\r"), int(100 * index / index_html_gz_len), max);
DEBUG_MSG_P(PSTR("[WEB] Sending %d%%%% (max chunk size: %4d)\r"), int(100 * index / webui_image_len), max);
if (len == 0) DEBUG_MSG_P(PSTR("\n"));
// Return the actual length of the chunk (0 for end of file)
@ -154,15 +193,15 @@ void _onHome(AsyncWebServerRequest *request) {
#else
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html_gz, index_html_gz_len);
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", webui_image, webui_image_len);
#endif
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Last-Modified", _last_modified);
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
request->send(response);
}
@ -338,6 +377,7 @@ void webSetup() {
_server->on("/config", HTTP_GET, _onGetConfig);
_server->on("/config", HTTP_POST | HTTP_PUT, _onPostConfig, _onPostConfigData);
_server->on("/upgrade", HTTP_POST, _onUpgrade, _onUpgradeData);
_server->on("/discover", HTTP_GET, _onDiscover);
// Serve static files
#if SPIFFS_SUPPORT


+ 10
- 0
code/espurna/ws.ino View File

@ -489,12 +489,22 @@ void wsReload() {
}
void wsSetup() {
_ws.onEvent(_wsEvent);
webServer()->addHandler(&_ws);
// CORS
#ifdef WEB_REMOTE_DOMAIN
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", WEB_REMOTE_DOMAIN);
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
#endif
webServer()->on("/auth", HTTP_GET, _onAuth);
#if MQTT_SUPPORT
mqttRegister(_wsMQTTCallback);
#endif
wsOnSendRegister(_wsOnStart);
wsOnReceiveRegister(_wsOnReceive);
espurnaRegisterLoop(_wsLoop);


+ 32
- 9
code/extra_scripts.py View File

@ -1,7 +1,14 @@
#!/usr/bin/env python
from subprocess import call
from __future__ import print_function
import os
import time
import sys
from subprocess import call
import click
from platformio import util
import distutils.spawn
Import("env")
@ -30,6 +37,17 @@ class Color(object):
def clr(color, text):
return color + str(text) + '\x1b[0m'
def print_warning(message, color=Color.LIGHT_YELLOW):
print(clr(color, message), file=sys.stderr)
def print_filler(fill, color=Color.WHITE, err=False):
width, _ = click.get_terminal_size()
if len(fill) > 1:
fill = fill[0]
out = sys.stderr if err else sys.stdout
print(clr(color, fill * width), file=out)
# ------------------------------------------------------------------------------
# Callbacks
# ------------------------------------------------------------------------------
@ -51,12 +69,18 @@ def cpp_check(source, target, env):
print("Finished cppcheck...\n")
def check_size(source, target, env):
time.sleep(2)
size = target[0].get_size()
print clr(Color.LIGHT_BLUE, "Binary size: %s bytes" % size)
#if size > 512000:
# print clr(Color.LIGHT_RED, "File too large for OTA!")
# Exit(1)
(binary,) = target
path = binary.get_abspath()
size = os.stat(path).st_size
print(clr(Color.LIGHT_BLUE, "Binary size: {} bytes".format(size)))
# Warn 1MB variants about exceeding OTA size limit
flash_size = int(env.BoardConfig().get("upload.maximum_size", 0))
if (flash_size == 1048576) and (size >= 512000):
print_filler("*", color=Color.LIGHT_YELLOW, err=True)
print_warning("File is too large for OTA! Here you can find instructions on how to flash it:")
print_warning("https://github.com/xoseperez/espurna/wiki/TwoStepUpdates", color=Color.LIGHT_CYAN)
print_filler("*", color=Color.LIGHT_YELLOW, err=True)
# ------------------------------------------------------------------------------
# Hooks
@ -64,5 +88,4 @@ def check_size(source, target, env):
remove_float_support()
#env.AddPreAction("buildprog", cpp_check)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", check_size)

+ 124
- 45
code/gulpfile.js View File

@ -23,7 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/*eslint-env es6*/
// -----------------------------------------------------------------------------
// File system builder
// Dependencies
// -----------------------------------------------------------------------------
const fs = require('fs');
@ -35,49 +35,75 @@ const inline = require('gulp-inline');
const inlineImages = require('gulp-css-base64');
const favicon = require('gulp-base64-favicon');
const htmllint = require('gulp-htmllint');
const log = require('fancy-log');
const csslint = require('gulp-csslint');
const crass = require('gulp-crass');
const replace = require('gulp-replace');
const remover = require('gulp-remove-code');
const map = require('map-stream');
const rename = require('gulp-rename');
const runSequence = require('run-sequence');
// -----------------------------------------------------------------------------
// Configuration
// -----------------------------------------------------------------------------
const dataFolder = 'espurna/data/';
const staticFolder = 'espurna/static/';
var toHeader = function(filename) {
// -----------------------------------------------------------------------------
// Methods
// -----------------------------------------------------------------------------
var source = dataFolder + filename;
var destination = staticFolder + filename + '.h';
var safename = filename.split('.').join('_');
var buildHeaderFile = function() {
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
log.error(err);
});
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
var data = fs.readFileSync(source);
return map(function(file, cb) {
wstream.write('#define ' + safename + '_len ' + data.length + '\n');
wstream.write('const uint8_t ' + safename + '[] PROGMEM = {');
var parts = file.path.split("/");
var filename = parts[parts.length - 1];
var destination = staticFolder + filename + ".h";
var safename = "webui_image";
for (var i=0; i<data.length; i++) {
if (0 === (i % 20)) {
wstream.write('\n');
}
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < (data.length - 1)) {
wstream.write(',');
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.error(err);
});
var data = fs.readFileSync(file.path);
wstream.write('#define ' + safename + '_len ' + data.length + '\n');
wstream.write('const uint8_t ' + safename + '[] PROGMEM = {');
for (var i=0; i<data.length; i++) {
if (0 === (i % 20)) {
wstream.write('\n');
}
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < (data.length - 1)) {
wstream.write(',');
}
}
}
wstream.write('\n};');
wstream.end();
wstream.write('\n};');
wstream.end();
};
var fstat = fs.statSync(file.path);
console.log("Created '" + filename + "' size: " + fstat.size + " bytes");
cb(0, destination);
});
}
var htmllintReporter = function(filepath, issues) {
if (issues.length > 0) {
issues.forEach(function (issue) {
log.info(
if (issues.length > 0) {
issues.forEach(function (issue) {
console.info(
'[gulp-htmllint] ' +
filepath + ' [' +
issue.line + ',' +
@ -85,27 +111,23 @@ var htmllintReporter = function(filepath, issues) {
'(' + issue.code + ') ' +
issue.msg
);
});
process.exitCode = 1;
}
});
process.exitCode = 1;
}
};
gulp.task('build_certs', function() {
toHeader('server.cer');
toHeader('server.key');
});
var buildWebUI = function(module) {
gulp.task('csslint', function() {
gulp.src('html/*.css').
pipe(csslint({ids: false})).
pipe(csslint.formatter());
});
gulp.task('buildfs_embeded', ['buildfs_inline'], function() {
toHeader('index.html.gz');
});
var modules = {"light": false, "sensor": false, "rfbridge": false, "rfm69": false};
if ("all" == module) {
modules["light"] = true;
modules["sensor"] = true;
modules["rfbridge"] = false; // we will never be adding this except when building RFBRIDGE
modules["rfm69"] = false; // we will never be adding this except when building RFM69GW
} else if ("small" != module) {
modules[module] = true;
}
gulp.task('buildfs_inline', function() {
return gulp.src('html/*.html').
pipe(htmllint({
'failOnError': true,
@ -117,10 +139,11 @@ gulp.task('buildfs_inline', function() {
pipe(favicon()).
pipe(inline({
base: 'html/',
js: [uglify],
js: [],
css: [crass, inlineImages],
disabledTypes: ['svg', 'img']
})).
pipe(remover(modules)).
pipe(htmlmin({
collapseWhitespace: true,
removeComments: true,
@ -129,8 +152,64 @@ gulp.task('buildfs_inline', function() {
})).
pipe(replace('pure-', 'p-')).
pipe(gzip()).
pipe(rename("index." + module + ".html.gz")).
pipe(gulp.dest(dataFolder));
};
// -----------------------------------------------------------------------------
// Tasks
// -----------------------------------------------------------------------------
gulp.task('build_certs', function() {
gulp.src(dataFolder + 'server.*').
pipe(buildHeaderFile());
});
gulp.task('csslint', function() {
gulp.src('html/*.css').
pipe(csslint({ids: false})).
pipe(csslint.formatter());
});
gulp.task('build_webui_small', function() {
return buildWebUI("small");
})
gulp.task('build_webui_sensor', function() {
return buildWebUI("sensor");
})
gulp.task('build_webui_light', function() {
return buildWebUI("light");
})
gulp.task('build_webui_rfbridge', function() {
return buildWebUI("rfbridge");
})
gulp.task('build_webui_rfm69', function() {
return buildWebUI("rfm69");
})
gulp.task('build_webui_all', function() {
return buildWebUI("all");
})
gulp.task('buildfs_inline', function(cb) {
runSequence([
'build_webui_small',
'build_webui_sensor',
'build_webui_light',
'build_webui_rfbridge',
'build_webui_rfm69',
'build_webui_all'
], cb);
});
gulp.task('buildfs_embeded', ['buildfs_inline'], function() {
gulp.src(dataFolder + 'index.*').
pipe(buildHeaderFile());
});
gulp.task('default', ['buildfs_embeded']);

+ 89
- 2
code/html/custom.css View File

@ -147,8 +147,8 @@ div.state {
color: white;
letter-spacing: 0;
margin-bottom: 10px;
padding: 8px 8px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
padding: 8px 8px;
}
.main-buttons {
@ -164,9 +164,13 @@ div.state {
.button-ha-del,
.button-rfb-forget,
.button-del-network,
.button-del-mapping,
.button-del-schedule,
.button-dbg-clear,
.button-upgrade,
.button-clear-filters,
.button-clear-messages,
.button-clear-counts,
.button-settings-factory {
background: rgb(192, 0, 0); /* redish */
}
@ -174,8 +178,9 @@ div.state {
.button-update,
.button-update-password,
.button-add-network,
.button-rfb-learn,
.button-add-mapping,
.button-upgrade-browse,
.button-rfb-learn,
.button-ha-add,
.button-ha-config,
.button-settings-backup,
@ -199,6 +204,9 @@ div.state {
}
.button-upgrade-browse,
.button-clear-filters,
.button-clear-messages,
.button-clear-counts,
.button-dbgcmd,
.button-ha-add,
.button-apikey,
@ -221,6 +229,65 @@ span.slider {
margin-top: 7px;
}
/* -----------------------------------------------------------------------------
Checkboxes
-------------------------------------------------------------------------- */
.checkbox-container {
width: 130px;
height: 30px;
margin: 3px 0 10px 0px;
position: relative;
border-radius: 4px;
overflow: hidden;
user-select: none;
cursor: pointer;
}
.checkbox-container input {
display: none;
}
.inner-container {
position: absolute;
left: 0;
top: 0;
width: inherit;
height: inherit;
text-transform: uppercase;
font-size: .7em;
letter-spacing: .2em;
}
.inner-container:first-child {
background: #e9e9e9;
color: #a9a9a9;
}
.inner-container:nth-child(2) {
background: #c00000;
color: white;
-webkit-clip-path: inset(0 50% 0 0);
clip-path: inset(0 50% 0 0);
transition: .3s cubic-bezier(0,0,0,1);
}
.toggle {
width: 50%;
position: absolute;
height: inherit;
display: flex;
box-sizing: border-box;
}
.toggle p {
margin: auto;
}
.toggle:nth-child(1) {
right: 0;
}
/* -----------------------------------------------------------------------------
Loading
-------------------------------------------------------------------------- */
@ -311,6 +378,26 @@ span.slider {
padding: 10px;
}
/* -----------------------------------------------------------------------------
Table
-------------------------------------------------------------------------- */
.right {
text-align: right;
}
table.dataTable.display tbody td {
text-align: center;
}
#packets_filter {
display: none;
}
.filtered {
color: rgb(202, 60, 60);
}
/* -----------------------------------------------------------------------------
Logs
-------------------------------------------------------------------------- */


+ 260
- 42
code/html/custom.js View File

@ -18,6 +18,11 @@ var useCCT = false;
var now = 0;
var ago = 0;
<!-- removeIf(!rfm69)-->
var packets;
var filters = [];
<!-- endRemoveIf(!rfm69)-->
// -----------------------------------------------------------------------------
// Messages
// -----------------------------------------------------------------------------
@ -34,6 +39,7 @@ function initMessages() {
messages[10] = "Session expired, please reload page...";
}
<!-- removeIf(!sensor)-->
function sensorName(id) {
var names = [
"DHT", "Dallas", "Emon Analog", "Emon ADC121", "Emon ADS1X15",
@ -73,6 +79,7 @@ function magnitudeError(error) {
}
return "Error " + error;
}
<!-- endRemoveIf(!sensor)-->
// -----------------------------------------------------------------------------
// Utils
@ -204,7 +211,8 @@ function addValue(data, name, value) {
"dczRelayIdx", "dczMagnitude",
"tspkRelay", "tspkMagnitude",
"ledMode",
"adminPass"
"adminPass",
"node", "key", "topic"
];
if (name in data) {
@ -307,11 +315,17 @@ function checkFirmware(file, callback) {
reader.onloadend = function(evt) {
if (FileReader.DONE === evt.target.readyState) {
callback(0xE9 === evt.target.result.charCodeAt(0));
if (0xE9 !== evt.target.result.charCodeAt(0)) callback(false);
if (0x03 !== evt.target.result.charCodeAt(2)) {
var response = window.confirm("Binary image is not using DOUT flash mode. This might cause resets in some devices. Press OK to continue.");
callback(response);
} else {
callback(true);
}
}
};
var blob = file.slice(0, 1);
var blob = file.slice(0, 3);
reader.readAsBinaryString(blob);
}
@ -452,12 +466,8 @@ function doUpdate() {
// Empty special fields
$(".pwrExpected").val(0);
$("input[name='pwrResetCalibration']").
prop("checked", false).
iphoneStyle("refresh");
$("input[name='pwrResetE']").
prop("checked", false).
iphoneStyle("refresh");
$("input[name='pwrResetCalibration']").prop("checked", false);
$("input[name='pwrResetE']").prop("checked", false);
// Change handling
numChanged = 0;
@ -534,13 +544,12 @@ function doFactoryReset() {
if (response === false) {
return false;
}
websock.send(JSON.stringify({"action": "factory_reset"}));
sendAction("factory_reset", {});
doReload(5000);
return false;
}
function doToggle(element, value) {
var id = parseInt(element.attr("data"), 10);
function doToggle(id, value) {
sendAction("relay", {id: id, status: value ? 1 : 0 });
return false;
}
@ -571,6 +580,56 @@ function doDebugClear() {
return false;
}
<!-- removeIf(!rfm69)-->
function doClearCounts() {
sendAction("clear-counts", {});
return false;
}
function doClearMessages() {
packets.clear().draw(false);
return false;
}
function doFilter(e) {
var index = packets.cell(this).index();
if (index == 'undefined') return;
var c = index.column;
var column = packets.column(c);
if (filters[c]) {
filters[c] = false;
column.search("");
$(column.header()).removeClass("filtered");
} else {
filters[c] = true;
var data = packets.row(this).data();
if (e.which == 1) {
column.search('^' + data[c] + '$', true, false );
} else {
column.search('^((?!(' + data[c] + ')).)*$', true, false );
}
$(column.header()).addClass("filtered");
}
column.draw();
return false;
}
function doClearFilters() {
for (var i = 0; i < packets.columns()[0].length; i++) {
if (filters[i]) {
filters[i] = false;
var column = packets.column(i);
column.search("");
$(column.header()).removeClass("filtered");
column.draw();
}
}
return false;
}
<!-- endRemoveIf(!rfm69)-->
// -----------------------------------------------------------------------------
// Visualization
// -----------------------------------------------------------------------------
@ -584,10 +643,7 @@ function toggleMenu() {
function showPanel() {
$(".panel").hide();
if ($("#layout").hasClass("active")) { toggleMenu(); }
$("#" + $(this).attr("data")).show().
find("input[type='checkbox']").
iphoneStyle("calculateDimensions").
iphoneStyle("refresh");
$("#" + $(this).attr("data")).show();
}
// -----------------------------------------------------------------------------
@ -609,6 +665,7 @@ function createRelayList(data, container, template_name) {
}
<!-- removeIf(!sensor)-->
function createMagnitudeList(data, container, template_name) {
var current = $("#" + container + " > div").length;
@ -625,6 +682,29 @@ function createMagnitudeList(data, container, template_name) {
}
}
<!-- endRemoveIf(!sensor)-->
// -----------------------------------------------------------------------------
// RFM69
// -----------------------------------------------------------------------------
<!-- removeIf(!rfm69)-->
function addMapping() {
var template = $("#nodeTemplate .pure-g")[0];
var line = $(template).clone();
var tabindex = $("#mapping > div").length * 3 + 50;
$(line).find("input").each(function() {
$(this).attr("tabindex", tabindex++);
});
$(line).find("button").on('click', delMapping);
line.appendTo("#mapping");
}
function delMapping() {
var parent = $(this).parent().parent();
$(parent).remove();
}
<!-- endRemoveIf(!rfm69)-->
// -----------------------------------------------------------------------------
// Wifi
@ -666,6 +746,7 @@ function addNetwork() {
// -----------------------------------------------------------------------------
// Relays scheduler
// -----------------------------------------------------------------------------
function delSchedule() {
var parent = $(this).parents(".pure-g");
$(parent).remove();
@ -677,6 +758,7 @@ function moreSchedule() {
}
function addSchedule(event) {
var numSchedules = $("#schedules > div").length;
if (numSchedules >= maxSchedules) {
alert("Max number of schedules reached");
@ -699,13 +781,12 @@ function addSchedule(event) {
$(line).find(".button-del-schedule").on("click", delSchedule);
$(line).find(".button-more-schedule").on("click", moreSchedule);
line.appendTo("#schedules");
$(line).find("input[type='checkbox']").prop("checked", false);
$(line).find("input[type='checkbox']").
prop("checked", false).
iphoneStyle("calculateDimensions").
iphoneStyle("refresh");
initCheckboxes();
return line;
}
// -----------------------------------------------------------------------------
@ -723,15 +804,8 @@ function initRelays(data) {
// Add relay fields
var line = $(template).clone();
$(".id", line).html(i);
$("input", line).attr("data", i);
$(":checkbox", line).prop('checked', data[i]).attr("data", i);
line.appendTo("#relays");
$("input[type='checkbox']", line).iphoneStyle({
onChange: doToggle,
resizeContainer: true,
resizeHandle: true,
checkedLabel: "ON",
uncheckedLabel: "OFF"
});
// Populate the relay SELECTs
$("select.isrelay").append(
@ -739,6 +813,61 @@ function initRelays(data) {
}
}
function initCheckboxes() {
var setCheckbox = function(element, value) {
var container = $(".toggle-container", $(element));
if (value) {
container.css("-webkit-clip-path", "inset(0 0 0 50%)");
container.css("clip-path", "inset(0 0 0 50%)");
container.css("backgroundColor", "#00c000");
} else {
container.css("-webkit-clip-path", "inset(0 50% 0 0)");
container.css("clip-path", "inset(0 50% 0 0)");
container.css("backgroundColor", "#c00000");
}
}
$(".checkbox-container")
.each(function() {
var status = $(this).next().prop('checked');
setCheckbox(this, status);
})
.off('click')
.on('click', function() {
var checkbox = $(this).next();
var status = checkbox.prop('checked');
status = !status;
checkbox.prop('checked', status);
setCheckbox(this, status);
if ("relay" == checkbox.attr('name')) {
var id = parseInt(checkbox.attr('data'), 10);
doToggle(id, status);
}
});
}
function createCheckboxes() {
$("input[type='checkbox']").each(function() {
var text_on = $(this).attr("on") || "YES";
var text_off = $(this).attr("off") || "NO";
var toggles = "<div class=\"toggle\"><p>" + text_on + "</p></div><div class=\"toggle\"><p>" + text_off + "</p></div>";
var content = "<div class=\"checkbox-container\"><div class=\"inner-container\">" + toggles
+ "</div><div class=\"inner-container toggle-container\">" + toggles + "</div></div>";
$(this).before(content).hide();
});
}
@ -768,6 +897,7 @@ function initRelayConfig(data) {
// Sensors & Magnitudes
// -----------------------------------------------------------------------------
<!-- removeIf(!sensor)-->
function initMagnitudes(data) {
// check if already initialized
@ -786,11 +916,14 @@ function initMagnitudes(data) {
}
}
<!-- endRemoveIf(!sensor)-->
// -----------------------------------------------------------------------------
// Lights
// -----------------------------------------------------------------------------
<!-- removeIf(!light)-->
function initColorRGB() {
// check if already initialized
@ -909,11 +1042,14 @@ function initChannels(num) {
}
}
<!-- endRemoveIf(!light)-->
// -----------------------------------------------------------------------------
// RFBridge
// -----------------------------------------------------------------------------
<!-- removeIf(!rfbridge)-->
function rfbLearn() {
var parent = $(this).parents(".pure-g");
var input = $("input", parent);
@ -952,6 +1088,7 @@ function addRfbNode() {
return line;
}
<!-- endRemoveIf(!rfbridge)-->
// -----------------------------------------------------------------------------
// Processing
@ -1000,6 +1137,8 @@ function processData(data) {
// RFBridge
// ---------------------------------------------------------------------
<!-- removeIf(!rfbridge)-->
if ("rfbCount" === key) {
for (i=0; i<data.rfbCount; i++) { addRfbNode(); }
return;
@ -1017,11 +1156,58 @@ function processData(data) {
}
return;
}
<!-- endRemoveIf(!rfbridge)-->
// ---------------------------------------------------------------------
// RFM69
// ---------------------------------------------------------------------
<!-- removeIf(!rfm69)-->
if (key == "packet") {
var packet = data.packet;
var d = new Date();
packets.row.add([
d.toLocaleTimeString('en-US', { hour12: false }),
packet.senderID,
packet.packetID,
packet.targetID,
packet.key,
packet.value,
packet.rssi,
packet.duplicates,
packet.missing,
]).draw(false);
return;
}
if (key == "mapping") {
for (var i in data.mapping) {
// add a new row
addMapping();
// get group
var line = $("#mapping .pure-g")[i];
// fill in the blanks
var mapping = data.mapping[i];
Object.keys(mapping).forEach(function(key) {
var id = "input[name=" + key + "]";
if ($(id, line).length) $(id, line).val(mapping[key]).attr("original", mapping[key]);
});
}
return;
}
<!-- endRemoveIf(!rfm69)-->
// ---------------------------------------------------------------------
// Lights
// ---------------------------------------------------------------------
<!-- removeIf(!light)-->
if ("rgb" === key) {
initColorRGB();
$("input[name='color']").wheelColorPicker("setValue", value, true);
@ -1072,10 +1258,14 @@ function processData(data) {
useCCT = value;
}
<!-- endRemoveIf(!light)-->
// ---------------------------------------------------------------------
// Sensors & Magnitudes
// ---------------------------------------------------------------------
<!-- removeIf(!sensor)-->
if ("magnitudes" === key) {
initMagnitudes(value);
for (i in value) {
@ -1091,6 +1281,8 @@ function processData(data) {
return;
}
<!-- endRemoveIf(!sensor)-->
// ---------------------------------------------------------------------
// WiFi
// ---------------------------------------------------------------------
@ -1142,9 +1334,7 @@ function processData(data) {
var sch_value = schedule[key];
$("input[name='" + key + "']", sch_line).val(sch_value);
$("select[name='" + key + "']", sch_line).prop("value", sch_value);
$("input[type='checkbox'][name='" + key + "']", sch_line).
prop("checked", sch_value).
iphoneStyle("refresh");
$("input[type='checkbox'][name='" + key + "']", sch_line).prop("checked", sch_value);
});
}
return;
@ -1157,12 +1347,7 @@ function processData(data) {
if ("relayStatus" === key) {
initRelays(value);
for (i in value) {
// Set the status for each relay
$("input.relayStatus[data='" + i + "']").
prop("checked", value[i]).
iphoneStyle("refresh");
$("input[name='relay'][data='" + i + "']").prop("checked", value[i]);
}
return;
}
@ -1184,10 +1369,12 @@ function processData(data) {
}
// Domoticz - Magnitudes
<!-- removeIf(!sensor)-->
if ("dczMagnitudes" === key) {
createMagnitudeList(value, "dczMagnitudes", "dczMagnitudeTemplate");
return;
}
<!-- endRemoveIf(!sensor)-->
// ---------------------------------------------------------------------
// Thingspeak
@ -1200,10 +1387,12 @@ function processData(data) {
}
// Thingspeak - Magnitudes
<!-- removeIf(!sensor)-->
if ("tspkMagnitudes" === key) {
createMagnitudeList(value, "tspkMagnitudes", "tspkMagnitudeTemplate");
return;
}
<!-- endRemoveIf(!sensor)-->
// ---------------------------------------------------------------------
// General
@ -1232,7 +1421,7 @@ function processData(data) {
if ("deviceip" === key) {
var a_href = $("span[name='" + key + "']").parent();
a_href.attr("href", "http://" + value);
a_href.attr("href", "//" + value);
a_href.next().attr("href", "telnet://" + value);
}
@ -1273,9 +1462,7 @@ function processData(data) {
var input = $("input[name='" + key + "']");
if (input.length > 0) {
if (input.attr("type") === "checkbox") {
input.
prop("checked", value).
iphoneStyle("refresh");
input.prop("checked", value);
} else if (input.attr("type") === "radio") {
input.val([value]);
} else {
@ -1307,6 +1494,7 @@ function processData(data) {
}
resetOriginals();
initCheckboxes();
}
@ -1374,6 +1562,7 @@ function connectToURL(url) {
$.ajax({
'method': 'GET',
'crossDomain': true,
'url': urls.auth.href,
'xhrFields': { 'withCredentials': true }
}).done(function(data) {
@ -1402,10 +1591,16 @@ function connectToCurrentURL() {
connectToURL(new URL(window.location));
}
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
$(function() {
initMessages();
loadTimeZones();
createCheckboxes();
setInterval(function() { keepTime(); }, 1000);
$("#menuLink").on("click", toggleMenu);
@ -1440,13 +1635,36 @@ $(function() {
$(".more", addNetwork()).toggle();
});
$(".button-add-switch-schedule").on("click", { schType: 1 }, addSchedule);
<!-- removeIf(!light)-->
$(".button-add-light-schedule").on("click", { schType: 2 }, addSchedule);
<!-- endRemoveIf(!light)-->
<!-- removeIf(!rfm69)-->
$(".button-add-mapping").on('click', addMapping);
$(".button-del-mapping").on('click', delMapping);
$(".button-clear-counts").on('click', doClearCounts);
$(".button-clear-messages").on('click', doClearMessages);
$(".button-clear-filters").on('click', doClearFilters);
$('#packets tbody').on('mousedown', 'td', doFilter);
packets = $('#packets').DataTable({
"paging": false
});
for (var i = 0; i < packets.columns()[0].length; i++) {
filters[i] = false;
}
<!-- endRemoveIf(!rfm69)-->
$(document).on("change", "input", hasChanged);
$(document).on("change", "select", hasChanged);
// don't autoconnect when opening from filesystem
if (window.location.protocol === "file:") { return; }
connectToCurrentURL();
// Check host param in query string
if (host = getParameterByName('host')) {
connect(host);
} else {
connectToCurrentURL();
}
});

+ 157
- 14
code/html/index.html View File

@ -11,8 +11,12 @@
<link rel="stylesheet" href="vendor/pure-1.0.0.min.css" />
<link rel="stylesheet" href="vendor/pure-grids-responsive-1.0.0.min.css" />
<link rel="stylesheet" href="vendor/side-menu.css" />
<link rel="stylesheet" href="vendor/checkboxes.css" />
<!-- removeIf(!light) -->
<link rel="stylesheet" href="vendor/jquery.wheelcolorpicker-3.0.3.css" />
<!-- endRemoveIf(!light) -->
<!-- removeIf(!rfm69) -->
<link rel="stylesheet" href="vendor/datatables-1.10.16.css" />
<!-- endRemoveIf(!rfm69) -->
<link rel="stylesheet" href="custom.css" />
<!-- endbuild -->
@ -98,9 +102,21 @@
<a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
</li>
<!-- removeIf(!light) -->
<li class="pure-menu-item module module-color">
<a href="#" class="pure-menu-link" data="panel-color">LIGHTS</a>
</li>
<!-- endRemoveIf(!light) -->
<!-- removeIf(!rfm69) -->
<li class="pure-menu-item module module-rfm69">
<a href="#" class="pure-menu-link" data="panel-mapping">MAPPING</a>
</li>
<li class="pure-menu-item module module-rfm69">
<a href="#" class="pure-menu-link" data="panel-messages">MESSAGES</a>
</li>
<!-- endRemoveIf(!rfm69) -->
<li class="pure-menu-item module module-mqtt">
<a href="#" class="pure-menu-link" data="panel-mqtt">MQTT</a>
@ -110,17 +126,21 @@
<a href="#" class="pure-menu-link" data="panel-ntp">NTP</a>
</li>
<!-- removeIf(!rfbridge) -->
<li class="pure-menu-item module module-rfb">
<a href="#" class="pure-menu-link" data="panel-rfb">RF</a>
</li>
<!-- endRemoveIf(!rfbridge) -->
<li class="pure-menu-item module module-sch">
<a href="#" class="pure-menu-link" data="panel-schedule">SCHEDULE</a>
</li>
<!-- removeIf(!sensor) -->
<li class="pure-menu-item module module-sensors">
<a href="#" class="pure-menu-link" data="panel-sensors">SENSORS</a>
</li>
<!-- endRemoveIf(!sensor) -->
<li class="pure-menu-item module module-relay">
<a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a>
@ -177,13 +197,27 @@
<div id="relays"></div>
<!-- removeIf(!light) -->
<div id="colors"></div>
<div id="cct"></div>
<div id="channels"></div>
<!-- endRemoveIf(!light) -->
<!-- removeIf(!sensor) -->
<div id="magnitudes"></div>
<!-- endRemoveIf(!sensor) -->
<!-- removeIf(!rfm69) -->
<div class="pure-g module module-rfm69">
<label class="pure-u-1 pure-u-lg-1-4">Packet count</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1" type="text" name="packetCount" readonly /></div>
</div>
<div class="pure-g module module-rfm69">
<label class="pure-u-1 pure-u-lg-1-4">Node count</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1" type="text" name="nodeCount" readonly /></div>
</div>
<!-- endRemoveIf(!rfm69) -->
<div class="pure-u-1 pure-u-lg-1-2 state">
@ -211,11 +245,6 @@
<div class="pure-u-1-2">Firmware version</div>
<div class="pure-u-11-24"><span class="right" name="app_version"></span></div>
<!--
<div class="pure-u-1-2">Firmware revision</div>
<div class="pure-u-11-24"><span class="right" name="app_revision"></span></div>
-->
<div class="pure-u-1-2">Firmware build date</div>
<div class="pure-u-11-24"><span class="right" name="app_build"></span></div>
@ -343,7 +372,7 @@
<div class="pure-g module module-alexa">
<label class="pure-u-1 pure-u-lg-1-4">Alexa integration</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="alexaEnabled" tabindex="13" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="alexaEnabled" /></div>
</div>
</fieldset>
@ -382,6 +411,7 @@
</div>
</div>
<!-- removeIf(!light) -->
<div class="panel" id="panel-color">
<div class="header">
@ -467,6 +497,7 @@
</fieldset>
</div>
</div>
<!-- endRemoveIf(!light) -->
<div class="panel" id="panel-admin">
@ -638,13 +669,88 @@
<div id="schedules"></div>
<button type="button" class="pure-button button-add-switch-schedule module module-relay">Add switch schedule</button>
<!-- removeIf(!light) -->
<button type="button" class="pure-button button-add-light-schedule module module-color">Add channel schedule</button>
<!-- endRemoveIf(!light) -->
</fieldset>
</div>
</div>
<!-- removeIf(!rfm69) -->
<div class="panel" id="panel-mapping">
<div class="header">
<h1>MAPPING</h1>
<h2>
Configure the map between nodeID/key and MQTT topic. Messages from the given nodeID with the given key will be forwarded to the specified topic.
You can also configure a default topic using {nodeid} and {key} as placeholders, if the default topic is empty messages without defined map will be discarded.
</h2>
</div>
<div class="page">
<fieldset>
<legend>Default topic</legend>
<div class="pure-g">
<input name="rfm69Topic" type="text" class="pure-u-23-24" value="" size="8" tabindex="41" placeholder="Default MQTT Topic (use {nodeid} and {key} as placeholders)">
</div>
<legend>Specific topics</legend>
<div id="mapping"></div>
<button type="button" class="pure-button button-add-mapping">Add</button>
</fieldset>
</div>
</div>
<div class="panel" id="panel-messages">
<div class="header">
<h1>MESSAGES</h1>
<h2>
Messages being received. Previous messages are not displayed.
You have to keep the page open in order to keep receiving them.
You can filter/unfilter by clicking on the values.
Left click on a value to show only rows that match that value, middle click to show all rows but those matching that value.
Filtered colums have red headers.
</h2>
</div>
<div class="page">
<table id="packets" class="display" cellspacing="0">
<thead>
<tr>
<th>Timestamp</th>
<th>SenderID</th>
<th>PacketID</th>
<th>TargetID</th>
<th>Key</th>
<th>Value</th>
<th>RSSI</th>
<th>Duplicates</th>
<th>Missing</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<button class="pure-button button-clear-filters">Clear filters</button>
<button class="pure-button button-clear-messages">Clear messages</button>
<button class="pure-button button-clear-counts">Clear counts</button>
</div>
</div>
<!-- endRemoveIf(!rfm69) -->
<div class="panel" id="panel-mqtt">
@ -735,11 +841,13 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">
This is the root topic for this device. The {hostname} and {mac} placeholders will be replaced by the device hostname and MAC address.<br />
- <strong>&lt;root&gt;/relay/#/set</strong> Send a 0 or a 1 as a payload to this topic to switch it on or off. You can also send a 2 to toggle its current state. Replace # with the switch ID (starting from 0). If the board has only one switch it will be 0.<br />
<!-- removeIf(!light) -->
<span class="module module-color">- <strong>&lt;root&gt;/rgb/set</strong> Set the color using this topic, your can either send an "#RRGGBB" value or "RRR,GGG,BBB" (0-255 each).<br /></span>
<span class="module module-color">- <strong>&lt;root&gt;/hsv/set</strong> Set the color using hue (0-360), saturation (0-100) and value (0-100) values, comma separated.<br /></span>
<span class="module module-color">- <strong>&lt;root&gt;/brightness/set</strong> Set the brighness (0-255).<br /></span>
<span class="module module-color">- <strong>&lt;root&gt;/channel/#/set</strong> Set the value for a single color channel (0-255). Replace # with the channel ID (starting from 0 and up to 4 for RGBWC lights).<br /></span>
<span class="module module-color">- <strong>&lt;root&gt;/mired/set</strong> Set the temperature color in mired.<br /></span>
<!-- endRemoveIf(!light) -->
- <strong>&lt;root&gt;/status</strong> The device will report a 1 to this topic every few minutes. Upon MQTT disconnecting this will be set to 0.<br />
- Other values reported (depending on the build) are: <strong>firmware</strong> and <strong>version</strong>, <strong>hostname</strong>, <strong>IP</strong>, <strong>MAC</strong>, signal strenth (<strong>RSSI</strong>), <strong>uptime</strong> (in seconds), <strong>free heap</strong> and <strong>power supply</strong>.
</div>
@ -844,7 +952,9 @@
<div id="dczRelays"></div>
<!-- removeIf(!sensor) -->
<div id="dczMagnitudes"></div>
<!-- endRemoveIf(!sensor) -->
</fieldset>
</div>
@ -872,7 +982,9 @@
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Home Assistant auto-discovery feature. Enable and save to add the device to your HA console.
<!-- removeIf(!light) -->
When using a colour light you might want to disable CSS style so Home Assistant can parse the color.
<!-- endRemoveIf(!light) -->
</div>
</div>
@ -903,8 +1015,6 @@
</div>
<div class="panel" id="panel-thingspeak">
<div class="header">
@ -938,7 +1048,9 @@
<div id="tspkRelays"></div>
<!-- removeIf(!sensor) -->
<div id="tspkMagnitudes"></div>
<!-- endRemoveIf(!sensor) -->
</fieldset>
</div>
@ -1025,6 +1137,7 @@
</div>
<!-- removeIf(!sensor) -->
<div class="panel" id="panel-sensors">
<div class="header">
@ -1075,7 +1188,7 @@
</select>
</div>
<div class="pure-g module module-hlw module-cse module-emon">
<div class="pure-g module module-hlw module-cse module-emon module-pzem">
<label class="pure-u-1 pure-u-lg-1-4">Energy units</label>
<select name="energyUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Joules (J)</option>
@ -1161,7 +1274,9 @@
</div>
</div>
<!-- endRemoveIf(!sensor) -->
<!-- removeIf(!rfbridge) -->
<div class="panel" id="panel-rfb">
<div class="header">
@ -1182,6 +1297,7 @@
</fieldset>
</div>
</div>
<!-- endRemoveIf(!rfbridge) -->
</form>
@ -1191,6 +1307,7 @@
<!-- Templates -->
<!-- removeIf(!rfbridge) -->
<div id="rfbNodeTemplate" class="template">
<legend>Switch #<span></span></legend>
@ -1212,6 +1329,7 @@
</div>
</div>
<!-- endRemoveIf(!rfbridge) -->
<div id="networkTemplate" class="template">
@ -1299,6 +1417,7 @@
<input type="hidden" name="schType" value="1">
</div>
<!-- removeIf(!light) -->
<div id="lightActionTemplate" class="template">
<label class="pure-u-1 pure-u-lg-1-4">Brightness</label>
<div class="pure-u-1 pure-u-lg-1-5">
@ -1307,11 +1426,12 @@
<select class="pure-u-1 pure-u-lg-1-5 islight" name="schSwitch"></select>
<input type="hidden" name="schType" value="2">
</div>
<!-- endRemoveIf(!light) -->
<div id="relayTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Switch #<span class="id"></span></label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" class="relayStatus pure-u-1 pure-u-lg-1-4" data="0" /></div>
<input name="relay" type="checkbox" on="ON" off="OFF" />
</div>
</div>
@ -1366,6 +1486,7 @@
</div>
</div>
<!-- removeIf(!sensor) -->
<div id="dczMagnitudeTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Magnitude</label>
@ -1373,6 +1494,7 @@
<div class="pure-u-1 pure-u-lg-1-2 hint center"></div>
</div>
</div>
<!-- endRemoveIf(!sensor) -->
<div id="tspkRelayTemplate" class="template">
<div class="pure-g">
@ -1381,6 +1503,7 @@
</div>
</div>
<!-- removeIf(!sensor) -->
<div id="tspkMagnitudeTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Magnitude</label>
@ -1388,7 +1511,9 @@
<div class="pure-u-1 pure-u-lg-1-2 hint center"></div>
</div>
</div>
<!-- endRemoveIf(!sensor) -->
<!-- removeIf(!light) -->
<div id="colorRGBTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Color</label>
@ -1424,7 +1549,9 @@
<span class="slider mireds pure-u-lg-1-4"></span>
</div>
</div>
<!-- endRemoveIf(!light) -->
<!-- removeIf(!sensor) -->
<div id="magnitudeTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4"></label>
@ -1434,6 +1561,18 @@
<div class="pure-u-1 pure-u-lg-1-2 hint center"></div>
</div>
</div>
<!-- endRemoveIf(!sensor) -->
<!-- removeIf(!rfm69) -->
<div id="nodeTemplate" class="template">
<div class="pure-g">
<div class="pure-u-md-1-6 pure-u-1-2"><input name="node" type="text" class="pure-u-11-12" value="" size="8" tabindex="0" placeholder="Node ID"></div>
<div class="pure-u-md-1-6 pure-u-1-2"><input name="key" type="text" class="pure-u-11-12" value="" size="8" tabindex="0" placeholder="Key"></div>
<div class="pure-u-md-1-2 pure-u-3-4"><input name="topic" type="text" class="pure-md-11-12 pure-u-23-24" value="" size="8" tabindex="0" placeholder="MQTT Topic"></div>
<div class="pure-u-md-1-6 pure-u-1-4"><button type="button" class="pure-button button-del-mapping pure-u-5-6 pure-u-md-5-6">Del</button></div>
</div>
</div>
<!-- endRemoveIf(!rfm69) -->
<iframe id="downloader"></iframe>
<input id="uploader" type="file" />
@ -1443,8 +1582,12 @@
<!-- build:js script.js -->
<script src="vendor/jquery-3.2.1.min.js"></script>
<script src="custom.js"></script>
<script src="vendor/checkboxes.js"></script>
<!-- removeIf(!light) -->
<script src="vendor/jquery.wheelcolorpicker-3.0.3.min.js"></script>
<!-- endRemoveIf(!light) -->
<!-- removeIf(!rfm69) -->
<script src="vendor/datatables-1.10.16.min.js"></script>
<!-- endRemoveIf(!rfm69) -->
<!-- endbuild -->
</html>

+ 0
- 120
code/html/vendor/checkboxes.css View File

@ -1,120 +0,0 @@
.iPhoneCheckContainer {
-webkit-transform:translate3d(0,0,0);
position: relative;
height: 30px;
cursor: pointer;
overflow: hidden;
margin: 5px 0 10px 0;
}
.iPhoneCheckContainer input {
position: absolute;
top: 5px;
left: 30px;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
}
.iPhoneCheckContainer label {
white-space: nowrap;
font-size: 17px;
line-height: 17px;
font-weight: bold;
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
cursor: pointer;
display: block;
height: 27px;
position: absolute;
width: auto;
top: 0;
padding-top: 5px;
overflow: hidden;
}
.iPhoneCheckContainer, .iPhoneCheckContainer label {
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
}
.iPhoneCheckDisabled {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
opacity: 0.5;
}
div.iPhoneCheckBorderOn {
position: absolute;
left: 0;
width: 4px;
height: 100%;
background-image: url('images/border-on.png');
background-repeat: no-repeat;
}
label.iPhoneCheckLabelOn {
color: white;
background-image: url('images/label-on.png');
background-repeat: repeat-x;
text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.6);
left: 0;
padding-top: 5px;
margin-left: 4px;
margin-top: 0px;
}
label.iPhoneCheckLabelOn span {
padding-left: 4px;
}
label.iPhoneCheckLabelOff {
color: #8b8b8b;
background-image: url('images/label-off.png');
background-repeat: repeat-x;
text-shadow: 0px 0px 2px rgba(255, 255, 255, 0.6);
text-align: right;
margin-right: 4px;
margin-top: 0px;
right: 0;
}
label.iPhoneCheckLabelOff span {
padding-right: 4px;
}
div.iPhoneCheckBorderOff {
position: absolute;
width: 4px;
height: 100%;
background-image: url('images/border-off.png');
background-repeat: no-repeat;
}
.iPhoneCheckHandle {
display: block;
height: 27px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
width: 0;
background-image: url('images/handle-left.png');
background-repeat: no-repeat;
padding-left: 4px;
}
.iPhoneCheckHandleCenter {
position: absolute;
width: 100%;
height: 100%;
background-image: url('images/handle-center.png');
background-repeat: repeat-x;
}
.iPhoneCheckHandleRight {
position: absolute;
height: 100%;
width: 4px;
right: 0px;
background-image: url('images/handle-right.png');
background-repeat: no-repeat;
}

+ 0
- 366
code/html/vendor/checkboxes.js View File

@ -1,366 +0,0 @@
// Generated by CoffeeScript 1.6.2
/*eslint quotes: ["error", "double"]*/
/*eslint-env es6*/
(function() {
var iOSCheckbox, matched, userAgent,
__slice = [].slice;
if ($.browser == null) {
userAgent = navigator.userAgent || "";
jQuery.uaMatch = function(ua) {
var match;
ua = ua.toLowerCase();
match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version)?[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+))?/.exec(ua) || [];
return {
browser: match[1] || "",
version: match[2] || "0"
};
};
matched = jQuery.uaMatch(userAgent);
jQuery.browser = {};
if (matched.browser) {
jQuery.browser[matched.browser] = true;
jQuery.browser.version = matched.version;
}
if (jQuery.browser.webkit) {
jQuery.browser.safari = true;
}
}
iOSCheckbox = (function() {
function iOSCheckbox(elem, options) {
var key, opts, value;
this.elem = $(elem);
opts = $.extend({}, iOSCheckbox.defaults, options);
for (key in opts) {
if ({}.hasOwnProperty.call(opts, key)) {
value = opts[key];
this[key] = value;
}
}
this.elem.data(this.dataName, this);
this.wrapCheckboxWithDivs();
this.attachEvents();
this.disableTextSelection();
this.calculateDimensions();
}
iOSCheckbox.prototype.calculateDimensions = function() {
if (this.resizeHandle) {
this.optionallyResize("handle");
}
if (this.resizeContainer) {
this.optionallyResize("container");
}
return this.initialPosition();
};
iOSCheckbox.prototype.isDisabled = function() {
return this.elem.is(":disabled");
};
iOSCheckbox.prototype.wrapCheckboxWithDivs = function() {
this.elem.wrap("<div class='" + this.containerClass + "' />");
this.container = this.elem.parent();
this.offLabel = $("<label class='" + this.labelOffClass + "'>\n <span>" + this.uncheckedLabel + "</span>\n</label>").appendTo(this.container);
this.offSpan = this.offLabel.children("span");
this.onLabel = $("<label class='" + this.labelOnClass + "'>\n <span>" + this.checkedLabel + "</span>\n</label>").appendTo(this.container);
this.onBorder = $("<div class='iPhoneCheckBorderOn'</div>").appendTo(this.container);
this.offBorder = $("<div class='iPhoneCheckBorderOff'</div>").appendTo(this.container);
this.onSpan = this.onLabel.children("span");
this.handle = $("<div class='" + this.handleClass + "'></div>").appendTo(this.container);
this.handleCenter = $("<div class='" + this.handleCenterClass + "'></div>").appendTo(this.handle);
this.handleRight = $("<div class='" + this.handleRightClass + "'></div>").appendTo(this.handle);
return true;
};
iOSCheckbox.prototype.disableTextSelection = function() {
if ($.browser.msie) {
return $([this.handle, this.offLabel, this.onLabel, this.container]).attr("unselectable", "on");
}
};
iOSCheckbox.prototype._getDimension = function(elem, dimension) {
if ($.fn.actual != null) {
return elem.actual(dimension);
} else {
return elem[dimension]();
}
};
iOSCheckbox.prototype.optionallyResize = function(mode) {
var newWidth, offLabelWidth, offSpan, onLabelWidth, onSpan;
onSpan = this.onLabel.find("span");
onLabelWidth = this._getDimension(onSpan, "width");
onLabelWidth += parseInt(onSpan.css("padding-left"), 10);
offSpan = this.offLabel.find("span");
offLabelWidth = this._getDimension(offSpan, "width");
offLabelWidth += parseInt(offSpan.css("padding-right"), 10);
if (mode === "container") {
newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth;
newWidth += this._getDimension(this.handle, "width") + this.handleMargin;
return this.container.css({
width: newWidth
});
} else {
newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth;
this.handleCenter.css({
width: newWidth + 4
});
return this.handle.css({
width: newWidth + 7
});
}
};
iOSCheckbox.prototype.onMouseDown = function(event) {
var x;
event.preventDefault();
if (this.isDisabled()) {
return;
}
x = event.pageX || event.originalEvent.changedTouches[0].pageX;
iOSCheckbox.currentlyClicking = this.handle;
iOSCheckbox.dragStartPosition = x;
return iOSCheckbox.handleLeftOffset = parseInt(this.handle.css("left"), 10) || 0;
};
iOSCheckbox.prototype.onDragMove = function(event, x) {
var newWidth, p;
if (iOSCheckbox.currentlyClicking !== this.handle) {
return;
}
p = (x + iOSCheckbox.handleLeftOffset - iOSCheckbox.dragStartPosition) / this.rightSide;
if (p < 0) {
p = 0;
}
if (p > 1) {
p = 1;
}
newWidth = p * this.rightSide;
this.handle.css({
left: newWidth
});
this.onLabel.css({
width: newWidth + this.handleRadius
});
this.offSpan.css({
marginRight: -newWidth
});
return this.onSpan.css({
marginLeft: -(1 - p) * this.rightSide
});
};
iOSCheckbox.prototype.onDragEnd = function(event, x) {
var p;
if (iOSCheckbox.currentlyClicking !== this.handle) {
return;
}
if (this.isDisabled()) {
return;
}
if (iOSCheckbox.dragging) {
p = (x - iOSCheckbox.dragStartPosition) / this.rightSide;
this.elem.prop("checked", p >= 0.5).change();
} else {
this.elem.prop("checked", !this.elem.prop("checked")).change();
}
iOSCheckbox.currentlyClicking = null;
iOSCheckbox.dragging = null;
if (typeof this.onChange === "function") {
this.onChange(this.elem, this.elem.prop("checked"));
}
return this.didChange();
};
iOSCheckbox.prototype.refresh = function() {
return this.didChange();
};
iOSCheckbox.prototype.didChange = function() {
var newLeft;
if (this.isDisabled()) {
this.container.addClass(this.disabledClass);
return false;
} else {
this.container.removeClass(this.disabledClass);
}
newLeft = this.elem.prop("checked") ? this.rightSide + 2 : 0;
this.handle.animate({
left: newLeft
}, this.duration);
this.onLabel.animate({
width: newLeft + this.handleRadius
}, this.duration);
this.offSpan.animate({
marginRight: - newLeft
}, this.duration);
return this.onSpan.animate({
marginLeft: newLeft - this.rightSide
}, this.duration);
};
iOSCheckbox.prototype.attachEvents = function() {
var localMouseMove, localMouseUp, self;
self = this;
localMouseMove = function(event) {
return self.onGlobalMove.apply(self, arguments);
};
localMouseUp = function(event) {
self.onGlobalUp.apply(self, arguments);
$(document).unbind("mousemove touchmove", localMouseMove);
return $(document).unbind("mouseup touchend", localMouseUp);
};
this.elem.change(function() {
return self.refresh();
});
return this.container.bind("mousedown touchstart", function(event) {
self.onMouseDown.apply(self, arguments);
$(document).bind("mousemove touchmove", localMouseMove);
return $(document).bind("mouseup touchend", localMouseUp);
});
};
iOSCheckbox.prototype.initialPosition = function() {
var containerWidth, offset;
containerWidth = this._getDimension(this.container, "width");
this.offLabel.css({
width: containerWidth - this.containerRadius - 4
});
this.offBorder.css({
left: containerWidth - 4
});
offset = this.containerRadius + 1;
if ($.browser.msie && $.browser.version < 7) {
offset -= 3;
}
this.rightSide = containerWidth - this._getDimension(this.handle, "width") - offset;
if (this.elem.is(":checked")) {
this.handle.css({
left: this.rightSide
});
this.onLabel.css({
width: this.rightSide + this.handleRadius
});
this.offSpan.css({
marginRight: -this.rightSide,
});
} else {
this.onLabel.css({
width: 0
});
this.onSpan.css({
marginLeft: -this.rightSide
});
}
if (this.isDisabled()) {
return this.container.addClass(this.disabledClass);
}
};
iOSCheckbox.prototype.onGlobalMove = function(event) {
var x;
if (!(!this.isDisabled() && iOSCheckbox.currentlyClicking)) {
return;
}
event.preventDefault();
x = event.pageX || event.originalEvent.changedTouches[0].pageX;
if (!iOSCheckbox.dragging && (Math.abs(iOSCheckbox.dragStartPosition - x) > this.dragThreshold)) {
iOSCheckbox.dragging = true;
}
return this.onDragMove(event, x);
};
iOSCheckbox.prototype.onGlobalUp = function(event) {
var x;
if (!iOSCheckbox.currentlyClicking) {
return;
}
event.preventDefault();
x = event.pageX || event.originalEvent.changedTouches[0].pageX;
this.onDragEnd(event, x);
return false;
};
iOSCheckbox.defaults = {
duration: 200,
checkedLabel: "ON",
uncheckedLabel: "OFF",
resizeHandle: true,
resizeContainer: true,
disabledClass: "iPhoneCheckDisabled",
containerClass: "iPhoneCheckContainer",
labelOnClass: "iPhoneCheckLabelOn",
labelOffClass: "iPhoneCheckLabelOff",
handleClass: "iPhoneCheckHandle",
handleCenterClass: "iPhoneCheckHandleCenter",
handleRightClass: "iPhoneCheckHandleRight",
dragThreshold: 5,
handleMargin: 15,
handleRadius: 4,
containerRadius: 5,
dataName: "iphoneStyle",
onChange: function() {}
};
return iOSCheckbox;
})();
$.iphoneStyle = this.iOSCheckbox = iOSCheckbox;
$.fn.iphoneStyle = function() {
var args, checkbox, dataName, existingControl, method, params, _i, _len, _ref, _ref1, _ref2, _ref3;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
dataName = (_ref = (_ref1 = args[0]) != null ? _ref1.dataName : void 0) != null ? _ref : iOSCheckbox.defaults.dataName;
_ref2 = this.filter(":checkbox");
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
checkbox = _ref2[_i];
existingControl = $(checkbox).data(dataName);
if (existingControl != null) {
method = args[0], params = 2 <= args.length ? __slice.call(args, 1) : [];
if ((_ref3 = existingControl[method]) != null) {
_ref3.apply(existingControl, params);
}
} else {
new iOSCheckbox(checkbox, args[0]);
}
}
return this;
};
$.fn.iOSCheckbox = function(options) {
var opts;
if (options == null) {
options = {};
}
opts = $.extend({}, options, {
resizeHandle: false,
disabledClass: "iOSCheckDisabled",
containerClass: "iOSCheckContainer",
labelOnClass: "iOSCheckLabelOn",
labelOffClass: "iOSCheckLabelOff",
handleClass: "iOSCheckHandle",
handleCenterClass: "iOSCheckHandleCenter",
handleRightClass: "iOSCheckHandleRight",
dataName: "iOSCheckbox"
});
return this.iphoneStyle(opts);
};
}).call(this);

+ 460
- 0
code/html/vendor/datatables-1.10.16.css View File

@ -0,0 +1,460 @@
/*
* This combined file was created by the DataTables downloader builder:
* https://datatables.net/download
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#dt/dt-1.10.16
*
* Included libraries:
* DataTables 1.10.16
*/
/*
* Table styles
*/
table.dataTable {
width: 100%;
margin: 0 auto;
clear: both;
border-collapse: separate;
border-spacing: 0;
/*
* Header and footer styles
*/
/*
* Body styles
*/
}
table.dataTable thead th,
table.dataTable tfoot th {
font-weight: bold;
}
table.dataTable thead th,
table.dataTable thead td {
padding: 10px 18px;
border-bottom: 1px solid #111;
}
table.dataTable thead th:active,
table.dataTable thead td:active {
outline: none;
}
table.dataTable tfoot th,
table.dataTable tfoot td {
padding: 10px 18px 6px 18px;
border-top: 1px solid #111;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
cursor: pointer;
*cursor: hand;
background-repeat: no-repeat;
background-position: center right;
}
table.dataTable thead .sorting {
background-image: url("images/sort_both.png");
}
table.dataTable thead .sorting_asc {
background-image: url("images/sort_asc.png");
}
table.dataTable thead .sorting_desc {
background-image: url("images/sort_desc.png");
}
table.dataTable thead .sorting_asc_disabled {
background-image: url("images/sort_asc_disabled.png");
}
table.dataTable thead .sorting_desc_disabled {
background-image: url("images/sort_desc_disabled.png");
}
table.dataTable tbody tr {
background-color: #ffffff;
}
table.dataTable tbody tr.selected {
background-color: #B0BED9;
}
table.dataTable tbody th,
table.dataTable tbody td {
padding: 8px 10px;
}
table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {
border-top: 1px solid #ddd;
}
table.dataTable.row-border tbody tr:first-child th,
table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,
table.dataTable.display tbody tr:first-child td {
border-top: none;
}
table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {
border-top: 1px solid #ddd;
border-right: 1px solid #ddd;
}
table.dataTable.cell-border tbody tr th:first-child,
table.dataTable.cell-border tbody tr td:first-child {
border-left: 1px solid #ddd;
}
table.dataTable.cell-border tbody tr:first-child th,
table.dataTable.cell-border tbody tr:first-child td {
border-top: none;
}
table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {
background-color: #f9f9f9;
}
table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {
background-color: #acbad4;
}
table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
background-color: #f6f6f6;
}
table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {
background-color: #aab7d1;
}
table.dataTable.order-column tbody tr > .sorting_1,
table.dataTable.order-column tbody tr > .sorting_2,
table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
table.dataTable.display tbody tr > .sorting_2,
table.dataTable.display tbody tr > .sorting_3 {
background-color: #fafafa;
}
table.dataTable.order-column tbody tr.selected > .sorting_1,
table.dataTable.order-column tbody tr.selected > .sorting_2,
table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,
table.dataTable.display tbody tr.selected > .sorting_2,
table.dataTable.display tbody tr.selected > .sorting_3 {
background-color: #acbad5;
}
table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
background-color: #f1f1f1;
}
table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
background-color: #f3f3f3;
}
table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
background-color: whitesmoke;
}
table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
background-color: #a6b4cd;
}
table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
background-color: #a8b5cf;
}
table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
background-color: #a9b7d1;
}
table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
background-color: #fafafa;
}
table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
background-color: #fcfcfc;
}
table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
background-color: #fefefe;
}
table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
background-color: #acbad5;
}
table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
background-color: #aebcd6;
}
table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
background-color: #afbdd8;
}
table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
background-color: #eaeaea;
}
table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
background-color: #ececec;
}
table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
background-color: #efefef;
}
table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
background-color: #a2aec7;
}
table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
background-color: #a3b0c9;
}
table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
background-color: #a5b2cb;
}
table.dataTable.no-footer {
border-bottom: 1px solid #111;
}
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.compact thead th,
table.dataTable.compact thead td {
padding: 4px 17px 4px 4px;
}
table.dataTable.compact tfoot th,
table.dataTable.compact tfoot td {
padding: 4px;
}
table.dataTable.compact tbody th,
table.dataTable.compact tbody td {
padding: 4px;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
}
table.dataTable,
table.dataTable th,
table.dataTable td {
box-sizing: content-box;
}
/*
* Control feature layout
*/
.dataTables_wrapper {
position: relative;
clear: both;
*zoom: 1;
zoom: 1;
}
.dataTables_wrapper .dataTables_length {
float: left;
}
.dataTables_wrapper .dataTables_filter {
float: right;
text-align: right;
}
.dataTables_wrapper .dataTables_filter input {
margin-left: 0.5em;
}
.dataTables_wrapper .dataTables_info {
clear: both;
float: left;
padding-top: 0.755em;
}
.dataTables_wrapper .dataTables_paginate {
float: right;
text-align: right;
padding-top: 0.25em;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
box-sizing: border-box;
display: inline-block;
min-width: 1.5em;
padding: 0.5em 1em;
margin-left: 2px;
text-align: center;
text-decoration: none !important;
cursor: pointer;
*cursor: hand;
color: #333 !important;
border: 1px solid transparent;
border-radius: 2px;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
color: #333 !important;
border: 1px solid #979797;
background-color: white;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);
/* IE10+ */
background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);
/* W3C */
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
cursor: default;
color: #666 !important;
border: 1px solid transparent;
background: transparent;
box-shadow: none;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
color: white !important;
border: 1px solid #111;
background-color: #585858;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, #585858 0%, #111 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, #585858 0%, #111 100%);
/* IE10+ */
background: -o-linear-gradient(top, #585858 0%, #111 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, #585858 0%, #111 100%);
/* W3C */
}
.dataTables_wrapper .dataTables_paginate .paginate_button:active {
outline: none;
background-color: #2b2b2b;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* IE10+ */
background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);
/* W3C */
box-shadow: inset 0 0 3px #111;
}
.dataTables_wrapper .dataTables_paginate .ellipsis {
padding: 0 1em;
}
.dataTables_wrapper .dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 40px;
margin-left: -50%;
margin-top: -25px;
padding-top: 20px;
text-align: center;
font-size: 1.2em;
background-color: white;
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));
background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
}
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_processing,
.dataTables_wrapper .dataTables_paginate {
color: #333;
}
.dataTables_wrapper .dataTables_scroll {
clear: both;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
*margin-top: -1px;
-webkit-overflow-scrolling: touch;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td {
vertical-align: middle;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing {
height: 0;
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
}
.dataTables_wrapper.no-footer .dataTables_scrollBody {
border-bottom: 1px solid #111;
}
.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,
.dataTables_wrapper.no-footer div.dataTables_scrollBody > table {
border-bottom: none;
}
.dataTables_wrapper:after {
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
}
@media screen and (max-width: 767px) {
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate {
float: none;
text-align: center;
}
.dataTables_wrapper .dataTables_paginate {
margin-top: 0.5em;
}
}
@media screen and (max-width: 640px) {
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter {
float: none;
text-align: center;
}
.dataTables_wrapper .dataTables_filter {
margin-top: 0.5em;
}
}

+ 178
- 0
code/html/vendor/datatables-1.10.16.min.js View File

@ -0,0 +1,178 @@
/*
* This combined file was created by the DataTables downloader builder:
* https://datatables.net/download
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#dt/dt-1.10.16
*
* Included libraries:
* DataTables 1.10.16
*/
/*!
DataTables 1.10.16
©2008-2017 SpryMedia Ltd - datatables.net/license
*/
(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(E){return h(E,window,document)}):"object"===typeof exports?module.exports=function(E,G){E||(E=window);G||(G="undefined"!==typeof window?require("jquery"):require("jquery")(E));return h(G,E,E.document)}:h(jQuery,window,document)})(function(h,E,G,k){function X(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),
d[c]=e,"o"===b[1]&&X(a[e])});a._hungarianMap=d}function I(a,b,c){a._hungarianMap||X(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),I(a[d],b[d],c)):b[d]=b[e]})}function Ca(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&F(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&F(a,a,"sZeroRecords","sLoadingRecords");
a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&cb(a)}function db(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");"boolean"===typeof a.scrollX&&(a.scrollX=
a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&I(m.models.oSearch,a[b])}function eb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;"number"===typeof b&&!h.isArray(b)&&(a.aDataSort=[b])}function fb(a){if(!m.__browser){var b={};m.__browser=b;var c=h("<div/>").css({position:"fixed",top:0,left:-1*h(E).scrollLeft(),height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",
top:1,left:1,width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function gb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==
e;)a.hasOwnProperty(d)&&(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Da(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:G.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);ja(a,d,h(b).data())}function ja(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=
e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(eb(c),I(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),h.extend(b,c),F(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),F(b,c,"aDataSort"));var g=b.mData,j=Q(g),i=b.mRender?Q(b.mRender):null,c=function(a){return"string"===
typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return R(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=
d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ea(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&ka(a);r(a,null,"column-sizing",[a])}function Z(a,b){var c=la(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=
la(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function aa(a){var b=0;h.each(a.aoColumns,function(a,d){d.bVisible&&"none"!==h(d.nTh).css("display")&&b++});return b}function la(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Fa(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,j,i,h,l,q,t;e=0;for(f=b.length;e<f;e++)if(l=b[e],t=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(j=d.length;g<j;g++){i=0;for(h=c.length;i<h;i++){t[i]===
k&&(t[i]=B(a,i,e,"type"));q=d[g](t[i],a);if(!q&&g!==d.length-1)break;if("html"===q)break}if(q){l.sType=q;break}}l.sType||(l.sType="string")}}function hb(a,b,c,d){var e,f,g,j,i,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var q=n.targets!==k?n.targets:n.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Da(a);d(q[f],n)}else if("number"===typeof q[f]&&0>q[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;j<
i;j++)("_all"==q[f]||h(l[j].nTh).hasClass(q[f]))&&d(j,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function M(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,j=0,i=g.length;j<i;j++)g[j].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ga(a,e,c,d);return e}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=Ha(a,e);return M(a,
c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,i=f.fnGetData(g,d,{settings:a,row:b,col:c});if(i===k)return a.iDrawError!=e&&null===j&&(J(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=e),j;if((i===g||null===i)&&null!==j&&d!==k)i=j;else if("function"===typeof i)return i.call(g);return null===i&&"display"==d?"":i}function ib(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,
d,{settings:a,row:b,col:c})}function Ia(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\\./g,".")})}function Q(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=Q(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,
b,f){var g,j;if(""!==f){j=Ia(f);for(var i=0,n=j.length;i<n;i++){f=j[i].match(ba);g=j[i].match(U);if(f){j[i]=j[i].replace(ba,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");if(h.isArray(a)){i=0;for(n=a.length;i<n;i++)g.push(c(a[i],b,j))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(U,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===k)return k;a=a[j[i]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function R(a){if(h.isPlainObject(a))return R(a._);
if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=Ia(e),f;f=e[e.length-1];for(var g,j,i=0,n=e.length-1;i<n;i++){g=e[i].match(ba);j=e[i].match(U);if(g){e[i]=e[i].replace(ba,"");a[e[i]]=[];f=e.slice();f.splice(0,i+1);g=f.join(".");if(h.isArray(d)){j=0;for(n=d.length;j<n;j++)f={},b(f,d[j],g),a[e[i]].push(f)}else a[e[i]]=d;return}j&&(e[i]=e[i].replace(U,
""),a=a[e[i]](d));if(null===a[e[i]]||a[e[i]]===k)a[e[i]]={};a=a[e[i]]}if(f.match(U))a[f.replace(U,"")](d);else a[f.replace(ba,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ja(a){return D(a.aoData,"_aData")}function na(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function oa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ca(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ha(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;Ka(a,e)}}function Ha(a,b,c,d){var e=[],f=b.firstChild,g,j,i=0,n,l=a.aoColumns,q=a._rowReadObject,d=d!==k?d:q?{}:[],t=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
-1!==c&&(c=a.substring(c+1),R(a)(d,b.getAttribute(c)))}},m=function(a){if(c===k||c===i)j=l[i],n=h.trim(a.innerHTML),j&&j._bAttrSrc?(R(j.mData._)(d,n),t(j.mData.sort,a),t(j.mData.type,a),t(j.mData.filter,a)):q?(j._setter||(j._setter=R(j.mData)),j._setter(d,n)):d[i]=n;i++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)m(f),e.push(f);f=f.nextSibling}else{e=b.anCells;f=0;for(g=e.length;f<g;f++)m(e[f])}if(b=b.firstChild?b:b.nTr)(b=b.getAttribute("id"))&&R(a.rowId)(d,b);return{data:d,cells:e}}
function Ga(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],j,i,n,l,q;if(null===e.nTr){j=c||G.createElement("tr");e.nTr=j;e.anCells=g;j._DT_RowIndex=b;Ka(a,e);l=0;for(q=a.aoColumns.length;l<q;l++){n=a.aoColumns[l];i=c?d[l]:G.createElement(n.sCellType);i._DT_CellIndex={row:b,column:l};g.push(i);if((!c||n.mRender||n.mData!==l)&&(!h.isPlainObject(n.mData)||n.mData._!==l+".display"))i.innerHTML=B(a,b,l,"display");n.sClass&&(i.className+=" "+n.sClass);n.bVisible&&!c?j.appendChild(i):!n.bVisible&&c&&i.parentNode.removeChild(i);
n.fnCreatedCell&&n.fnCreatedCell.call(a.oInstance,i,B(a,b,l),f,b,l)}r(a,"aoRowCreatedCallback",null,[j,f,b])}e.nTr.setAttribute("role","row")}function Ka(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?qa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function jb(a){var b,c,d,e,f,g=a.nTHead,j=a.nTFoot,i=0===
h("th, td",g).length,n=a.oClasses,l=a.aoColumns;i&&(e=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),i&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),La(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Ma(a,"header")(a,d,f,n);i&&da(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH);
if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ea(a,b,c){var d,e,f,g=[],j=[],i=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);j.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=i=1,j[d][f]===k){a.appendChild(g[d][f].cell);
for(j[d][f]=1;g[d+i]!==k&&g[d][f].cell==g[d+i][f].cell;)j[d+i][f]=1,i++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<i;c++)j[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",i).attr("colspan",n)}}}}function N(a){var b=r(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==y(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=
-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!kb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j<f;j++){var l=i[j],q=a.aoData[l];null===q.nTr&&Ga(a,l);l=q.nTr;if(0!==e){var t=d[c%e];q._sRowStripe!=t&&(h(l).removeClass(q._sRowStripe).addClass(t),q._sRowStripe=t)}r(a,"aoRowCallback",null,[l,q._aData,c,j]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:
f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];r(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ja(a),g,n,i]);r(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ja(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));r(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function S(a,b){var c=a.oFeatures,d=c.bFilter;
c.bSort&&lb(a);d?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;N(a);a._drawHold=!1}function mb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,k=0;k<f.length;k++){g=null;j=f[k];if("<"==j){i=h("<div/>")[0];
n=f[k+1];if("'"==n||'"'==n){l="";for(q=2;f[k+q]!=n;)l+=f[k+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;k+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=nb(a);else if("f"==j&&d.bFilter)g=ob(a);else if("r"==j&&d.bProcessing)g=pb(a);else if("t"==j)g=qb(a);else if("i"==j&&d.bInfo)g=rb(a);else if("p"==
j&&d.bPaginate)g=sb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q<n;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function da(a,b){var c=h(b).children("tr"),d,e,f,g,j,i,n,l,q,k;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");
q=1*e.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;n=g;k=1===l?!0:!1;for(j=0;j<l;j++)for(g=0;g<q;g++)a[f+g][n+j]={cell:e,unique:k},a[f+g].nTr=d}e=e.nextSibling}}}function ra(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],da(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function sa(a,b,c){r(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},
e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,j=a.oInstance,i=function(b){r(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&J(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=r(a,null,"xhr",
[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==c?J(a,0,"Invalid JSON response",1):4===b.readyState&&J(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;r(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function kb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,
!0),sa(a,tb(a),function(b){ub(a,b)}),!1):!0}function tb(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,n,l,k=V(a);g=a._iDisplayStart;i=!1!==d.bPaginate?a._iDisplayLength:-1;var t=function(a,b){j.push({name:a,value:b})};t("sEcho",a.iDraw);t("iColumns",c);t("sColumns",D(b,"sName").join(","));t("iDisplayStart",g);t("iDisplayLength",i);var pa={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],
l=f[g],i="function"==typeof n.mData?"function":n.mData,pa.columns.push({data:i,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),t("mDataProp_"+g,i),d.bFilter&&(t("sSearch_"+g,l.sSearch),t("bRegex_"+g,l.bRegex),t("bSearchable_"+g,n.bSearchable)),d.bSort&&t("bSortable_"+g,n.bSortable);d.bFilter&&(t("sSearch",e.sSearch),t("bRegex",e.bRegex));d.bSort&&(h.each(k,function(a,b){pa.order.push({column:b.col,dir:b.dir});t("iSortCol_"+a,b.col);t("sSortDir_"+
a,b.dir)}),t("iSortingCols",k.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?j:pa:b?j:pa}function ub(a,b){var c=ta(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}na(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)M(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;N(a);a._bInitComplete||
ua(a,b);a.bAjaxDataGet=!0;C(a,!1)}function ta(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?Q(c)(b):b}function ob(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),f=function(){var b=!this.value?
"":this.value;b!=e.sSearch&&(fa(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,N(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,i=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).on("keyup.DT search.DT input.DT paste.DT cut.DT",g?Na(f,g):f).on("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==G.activeElement&&i.val(e.sSearch)}catch(d){}});
return b[0]}function fa(a,b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Fa(a);if("ssp"!=y(a)){vb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)wb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);xb(a)}else f(b);a.bFiltered=!0;r(a,null,"search",[a])}function xb(a){for(var b=
m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<g;f++){for(var j=[],i=0,n=c.length;i<n;i++)e=c[i],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,i)&&j.push(e);c.length=0;h.merge(c,j)}}function wb(a,b,c,d,e,f){if(""!==b){for(var g=[],j=a.aiDisplay,d=Oa(b,d,e,f),e=0;e<j.length;e++)b=a.aoData[j[e]]._aFilterData[c],d.test(b)&&g.push(j[e]);a.aiDisplay=g}}function vb(a,b,c,d,e,f){var d=Oa(b,d,e,f),f=a.oPreviousSearch.sSearch,g=a.aiDisplayMaster,j,e=[];0!==m.ext.search.length&&(c=!0);j=yb(a);if(0>=b.length)a.aiDisplay=
g.slice();else{if(j||c||f.length>b.length||0!==b.indexOf(f)||a.bSorted)a.aiDisplay=g.slice();b=a.aiDisplay;for(c=0;c<b.length;c++)d.test(a.aoData[b[c]]._sFilterRow)&&e.push(b[c]);a.aiDisplay=e}}function Oa(a,b,c,d){a=b?a:Pa(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function yb(a){var b=a.aoColumns,c,d,e,f,g,j,i,h,l=m.ext.type.search;c=!1;
d=0;for(f=a.aoData.length;d<f;d++)if(h=a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(i=B(a,d,e,"filter"),l[c.sType]&&(i=l[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(va.innerHTML=i,i=Wb?va.textContent:va.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join(" ");c=!0}return c}function zb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,
caseInsensitive:a.bCaseInsensitive}}function Ab(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function rb(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Bb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Bb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+
1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Cb(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Cb(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,
f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ga(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){mb(a);jb(a);ea(a,a.aoHeader);ea(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ea(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=v(f.sWidth));r(a,null,"preInit",[a]);S(a);e=y(a);if("ssp"!=e||g)"ajax"==e?sa(a,[],function(c){var f=ta(a,c);for(b=0;b<f.length;b++)M(a,f[b]);a.iInitDisplayStart=
d;S(a);C(a,!1);ua(a,c)},a):(C(a,!1),ua(a))}else setTimeout(function(){ga(a)},200)}function ua(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&Y(a);r(a,null,"plugin-init",[a,b]);r(a,"aoInitComplete","init",[a,b])}function Qa(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Ra(a);r(a,null,"length",[a,c])}function nb(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)e[0][g]=
new Option("number"===typeof d[g]?a.fnFormatNumber(d[g]):d[g],f[g]);var i=h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).on("change.DT",function(){Qa(a,h(this).val());N(a)});h(a.nTable).on("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function sb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){N(a)},
b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;l<k;l++)Ma(a,"pageButton")(a,f.p[l],l,h,b,i)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Sa(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===
e?d=0:"number"===typeof b?(d=b*e,d>f&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:J(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(r(a,null,"page",[a]),c&&N(a));return b}function pb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",
b?"block":"none");r(a,null,"processing",[a,b])}function qb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:v(d):"100%"}).append(h("<div/>",
{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:v(d)}).append(b));l&&i.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:v(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",
0).append("bottom"===j?g:null).append(b.children("tfoot")))));var b=i.children(),k=b[0],f=b[1],t=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(t.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=t;a.aoDrawCallback.push({fn:ka,sName:"scrolling"});return i[0]}function ka(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),
j=a.nScrollBody,l=h(j),q=j.style,t=h(a.nScrollFoot).children("div"),m=t.children("table"),o=h(a.nTHead),p=h(a.nTable),s=p[0],r=s.style,u=a.nTFoot?h(a.nTFoot):null,x=a.oBrowser,T=x.bScrollOversize,Xb=D(a.aoColumns,"nTh"),O,K,P,w,Ta=[],y=[],z=[],A=[],B,C=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};K=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==K&&a.scrollBarVis!==k)a.scrollBarVis=K,Y(a);else{a.scrollBarVis=K;p.children("thead, tfoot").remove();
u&&(P=u.clone().prependTo(p),O=u.find("tr"),P=P.find("tr"));w=o.clone().prependTo(p);o=o.find("tr");K=w.find("tr");w.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(ra(a,w),function(b,c){B=Z(a,b);c.style.width=a.aoColumns[B].sWidth});u&&H(function(a){a.style.width=""},P);f=p.outerWidth();if(""===c){r.width="100%";if(T&&(p.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=v(p.outerWidth()-b);f=p.outerWidth()}else""!==d&&(r.width=
v(d),f=p.outerWidth());H(C,K);H(function(a){z.push(a.innerHTML);Ta.push(v(h(a).css("width")))},K);H(function(a,b){if(h.inArray(a,Xb)!==-1)a.style.width=Ta[b]},o);h(K).height(0);u&&(H(C,P),H(function(a){A.push(a.innerHTML);y.push(v(h(a).css("width")))},P),H(function(a,b){a.style.width=y[b]},O),h(P).height(0));H(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+z[b]+"</div>";a.style.width=Ta[b]},K);u&&H(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+
A[b]+"</div>";a.style.width=y[b]},P);if(p.outerWidth()<f){O=j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(T&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=v(O-b);(""===c||""!==d)&&J(a,1,"Possible column misalignment",6)}else O="100%";q.width=v(O);g.width=v(O);u&&(a.nScrollFoot.style.width=v(O));!e&&T&&(q.height=v(s.offsetHeight+b));c=p.outerWidth();n[0].style.width=v(c);i.width=v(c);d=p.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+
(x.bScrollbarLeft?"Left":"Right");i[e]=d?b+"px":"0px";u&&(m[0].style.width=v(c),t[0].style.width=v(c),t[0].style[e]=d?b+"px":"0px");p.children("colgroup").insertBefore(p.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function H(a,b,c){for(var d=0,e=0,f=b.length,g,j;e<f;){g=b[e].firstChild;for(j=c?c[e].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,d):a(g,d),d++),g=g.nextSibling,j=c?j.nextSibling:null;e++}}function Ea(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,
e=d.sY,f=d.sX,g=d.sXInner,j=c.length,i=la(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,t=!1,m,o,p=a.oBrowser,d=p.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<i.length;m++)o=c[i[m]],null!==o.sWidth&&(o.sWidth=Db(o.sWidthOrig,k),t=!0);if(d||!t&&!f&&!e&&j==aa(a)&&j==n.length)for(m=0;m<j;m++)i=Z(a,m),null!==i&&(c[i].sWidth=v(n.eq(m).width()));else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var s=h("<tr/>").appendTo(j.find("tbody"));
j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=ra(a,j.find("thead")[0]);for(m=0;m<i.length;m++)o=c[i[m]],n[m].style.width=null!==o.sWidthOrig&&""!==o.sWidthOrig?v(o.sWidthOrig):"",o.sWidthOrig&&f&&h(n[m]).append(h("<div/>").css({width:o.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m<i.length;m++)t=i[m],o=c[t],h(Eb(a,t)).clone(!1).append(o.sContentPadding).appendTo(s);h("[name]",
j).removeAttr("name");o=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()<k.clientWidth&&l&&j.width(k.clientWidth)):e?j.width(k.clientWidth):l&&j.width(l);for(m=e=0;m<i.length;m++)k=h(n[m]),g=k.outerWidth()-k.width(),k=p.bBounding?Math.ceil(n[m].getBoundingClientRect().width):k.outerWidth(),e+=k,c[i[m]].sWidth=v(k-g);b.style.width=v(e);o.remove()}l&&(b.style.width=
v(l));if((l||f)&&!a._reszEvt)b=function(){h(E).on("resize.DT-"+a.sInstance,Na(function(){Y(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function Db(a,b){if(!a)return 0;var c=h("<div/>").css("width",v(a)).appendTo(b||G.body),d=c[0].offsetWidth;c.remove();return d}function Eb(a,b){var c=Fb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Fb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace(Yb,
""),c=c.replace(/&nbsp;/g," "),c.length>d&&(d=c.length,e=f);return e}function v(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){i=n[a][0];f=e[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=e[g].sType||
"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:i,col:g,dir:n[a][1],index:n[a]._idx,type:j,formatter:m.ext.type.order[j+"-pre"]})}return d}function lb(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;Fa(a);h=V(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Gb(a,j.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)d[i[b]]=b;g===h.length?i.sort(function(a,b){var c,e,g,j,i=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=
0;g<i;g++)if(j=h[g],c=k[j.col],e=m[j.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,o=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=m[i.col],g=o[i.col],i=e[i.type+"-"+i.dir]||e["string-"+i.dir],c=i(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Hb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,
"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(i.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=j[e[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",b)}}function Ua(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,
D(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);S(a);"function"==typeof d&&d(a)}function La(a,b,c,d){var e=a.aoColumns[c];Va(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Ua(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Ua(a,c,b.shiftKey,d))})}
function wa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=V(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;for(f=d.length;e<f;e++)g=d[e].src,h(D(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Gb(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],
c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[j]:B(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function xa(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:zb(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:zb(a.aoPreSearchCols[d])}})};r(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,
b)}}function Ib(a,b,c){var d,e,f=a.aoColumns,b=function(b){if(b&&b.time){var g=r(a,"aoStateLoadParams","stateLoadParams",[a,b]);if(-1===h.inArray(!1,g)&&(g=a.iStateDuration,!(0<g&&b.time<+new Date-1E3*g)&&!(b.columns&&f.length!==b.columns.length))){a.oLoadedState=h.extend(!0,{},b);b.start!==k&&(a._iDisplayStart=b.start,a.iInitDisplayStart=b.start);b.length!==k&&(a._iDisplayLength=b.length);b.order!==k&&(a.aaSorting=[],h.each(b.order,function(b,c){a.aaSorting.push(c[0]>=f.length?[0,c[1]]:c)}));b.search!==
k&&h.extend(a.oPreviousSearch,Ab(b.search));if(b.columns){d=0;for(e=b.columns.length;d<e;d++)g=b.columns[d],g.visible!==k&&(f[d].bVisible=g.visible),g.search!==k&&h.extend(a.aoPreSearchCols[d],Ab(g.search))}r(a,"aoStateLoaded","stateLoaded",[a,b])}}c()};if(a.oFeatures.bStateSave){var g=a.fnStateLoadCallback.call(a.oInstance,a,b);g!==k&&b(g)}else c()}function ya(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function J(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+
" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)E.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&r(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,d,c)}}function F(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?F(a,b,d[0],d[1]):F(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Jb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],
h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&h.isArray(d)?d.slice():d);return a}function Va(a,b,c){h(a).on("click.DT",b,function(b){a.blur();c(b)}).on("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).on("selectstart.DT",function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function r(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+
".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Ra(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Ma(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ha(a,b){var c=[],c=Kb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,
c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function cb(a){h.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Wa)},"html-num":function(b){return za(b,a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Wa)}},function(b,c){x.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(x.type.search[b+a]=x.type.search.html)})}function Lb(a){return function(){var b=
[ya(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new s(ya(this[x.iApiIndex])):new s(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=
function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&ka(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};
this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();
return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return ya(this[x.iApiIndex])};
this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=x.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=x.internal;for(var e in m.ext.internal)e&&(this[e]=Lb(e));this.each(function(){var e={},g=1<d?Jb(e,a,!0):
a,j=0,i,e=this.getAttribute("id"),n=!1,l=m.defaults,q=h(this);if("table"!=this.nodeName.toLowerCase())J(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{db(l);eb(l.column);I(l,l,!0);I(l.column,l.column,!0);I(l,h.extend(g,q.data()));var t=m.settings,j=0;for(i=t.length;j<i;j++){var o=t[j];if(o.nTable==this||o.nTHead.parentNode==this||o.nTFoot&&o.nTFoot.parentNode==this){var s=g.bRetrieve!==k?g.bRetrieve:l.bRetrieve;if(c||s)return o.oInstance;if(g.bDestroy!==k?g.bDestroy:l.bDestroy){o.oInstance.fnDestroy();
break}else{J(o,0,"Cannot reinitialise DataTable",3);return}}if(o.sTableId==this.id){t.splice(j,1);break}}if(null===e||""===e)this.id=e="DataTables_Table_"+m.ext._unique++;var p=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:e,sTableId:e});p.nTable=this;p.oApi=b.internal;p.oInit=g;t.push(p);p.oInstance=1===b.length?b:q.dataTable();db(g);g.oLanguage&&Ca(g.oLanguage);g.aLengthMenu&&!g.iDisplayLength&&(g.iDisplayLength=h.isArray(g.aLengthMenu[0])?g.aLengthMenu[0][0]:g.aLengthMenu[0]);
g=Jb(h.extend(!0,{},l),g);F(p.oFeatures,g,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));F(p,g,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],
["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"]]);F(p.oScroll,g,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);F(p.oLanguage,g,"fnInfoCallback");z(p,"aoDrawCallback",g.fnDrawCallback,"user");z(p,"aoServerParams",g.fnServerParams,"user");z(p,"aoStateSaveParams",g.fnStateSaveParams,"user");z(p,"aoStateLoadParams",g.fnStateLoadParams,"user");z(p,"aoStateLoaded",g.fnStateLoaded,"user");z(p,"aoRowCallback",
g.fnRowCallback,"user");z(p,"aoRowCreatedCallback",g.fnCreatedRow,"user");z(p,"aoHeaderCallback",g.fnHeaderCallback,"user");z(p,"aoFooterCallback",g.fnFooterCallback,"user");z(p,"aoInitComplete",g.fnInitComplete,"user");z(p,"aoPreDrawCallback",g.fnPreDrawCallback,"user");p.rowIdFn=Q(g.rowId);fb(p);var u=p.oClasses;h.extend(u,m.ext.classes,g.oClasses);q.addClass(u.sTable);p.iInitDisplayStart===k&&(p.iInitDisplayStart=g.iDisplayStart,p._iDisplayStart=g.iDisplayStart);null!==g.iDeferLoading&&(p.bDeferLoading=
!0,e=h.isArray(g.iDeferLoading),p._iRecordsDisplay=e?g.iDeferLoading[0]:g.iDeferLoading,p._iRecordsTotal=e?g.iDeferLoading[1]:g.iDeferLoading);var v=p.oLanguage;h.extend(!0,v,g.oLanguage);v.sUrl&&(h.ajax({dataType:"json",url:v.sUrl,success:function(a){Ca(a);I(l.oLanguage,a);h.extend(true,v,a);ga(p)},error:function(){ga(p)}}),n=!0);null===g.asStripeClasses&&(p.asStripeClasses=[u.sStripeOdd,u.sStripeEven]);var e=p.asStripeClasses,x=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(e,function(a){return x.hasClass(a)}))&&
(h("tbody tr",this).removeClass(e.join(" ")),p.asDestroyStripes=e.slice());e=[];t=this.getElementsByTagName("thead");0!==t.length&&(da(p.aoHeader,t[0]),e=ra(p));if(null===g.aoColumns){t=[];j=0;for(i=e.length;j<i;j++)t.push(null)}else t=g.aoColumns;j=0;for(i=t.length;j<i;j++)Da(p,e?e[j]:null);hb(p,g.aoColumnDefs,t,function(a,b){ja(p,a,b)});if(x.length){var w=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(x[0]).children("th, td").each(function(a,b){var c=p.aoColumns[a];if(c.mData===
a){var d=w(b,"sort")||w(b,"order"),e=w(b,"filter")||w(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};ja(p,a)}}})}var T=p.oFeatures,e=function(){if(g.aaSorting===k){var a=p.aaSorting;j=0;for(i=a.length;j<i;j++)a[j][1]=p.aoColumns[j].asSorting[0]}wa(p);T.bSort&&z(p,"aoDrawCallback",function(){if(p.bSorted){var a=V(p),b={};h.each(a,function(a,c){b[c.src]=c.dir});r(p,null,"order",[p,a,b]);Hb(p)}});
z(p,"aoDrawCallback",function(){(p.bSorted||y(p)==="ssp"||T.bDeferRender)&&wa(p)},"sc");var a=q.children("caption").each(function(){this._captionSide=h(this).css("caption-side")}),b=q.children("thead");b.length===0&&(b=h("<thead/>").appendTo(q));p.nTHead=b[0];b=q.children("tbody");b.length===0&&(b=h("<tbody/>").appendTo(q));p.nTBody=b[0];b=q.children("tfoot");if(b.length===0&&a.length>0&&(p.oScroll.sX!==""||p.oScroll.sY!==""))b=h("<tfoot/>").appendTo(q);if(b.length===0||b.children().length===0)q.addClass(u.sNoFooter);
else if(b.length>0){p.nTFoot=b[0];da(p.aoFooter,p.nTFoot)}if(g.aaData)for(j=0;j<g.aaData.length;j++)M(p,g.aaData[j]);else(p.bDeferLoading||y(p)=="dom")&&ma(p,h(p.nTBody).children("tr"));p.aiDisplay=p.aiDisplayMaster.slice();p.bInitialised=true;n===false&&ga(p)};g.bStateSave?(T.bStateSave=!0,z(p,"aoDrawCallback",xa,"state_save"),Ib(p,g,e)):e()}});b=null;return this},x,s,o,u,Xa={},Mb=/[\r\n]/g,Aa=/<.*?>/g,Zb=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,$b=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)",
"g"),Wa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,L=function(a){return!a||!0===a||"-"===a?!0:!1},Nb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Ob=function(a,b){Xa[b]||(Xa[b]=RegExp(Pa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Xa[b],"."):a},Ya=function(a,b,c){var d="string"===typeof a;if(L(a))return!0;b&&d&&(a=Ob(a,b));c&&d&&(a=a.replace(Wa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Pb=function(a,b,c){return L(a)?!0:!(L(a)||"string"===
typeof a)?null:Ya(a.replace(Aa,""),b,c)?!0:null},D=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]);return d},ia=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},W=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Qb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);
return b},qa=function(a){var b;a:{if(!(2>a.length)){b=a.slice().sort();for(var c=b[0],d=1,e=b.length;d<e;d++){if(b[d]===c){b=!1;break a}c=b[d]}}b=!0}if(b)return a.slice();b=[];var e=a.length,f,g=0,d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b};m.util={throttle:function(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,j=arguments;d&&g<d+c?(clearTimeout(e),e=setTimeout(function(){d=k;a.apply(b,j)},c)):(d=g,a.apply(b,j))}},escapeRegex:function(a){return a.replace($b,
"\\$1")}};var A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ba=/\[.*?\]$/,U=/\(\)$/,Pa=m.util.escapeRegex,va=h("<div>")[0],Wb=va.textContent!==k,Yb=/<.*?>/g,Na=m.util.throttle,Rb=[],w=Array.prototype,ac=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof
h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};s=function(a,b){if(!(this instanceof s))return new s(a,b);var c=[],d=function(a){(a=ac(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=qa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};s.extend(this,this,Rb)};m.Api=s;h.extend(s.prototype,{any:function(){return 0!==this.count()},concat:w.concat,context:[],count:function(){return this.flatten().length},
each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new s(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new s(this.context,b)},flatten:function(){var a=[];return new s(this.context,a.concat.apply(a,this.toArray()))},join:w.join,indexOf:w.indexOf||function(a,b){for(var c=b||0,
d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,j,h,n,l=this.context,m,o,u=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(j=l.length;g<j;g++){var r=new s(l[g]);if("table"===b)f=c.call(r,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(r,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===b||"row"===b||"cell"===b){o=this[g];"column-rows"===b&&(m=Ba(l[g],u.opts));h=0;for(n=o.length;h<n;h++)f=o[h],f=
"cell"===b?c.call(r,l[g],f.row,f.column,g,h):c.call(r,l[g],f,g,h,m),f!==k&&e.push(f)}}return e.length||d?(a=new s(l,a?e.concat.apply([],e):e),b=a.selector,b.rows=u.rows,b.cols=u.cols,b.opts=u.opts,a):this},lastIndexOf:w.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(w.map)b=w.map.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new s(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},
pop:w.pop,push:w.push,reduce:w.reduce||function(a,b){return gb(this,a,b,0,this.length,1)},reduceRight:w.reduceRight||function(a,b){return gb(this,a,b,this.length-1,-1,-1)},reverse:w.reverse,selector:null,shift:w.shift,slice:function(){return new s(this.context,this)},sort:w.sort,splice:w.splice,toArray:function(){return w.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},unique:function(){return new s(this.context,qa(this))},unshift:w.unshift});s.extend=function(a,
b,c){if(c.length&&b&&(b instanceof s||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);s.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,s.extend(a,b[f.name],f.propExt)}};s.register=o=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<d;c++)s.register(a[c],b);else for(var e=a.split("."),f=Rb,g,j,c=0,d=e.length;c<d;c++){g=(j=-1!==
e[c].indexOf("()"))?e[c].replace("()",""):e[c];var i;a:{i=0;for(var n=f.length;i<n;i++)if(f[i].name===g){i=f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===d-1?i.val=b:f=j?i.methodExt:i.propExt}};s.registerPlural=u=function(a,b,c){s.register(a,c);s.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof s?a.length?h.isArray(a[0])?new s(a.context,a[0]):a[0]:k:a})};o("tables()",function(a){var b;if(a){b=s;var c=this.context;if("number"===
typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});o("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new s(b[0]):a});u("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});u("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody},1)});u("tables().header()",
"table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});u("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});u("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});o("draw()",function(a){return this.iterator("table",function(b){"page"===a?N(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),S(b,!1===a))})});o("page()",function(a){return a===
k?this.page.info().page:this.iterator("table",function(b){Sa(b,a)})});o("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===y(a)}});o("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:
k:this.iterator("table",function(b){Qa(b,a)})});var Sb=function(a,b,c){if(c){var d=new s(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))S(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();sa(a,[],function(c){na(a);for(var c=ta(a,c),d=0,e=c.length;d<e;d++)M(a,c[d]);S(a,b);C(a,!1)})}};o("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});o("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});o("ajax.reload()",function(a,
b){return this.iterator("table",function(c){Sb(c,!1===b,a)})});o("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});o("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Sb(c,!1===b,a)})});var Za=function(a,b,c,d,e){var f=[],g,j,i,n,l,m;i=typeof b;if(!b||"string"===i||"function"===
i||b.length===k)b=[b];i=0;for(n=b.length;i<n;i++){j=b[i]&&b[i].split&&!b[i].match(/[\[\(:]/)?b[i].split(","):[b[i]];l=0;for(m=j.length;l<m;l++)(g=c("string"===typeof j[l]?h.trim(j[l]):j[l]))&&g.length&&(f=f.concat(g))}a=x.selector[a];if(a.length){i=0;for(n=a.length;i<n;i++)f=a[i](d,e,f)}return qa(f)},$a=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},ab=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=
a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Ba=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===j?[]:W(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===h.inArray(a,g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<
d;c++)"none"==j?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==j||0<=e&&"applied"==j)&&f.push(c))}return f};o("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=b,f;return Za("row",a,function(a){var b=Nb(a);if(b!==null&&!e)return[b];f||(f=Ba(c,e));if(b!==null&&h.inArray(b,f)!==-1)return[b];if(a===null||a===k||a==="")return f;if(typeof a==="function")return h.map(f,function(b){var e=c.aoData[b];return a(b,e._aData,e.nTr)?b:null});
b=Qb(ia(c.aoData,f,"nTr"));if(a.nodeName){if(a._DT_RowIndex!==k)return[a._DT_RowIndex];if(a._DT_CellIndex)return[a._DT_CellIndex.row];b=h(a).closest("*[data-dt-row]");return b.length?[b.data("dt-row")]:[]}if(typeof a==="string"&&a.charAt(0)==="#"){var i=c.aIds[a.replace(/^#/,"")];if(i!==k)return[i.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});o("rows().nodes()",function(){return this.iterator("row",function(a,
b){return a.aoData[b].nTr||k},1)});o("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return ia(a.aoData,b,"_aData")},1)});u("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});u("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ca(b,c,a)})});u("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,
b){return b},1)});u("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new s(c,b)});u("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c],g,h,i,n,l;e.splice(c,1);g=0;for(h=e.length;g<h;g++)if(i=e[g],l=i.anCells,null!==i.nTr&&(i.nTr._DT_RowIndex=g),null!==l){i=0;for(n=l.length;i<
n;i++)l[i]._DT_CellIndex.row=g}oa(b.aiDisplayMaster,c);oa(b.aiDisplay,c);oa(a[d],c,!1);0<b._iRecordsDisplay&&b._iRecordsDisplay--;Ra(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});o("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(M(b,c));return h},1),c=this.rows(-1);
c.pop();h.merge(c,b);return c});o("row()",function(a,b){return ab(this.rows(a,b))});o("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=a;ca(b[0],this[0],"data");return this});o("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});o("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&
"TR"===a.nodeName.toUpperCase()?ma(b,a)[0]:M(b,a)});return this.row(b[0])});var bb=function(a,b){var c=a.context;if(c.length&&(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=k,c._details=k},Tb=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new s(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<D(g,
"_details").length&&(f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===b)for(var c,d=aa(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&bb(f,c)}))}}};o("row().child()",function(a,b){var c=this.context;
if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)bb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(h.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=aa(d),e.push(c[0]))};f(a,b);c._details&&c._details.detach();c._details=h(e);c._detailsShow&&
c._details.insertAfter(c.nTr)}return this});o(["row().child.show()","row().child().show()"],function(){Tb(this,!0);return this});o(["row().child.hide()","row().child().hide()"],function(){Tb(this,!1);return this});o(["row().child.remove()","row().child().remove()"],function(){bb(this);return this});o("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var bc=/^([^:]+):(name|visIdx|visible)$/,Ub=function(a,b,c,d,e){for(var c=
[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));return c};o("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,j=D(g,"sName"),i=D(g,"nTh");return Za("column",e,function(a){var b=Nb(a);if(a==="")return W(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Ba(c,f);return h.map(g,function(b,f){return a(f,Ub(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(bc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=
parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[Z(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});u("columns().header()",
"column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});u("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});u("columns().data()","column().data()",function(){return this.iterator("column-rows",Ub,1)});u("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});u("columns().cache()","column().cache()",
function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ia(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});u("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ia(a.aoData,e,"anCells",b)},1)});u("columns().visible()","column().visible()",function(a,b){var c=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var f=b.aoColumns,g=f[c],j=b.aoData,i,n,l;if(a!==k&&g.bVisible!==a){if(a){var m=
h.inArray(!0,D(f,"bVisible"),c+1);i=0;for(n=j.length;i<n;i++)l=j[i].nTr,f=j[i].anCells,l&&l.insertBefore(f[c],f[m]||null)}else h(D(b.aoData,"anCells",c)).detach();g.bVisible=a;ea(b,b.aoHeader);ea(b,b.aoFooter);xa(b)}});a!==k&&(this.iterator("column",function(c,e){r(c,null,"column-visibility",[c,e,a,b])}),(b===k||b)&&this.columns.adjust());return c});u("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?$(b,c):c},1)});o("columns.adjust()",
function(){return this.iterator("table",function(a){Y(a)},1)});o("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return Z(c,b);if("fromData"===a||"toVisible"===a)return $(c,b)}});o("column()",function(a,b){return ab(this.columns(a,b))});o("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",function(b){var d=a,e=$a(c),f=
b.aoData,g=Ba(b,e),j=Qb(ia(f,g,"anCells")),i=h([].concat.apply([],j)),l,n=b.aoColumns.length,m,o,u,s,r,v;return Za("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){m=[];o=0;for(u=g.length;o<u;o++){l=g[o];for(s=0;s<n;s++){r={row:l,column:s};if(c){v=f[l];a(r,B(b,l,s),v.anCells?v.anCells[s]:null)&&m.push(r)}else m.push(r)}}return m}if(h.isPlainObject(a))return[a];c=i.filter(a).map(function(a,b){return{row:b._DT_CellIndex.row,column:b._DT_CellIndex.column}}).toArray();if(c.length||
!a.nodeName)return c;v=h(a).closest("*[data-dt-row]");return v.length?[{row:v.data("dt-row"),column:v.data("dt-column")}]:[]},b,e)});var d=this.columns(b,c),e=this.rows(a,c),f,g,j,i,n,l=this.iterator("table",function(a,b){f=[];g=0;for(j=e[b].length;g<j;g++){i=0;for(n=d[b].length;i<n;i++)f.push({row:e[b][g],column:d[b][i]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});u("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b])&&
a.anCells?a.anCells[c]:k},1)});o("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});u("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});u("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});u("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,
b,c){return{row:b,column:c,columnVisible:$(a,c)}},1)});u("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){ca(b,c,a,d)})});o("cell()",function(a,b,c){return ab(this.cells(a,b,c))});o("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;ib(b[0],c[0].row,c[0].column,a);ca(b[0],c[0].row,"data",c[0].column);return this});o("order()",function(a,b){var c=this.context;if(a===k)return 0!==
c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:a.length&&!h.isArray(a[0])&&(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});o("order.listener()",function(a,b,c){return this.iterator("table",function(d){La(d,a,b,c)})});o("order.fixed()",function(a){if(!a){var b=this.context,b=b.length?b[0].aaSortingFixed:k;return h.isArray(b)?{pre:b}:b}return this.iterator("table",function(b){b.aaSortingFixed=h.extend(!0,{},a)})});o(["columns().order()",
"column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});o("search()",function(a,b,c,d){var e=this.context;return a===k?0!==e.length?e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&fa(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});u("columns().search()","column().search()",function(a,
b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),fa(e,e.oPreviousSearch,1))})});o("state()",function(){return this.context.length?this.context[0].oSavedState:null});o("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});o("state.loaded()",function(){return this.context.length?
this.context[0].oLoadedState:null});o("state.save()",function(){return this.iterator("table",function(a){xa(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,d,e=0,f=a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;if(a instanceof m.Api)return!0;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?
h("table",e.nScrollFoot)[0]:null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new s(c):c};m.camelToHungarian=I;o("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){o(b+"()",function(){var a=Array.prototype.slice.call(arguments);
a[0]=h.map(a[0].split(/\s/),function(a){return!a.match(/\.dt\b/)?a+".dt":a}).join(" ");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});o("clear()",function(){return this.iterator("table",function(a){na(a)})});o("settings()",function(){return new s(this.context,this.context)});o("init()",function(){var a=this.context;return a.length?a[0].oInit:null});o("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});o("destroy()",function(a){a=a||
!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),o;b.bDestroying=!0;r(b,"aoDestroyCallback","destroy",[b]);a||(new s(b)).columns().visible(!0);k.off(".DT").find(":not(tbody *)").off(".DT");h(E).off(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));
b.aaSorting=[];b.aaSortingFixed=[];wa(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",b.sDestroyWidth).removeClass(d.sTable),(o=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%o])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,
1)})});h.each(["column","row","cell"],function(a,b){o(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,n){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,n)})})});o("i18n()",function(a,b,c){var d=this.context[0],a=Q(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.16";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow=
{nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,
sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+
"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",
sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};
X(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,
bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],
aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,
aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,
b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=x={buttons:{},classes:{},build:"dt/dt-1.10.16",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},
order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(x,{afnFiltering:x.search,aTypes:x.type.detect,ofnSearch:x.type.search,oSort:x.type.order,afnSortData:x.order,aoFeatures:x.feature,oApi:x.internal,oStdClasses:x.classes,oPagination:x.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",
sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",
sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Kb=m.ext.pager;h.extend(Kb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ha(a,
b)]},simple_numbers:function(a,b){return["previous",ha(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ha(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",ha(a,b),"last"]},_numbers:ha,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},n,l,m=0,o=function(b,d){var k,s,u,r,v=function(b){Sa(a,b.data.action,true)};k=0;for(s=d.length;k<s;k++){r=d[k];if(h.isArray(r)){u=
h("<"+(r.DT_el||"div")+"/>").appendTo(b);o(u,r)}else{n=null;l="";switch(r){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;case "first":n=j.sFirst;l=r+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":n=j.sPrevious;l=r+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":n=j.sNext;l=r+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":n=j.sLast;l=r+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:n=r+1;l=e===r?g.sPageButtonActive:""}if(n!==null){u=h("<a>",{"class":g.sPageButton+
" "+l,"aria-controls":a.sTableId,"aria-label":i[r],"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof r==="string"?a.sTableId+"_"+r:null}).html(n).appendTo(b);Va(u,{action:r},v);m++}}}},s;try{s=h(b).find(G.activeElement).data("dt-idx")}catch(u){}o(h(b).empty(),d);s!==k&&h(b).find("[data-dt-idx="+s+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&!Zb.test(a))return null;var b=Date.parse(a);
return null!==b&&!isNaN(b)||L(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Pb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Pb(a,c,!0)?"html-num-fmt"+c:null},function(a){return L(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return L(a)?a:"string"===typeof a?a.replace(Mb," ").replace(Aa,""):""},string:function(a){return L(a)?
a:"string"===typeof a?a.replace(Mb," "):a}});var za=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Ob(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(x.type.order,{"date-pre":function(a){return Date.parse(a)||-Infinity},"html-pre":function(a){return L(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return L(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<
b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});cb("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);
h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Vb=function(a){return"string"===typeof a?a.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,
"&quot;"):a};m.render={number:function(a,b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Vb(f);h=h.toFixed(c);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Vb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Lb,_fnBuildAjax:sa,_fnAjaxUpdate:kb,_fnAjaxParameters:tb,_fnAjaxUpdateDraw:ub,
_fnAjaxDataSrc:ta,_fnAddColumn:Da,_fnColumnOptions:ja,_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:Z,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:la,_fnColumnTypes:Fa,_fnApplyColumnDefs:hb,_fnHungarianMap:X,_fnCamelToHungarian:I,_fnLanguageCompat:Ca,_fnBrowserDetect:fb,_fnAddData:M,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:ib,
_fnSplitObjNotation:Ia,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:R,_fnGetDataMaster:Ja,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:Ha,_fnCreateTr:Ga,_fnBuildHead:jb,_fnDrawHead:ea,_fnDraw:N,_fnReDraw:S,_fnAddOptionsHtml:mb,_fnDetectHeader:da,_fnGetUniqueThs:ra,_fnFeatureHtmlFilter:ob,_fnFilterComplete:fa,_fnFilterCustom:xb,_fnFilterColumn:wb,_fnFilter:vb,_fnFilterCreateSearch:Oa,_fnEscapeRegex:Pa,_fnFilterData:yb,_fnFeatureHtmlInfo:rb,_fnUpdateInfo:Bb,_fnInfoMacros:Cb,_fnInitialise:ga,
_fnInitComplete:ua,_fnLengthChange:Qa,_fnFeatureHtmlLength:nb,_fnFeatureHtmlPaginate:sb,_fnPageChange:Sa,_fnFeatureHtmlProcessing:pb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:qb,_fnScrollDraw:ka,_fnApplyToChildren:H,_fnCalculateColumnWidths:Ea,_fnThrottle:Na,_fnConvertToWidth:Db,_fnGetWidestNode:Eb,_fnGetMaxLenString:Fb,_fnStringToCss:v,_fnSortFlatten:V,_fnSort:lb,_fnSortAria:Hb,_fnSortListener:Ua,_fnSortAttachListener:La,_fnSortingClasses:wa,_fnSortData:Gb,_fnSaveState:xa,_fnLoadState:Ib,_fnSettingsFromNode:ya,
_fnLog:J,_fnMap:F,_fnBindAction:Va,_fnCallbackReg:z,_fnCallbackFire:r,_fnLengthOverflow:Ra,_fnRenderer:Ma,_fnDataSource:y,_fnRowAttributes:Ka,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable});

BIN
code/html/vendor/images/border-off.png View File

Before After
Width: 4  |  Height: 27  |  Size: 308 B

BIN
code/html/vendor/images/border-on.png View File

Before After
Width: 4  |  Height: 27  |  Size: 302 B

BIN
code/html/vendor/images/handle-center.png View File

Before After
Width: 4  |  Height: 27  |  Size: 190 B

BIN
code/html/vendor/images/handle-left.png View File

Before After
Width: 4  |  Height: 27  |  Size: 264 B

BIN
code/html/vendor/images/handle-right.png View File

Before After
Width: 4  |  Height: 27  |  Size: 264 B

BIN
code/html/vendor/images/label-off.png View File

Before After
Width: 4  |  Height: 27  |  Size: 211 B

BIN
code/html/vendor/images/label-on.png View File

Before After
Width: 4  |  Height: 27  |  Size: 214 B

BIN
code/html/vendor/images/sort_asc.png View File

Before After
Width: 19  |  Height: 19  |  Size: 160 B

BIN
code/html/vendor/images/sort_both.png View File

Before After
Width: 19  |  Height: 19  |  Size: 201 B

BIN
code/html/vendor/images/sort_desc.png View File

Before After
Width: 19  |  Height: 19  |  Size: 158 B

+ 7
- 7
code/ota.py View File

@ -230,7 +230,7 @@ def store(device, env):
def run(device, env):
print("Building and flashing image over-the-air...")
command = "export ESPURNA_IP=\"%s\"; export ESPURNA_BOARD=\"%s\"; export ESPURNA_AUTH=\"%s\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s -t upload"
command = "ESPURNA_IP=\"%s\" ESPURNA_BOARD=\"%s\" ESPURNA_AUTH=\"%s\" ESPURNA_FLAGS=\"%s\" platformio run --silent --environment %s -t upload"
command = command % (device['ip'], device['board'], device['auth'], device['flags'], env)
subprocess.check_call(command, shell=True)
store(device, env)
@ -313,12 +313,12 @@ if __name__ == '__main__':
# Summary
print()
print("HOST = %s" % boardname(board))
print("IP = %s" % board['ip'])
print("BOARD = %s" % board['board'])
print("AUTH = %s" % board['auth'])
print("FLAGS = %s" % board['flags'])
print("ENV = %s" % env)
print("HOST = %s" % boardname(board))
print("IP = %s" % board['ip'])
print("BOARD = %s" % board['board'])
print("AUTH = %s" % board['auth'])
print("FLAGS = %s" % board['flags'])
print("ENV = %s" % env)
response = True
if args.yes == 0:


+ 270
- 7
code/package-lock.json View File

@ -48,6 +48,15 @@
"ansi-wrap": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz"
}
},
"ansi-cyan": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz",
"integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=",
"dev": true,
"requires": {
"ansi-wrap": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz"
}
},
"ansi-gray": {
"version": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz",
"integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=",
@ -56,6 +65,15 @@
"ansi-wrap": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz"
}
},
"ansi-red": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz",
"integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=",
"dev": true,
"requires": {
"ansi-wrap": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz"
}
},
"ansi-regex": {
"version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
@ -841,6 +859,16 @@
"clone": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz"
}
},
"define-properties": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
"integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
"dev": true,
"requires": {
"foreach": "2.0.5",
"object-keys": "1.0.11"
}
},
"define-property": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
@ -981,6 +1009,30 @@
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
"dev": true
},
"es-abstract": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
"integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
"dev": true,
"requires": {
"es-to-primitive": "1.1.1",
"function-bind": "1.1.1",
"has": "1.0.3",
"is-callable": "1.1.3",
"is-regex": "1.0.4"
}
},
"es-to-primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
"integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
"dev": true,
"requires": {
"is-callable": "1.1.3",
"is-date-object": "1.0.1",
"is-symbol": "1.0.1"
}
},
"escape-string-regexp": {
"version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
@ -999,11 +1051,19 @@
"requires": {
"duplexer": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
"from": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
"map-stream": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
"map-stream": "0.1.0",
"pause-stream": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
"split": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
"stream-combiner": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
"through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
},
"dependencies": {
"map-stream": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
"dev": true
}
}
},
"expand-brackets": {
@ -1273,6 +1333,12 @@
"for-in": "1.0.2"
}
},
"foreach": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
"dev": true
},
"forever-agent": {
"version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
@ -1307,6 +1373,12 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"gaze": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz",
@ -1579,7 +1651,7 @@
},
"gulp-csslint": {
"version": "https://registry.npmjs.org/gulp-csslint/-/gulp-csslint-1.0.1.tgz",
"integrity": "sha1-ESqQj3rvmO/Ce3vQCAHxOne+y5M=",
"integrity": "sha512-Rec56+RpCGg7feK3d/S45oqgxyLV3end0ed+UjWFv6YziQae2Bp4DNSDobwEvJdfCAsOhOSExEEB+jcfMx430w==",
"dev": true,
"requires": {
"csslint": "https://registry.npmjs.org/csslint/-/csslint-1.0.5.tgz",
@ -1602,7 +1674,7 @@
},
"plugin-error": {
"version": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
"integrity": "sha1-dwFr2JGdCsN3/c3QMiMolTyleBw=",
"integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==",
"dev": true,
"requires": {
"ansi-colors": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
@ -1658,8 +1730,9 @@
}
},
"gulp-htmllint": {
"version": "https://registry.npmjs.org/gulp-htmllint/-/gulp-htmllint-0.0.14.tgz",
"integrity": "sha1-fobrpgVmjfsVstLVAZX+VqdVhaI=",
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/gulp-htmllint/-/gulp-htmllint-0.0.14.tgz",
"integrity": "sha512-D5FfR2G4SUwxPd/ypE5glZSXXddl3bnGz9JlY3gS5e8AXSvYJco+baIWtpT8fq5aqyr5IELf2I+VgCe6b+yq1A==",
"dev": true,
"requires": {
"gulp-util": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz",
@ -1720,6 +1793,66 @@
"through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz"
}
},
"gulp-remove-code": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gulp-remove-code/-/gulp-remove-code-3.0.2.tgz",
"integrity": "sha512-Ptrwce+Nq8B+zPMomMzjfkEpldBpbHDOtE4Py0jyxh3eE7scfzEjxAs0XojwFw4xBkLWuNdqetZhPk7zsuyxAw==",
"dev": true,
"requires": {
"bufferstreams": "2.0.1",
"escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"object.entries": "1.0.4",
"plugin-error": "1.0.1",
"through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz"
},
"dependencies": {
"bufferstreams": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-2.0.1.tgz",
"integrity": "sha512-ZswyIoBfFb3cVDsnZLLj2IDJ/0ppYdil/v2EGlZXvoefO689FokEmFEldhN5dV7R2QBxFneqTJOMIpfqhj+n0g==",
"dev": true,
"requires": {
"readable-stream": "2.3.6"
}
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
"core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"isarray": "1.0.0",
"process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"string_decoder": "1.1.1",
"util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
}
}
}
},
"gulp-rename": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.3.0.tgz",
"integrity": "sha512-nEuZB7/9i0IZ8AXORTizl2QLP9tcC9uWc/s329zElBLJw1CfOhmMXBxwVlCRKjDyrWuhVP0uBKl61KeQ32TiCg==",
"dev": true
},
"gulp-replace": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.0.0.tgz",
@ -1847,6 +1980,15 @@
"har-schema": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "1.1.1"
}
},
"has-ansi": {
"version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
@ -2103,6 +2245,12 @@
"integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=",
"dev": true
},
"is-callable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
"integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=",
"dev": true
},
"is-data-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
@ -2112,6 +2260,12 @@
"kind-of": "6.0.2"
}
},
"is-date-object": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
"dev": true
},
"is-descriptor": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
@ -2218,6 +2372,15 @@
"isobject": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz"
}
},
"is-regex": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
"dev": true,
"requires": {
"has": "1.0.3"
}
},
"is-relative": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
@ -2227,6 +2390,12 @@
"is-unc-path": "1.0.0"
}
},
"is-symbol": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
"integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=",
"dev": true
},
"is-typedarray": {
"version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
@ -2601,8 +2770,9 @@
"dev": true
},
"map-stream": {
"version": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
"integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=",
"dev": true
},
"map-visit": {
@ -2846,6 +3016,12 @@
}
}
},
"object-keys": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
"integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=",
"dev": true
},
"object-visit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
@ -2867,6 +3043,18 @@
"isobject": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz"
}
},
"object.entries": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz",
"integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=",
"dev": true,
"requires": {
"define-properties": "1.1.2",
"es-abstract": "1.12.0",
"function-bind": "1.1.1",
"has": "1.0.3"
}
},
"object.map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
@ -3038,6 +3226,18 @@
"pinkie": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
}
},
"plugin-error": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
"integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==",
"dev": true,
"requires": {
"ansi-colors": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
"arr-diff": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
"arr-union": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
"extend-shallow": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz"
}
},
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -3321,6 +3521,69 @@
"glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz"
}
},
"run-sequence": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz",
"integrity": "sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw==",
"dev": true,
"requires": {
"chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"fancy-log": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz",
"plugin-error": "0.1.2"
},
"dependencies": {
"arr-diff": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz",
"integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=",
"dev": true,
"requires": {
"arr-flatten": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
"array-slice": "0.2.3"
}
},
"arr-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz",
"integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=",
"dev": true
},
"array-slice": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz",
"integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=",
"dev": true
},
"extend-shallow": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz",
"integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=",
"dev": true,
"requires": {
"kind-of": "1.1.0"
}
},
"kind-of": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=",
"dev": true
},
"plugin-error": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz",
"integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=",
"dev": true,
"requires": {
"ansi-cyan": "0.1.1",
"ansi-red": "0.1.1",
"arr-diff": "1.1.0",
"arr-union": "2.1.0",
"extend-shallow": "1.1.4"
}
}
}
},
"safe-buffer": {
"version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=",


+ 5
- 2
code/package.json View File

@ -7,7 +7,6 @@
"license": "GPL-3.0",
"devDependencies": {
"del": "^2.2.1",
"fancy-log": "^1.3.2",
"gulp": "^3.9.1",
"gulp-base64-favicon": "^1.0.2",
"gulp-crass": "^0.2.2",
@ -17,7 +16,11 @@
"gulp-htmllint": "0.0.14",
"gulp-htmlmin": "^2.0.0",
"gulp-inline": "^0.1.1",
"gulp-remove-code": "^3.0.2",
"gulp-rename": "^1.3.0",
"gulp-replace": "^1.0.0",
"gulp-uglify": "^1.5.3"
"gulp-uglify": "^1.5.3",
"map-stream": "0.0.7",
"run-sequence": "^2.2.1"
}
}

+ 963
- 818
code/platformio.ini
File diff suppressed because it is too large
View File


BIN
images/devices/allnet-esp8266-up-relay.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 89 KiB

BIN
images/devices/bh-onofre.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 29 KiB

BIN
images/devices/blitzwolf-bw-shp2.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 15 KiB

BIN
images/devices/fornorm-power-strip.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 18 KiB

BIN
images/devices/generic-geiger-diy.png View File

Before After
Width: 400  |  Height: 400  |  Size: 148 KiB

BIN
images/devices/itead-sonoff-ifan02.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 13 KiB

BIN
images/devices/itead-sonoff-pow-r2.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 40 KiB

BIN
images/devices/itead-sonoff-s31.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 13 KiB

BIN
images/devices/luani-hvio.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 54 KiB

BIN
images/devices/neo-coolcam-wifi.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 13 KiB

BIN
images/devices/pilotak-esp-din.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 77 KiB

BIN
images/devices/tinkerman-rfm69gw.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 45 KiB

BIN
images/devices/vanzavanzu-smart-wifi-plug-mini.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 13 KiB

BIN
images/devices/zhilde-zld-eu55-w.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 13 KiB

+ 5
- 2
pre-commit View File

@ -84,7 +84,7 @@ TEMPLATES = {
"(https://travis-ci.org/{USER}/{REPO})\n",
"![version]": "[![version](https://img.shields.io/badge/version-{VERSION}-brightgreen.svg)](CHANGELOG.md)\n",
"![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH}-orange.svg)]" \
"(https://github.org/{USER}/{REPO}/tree/{BRANCH}/)\n",
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n",
"![codacy]": "[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/{BRANCH}.svg)]" \
"(https://www.codacy.com/app/{USER}/{REPO}/dashboard)\n"
}
@ -120,4 +120,7 @@ if __name__ == "__main__":
for line in readme:
sys.stdout.write(fmt_line(line))
sys.exit(call(["git", "add", README]))
if call(["git", "add", README]):
sys.exit(1)
sys.exit(0);

Loading…
Cancel
Save