mirror of https://github.com/arendst/Tasmota.git
commit
6fbb12bb6f
|
@ -8,11 +8,11 @@ about: Create a report to help us improve
|
|||
> This BUG issue template is meant to REPORT Tasmota software BUGS ONLY>
|
||||
>
|
||||
> Please DO NOT OPEN AN ISSUE:
|
||||
> - If your Tasmota version is not the latest from the development branch, please update your device before submitting your issue. Your problem might already be solved. The latest precompiled binaries of Tasmota can be downloaded from http://thehackbox.org/tasmota/>
|
||||
> - If your issue is a flashing issue, please address it to the Tasmota Support Chat>
|
||||
> - If your issue is compilation problem, please address it to the Tasmota Support Chat>
|
||||
> - If your issue has been addresed before (duplicated issue), please ask in the original issue>
|
||||
> - If your issue is a Wi-Fi problem or MQTT problem, please try the steps provided in the FAQ and troubleshooting wiki articles>
|
||||
> - If your Tasmota version is not the latest from the development branch, please update your device before submitting your issue. Your problem might already be solved. The latest precompiled binaries of Tasmota can be downloaded from http://thehackbox.org/tasmota/
|
||||
> - If your issue is a flashing issue, please address it to the [Tasmota Support Chat](https://discord.gg/Ks2Kzd4)
|
||||
> - If your issue is compilation problem, please address it to the [Tasmota Support Chat](https://discord.gg/Ks2Kzd4)
|
||||
> - If your issue has been addressed before (i.e., duplicated issue), please ask in the original issue
|
||||
> - If your issue is a Wi-Fi problem or MQTT problem, please try the steps provided in the [FAQ](https://github.com/arendst/Sonoff-Tasmota/wiki/FAQ) and troubleshooting wiki articles
|
||||
>
|
||||
> Please take a few minutes to complete the requested information below. Our ability to provide assistance is greatly hampered without it. The details requested potentially affect which options to pursue. The small amount of time you spend completing the template will also help the volunteers providing the assistance to you to reduce the time required to help you.
|
||||
|
||||
|
@ -26,33 +26,35 @@ _Make sure your have performed every step and checked the applicable boxes befor
|
|||
**FAILURE TO COMPLETE THE REQUESTED INFORMATION WILL RESULT IN YOUR ISSUE BEING CLOSED**
|
||||
|
||||
- [ ] Read the [Contributing Guide and Policy](https://github.com/arendst/Sonoff-Tasmota/blob/development/CONTRIBUTING.md) and [the Code of Conduct](https://github.com/arendst/Sonoff-Tasmota/blob/development/CODE_OF_CONDUCT.md)
|
||||
- [ ] Searched the problem in issues (https://github.com/arendst/Sonoff-Tasmota/issues)
|
||||
- [ ] Searched the problem in the wiki (https://github.com/arendst/Sonoff-Tasmota/wiki/Troubleshooting)
|
||||
- [ ] Searched the problem in the forum (https://groups.google.com/d/forum/sonoffusers)
|
||||
- [ ] Searched the problem in the chat (https://discord.gg/Ks2Kzd4)
|
||||
- [ ] Searched the problem in [issues](https://github.com/arendst/Sonoff-Tasmota/issues)
|
||||
- [ ] Searched the problem in the [wiki](https://github.com/arendst/Sonoff-Tasmota/wiki/Troubleshooting)
|
||||
- [ ] Searched the problem in the [forum](https://groups.google.com/d/forum/sonoffusers)
|
||||
- [ ] Searched the problem in the [chat](https://discord.gg/Ks2Kzd4)
|
||||
- [ ] Device used (e.g., Sonoff Basic): _____
|
||||
- [ ] Tasmota binary firmware version number used: _____
|
||||
- [ ] Pre-compiled
|
||||
- [ ] Self-compiled
|
||||
- [ ] IDE / Compiler
|
||||
- [ ] IDE / Compiler used: _____
|
||||
- [ ] Flashing tools used: _____
|
||||
- [ ] Provide the output of command ``Backlog Template; Module; GPIO``:
|
||||
- [ ] Provide the output of command: ``Backlog Template; Module; GPIO``:
|
||||
```
|
||||
Configuration output here:
|
||||
|
||||
|
||||
```
|
||||
- [ ] If using rules, provide the output of command ``Backlog Rule1; Rule2; Rule3``:
|
||||
- [ ] If using rules, provide the output of this command: ``Backlog Rule1; Rule2; Rule3``:
|
||||
```
|
||||
Rules output here:
|
||||
|
||||
|
||||
```
|
||||
- [ ] Provide the output of command ``Status 0``:
|
||||
- [ ] Provide the output of this command: ``Status 0``:
|
||||
```
|
||||
STATUS 0 output here:
|
||||
|
||||
|
||||
```
|
||||
- [ ] Provide the output of console when you experience your issue if applicable:
|
||||
- [ ] Provide the output of the Console log output when you experience your issue; if applicable:
|
||||
_(Please use_ ``weblog 4`` _for more debug information)_
|
||||
```
|
||||
Console output here:
|
||||
|
|
|
@ -8,13 +8,13 @@ about: Users Troubleshooting Help
|
|||
> This troubleshooting issue template is meant to help Tasmota users with difficult problems. It is aimed to be opened if using the wiki and the support chat could not solve the issue. The Github Issue tracker is NOT a general discussion forum!
|
||||
>
|
||||
> Please DO NOT OPEN AN ISSUE:
|
||||
> - If you have general questions or you need help on Tasmota usage, go to the Tasmota support chat
|
||||
> - If you have general questions or you need help on Tasmota usage, go to the [Tasmota Support Chat](https://discord.gg/Ks2Kzd4)
|
||||
> - If your Tasmota version is not the latest from the development branch, please update your device before submitting your issue. Your problem might already be solved. The latest precompiled binaries of Tasmota can be downloaded from http://thehackbox.org/tasmota/
|
||||
> - If your issue is about a new device, please use the Tasmota [Template](../wiki/Templates) feature.
|
||||
> - If your issue is a flashing issue, please address it to the Tasmota Support Chat
|
||||
> - If your issue is compilation problem, please address it to the Tasmota Support Chat
|
||||
> - If your issue has been addresed before (duplicated issue), please ask in the original issue
|
||||
> - If your issue is a Wi-Fi problem or MQTT problem, please try the steps provided in the FAQ and troubleshooting wiki articles
|
||||
> - If your issue is a flashing issue, please address it to the [Tasmota Support Chat](https://discord.gg/Ks2Kzd4)
|
||||
> - If your issue is compilation problem, please address it to the [Tasmota Support Chat](https://discord.gg/Ks2Kzd4)
|
||||
> - If your issue has been addressed before (i.e., duplicated issue), please ask in the original issue
|
||||
> - If your issue is a Wi-Fi problem or MQTT problem, please try the steps provided in the [FAQ](https://github.com/arendst/Sonoff-Tasmota/wiki/FAQ) and troubleshooting wiki articles
|
||||
>
|
||||
> Please take a few minutes to complete the requested information below. Our ability to provide assistance is greatly hampered without it. The details requested potentially affect which options to pursue. The small amount of time you spend completing the template will also help the volunteers providing the assistance to you to reduce the time required to help you.
|
||||
|
||||
|
@ -36,25 +36,27 @@ _Make sure these boxes are checked before submitting your issue. Thank you_
|
|||
- [ ] Tasmota binary firmware version number used: _____
|
||||
- [ ] Pre-compiled
|
||||
- [ ] Self-compiled
|
||||
- [ ] IDE / Compiler
|
||||
- [ ] IDE / Compiler used: _____
|
||||
- [ ] Flashing tools used: _____
|
||||
- [ ] Provide the output of command ``Backlog Template; Module; GPIO``:
|
||||
- [ ] Provide the output of this command: ``Backlog Template; Module; GPIO``:
|
||||
```
|
||||
Configuration output here:
|
||||
|
||||
|
||||
```
|
||||
- [ ] If using rules, provide the output of command ``Backlog Rule1; Rule2; Rule3``:
|
||||
- [ ] If using rules, provide the output of this command: ``Backlog Rule1; Rule2; Rule3``:
|
||||
```
|
||||
Rules output here:
|
||||
|
||||
|
||||
```
|
||||
- [ ] Provide the output of command ``Status 0``:
|
||||
- [ ] Provide the output of this command: ``Status 0``:
|
||||
```
|
||||
STATUS 0 output here:
|
||||
|
||||
|
||||
```
|
||||
- [ ] Provide the output of console when you experience your issue if applicable:
|
||||
- [ ] Provide the output of the Console log output when you experience your issue; if applicable:
|
||||
_(Please use_ ``weblog 4`` _for more debug information)_
|
||||
```
|
||||
Console output here:
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
.piolibdeps
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
||||
.cache
|
||||
sonoff/user_config_override.h
|
||||
build
|
||||
firmware.map
|
||||
|
|
183
RELEASENOTES.md
183
RELEASENOTES.md
|
@ -1,4 +1,4 @@
|
|||
<img src="/tools/logo/TASMOTA_FullLogo_Vector.svg" alt="Logo" align="right" height="76"/>
|
||||
<img src="https://github.com/arendst/Sonoff-Tasmota/blob/master/tools/logo/TASMOTA_FullLogo_Vector.svg" alt="Logo" align="right" height="76"/>
|
||||
|
||||
# RELEASE NOTES
|
||||
|
||||
|
@ -10,8 +10,13 @@ See [wiki migration path](https://github.com/arendst/Sonoff-Tasmota/wiki/Upgrade
|
|||
3. Migrate to **Sonoff-Tasmota 5.14**
|
||||
4. Migrate to **Sonoff-Tasmota 6.x**
|
||||
|
||||
## Core version 2.3.0 vs 2.4.2
|
||||
This release is based on ESP8266/Arduino library core 2.3.0 (again) as some people encountered wifi related issues on core 2.4.2. For others core 2.4.2 is working just fine. Both version are available from http://thehackbox.org/tasmota/release/
|
||||
## Support of TLS
|
||||
TLS support for core 2.3.0 is removed.
|
||||
|
||||
TLS is supported on core 2.4.2 and up. To save resources when TLS is enabled mDNS needs to be disabled. In addition to TLS using fingerprints now also user supplied CA certs and AWS IoT is supported. See full documentation on https://github.com/arendst/Sonoff-Tasmota/wiki/AWS-IoT
|
||||
|
||||
## Core version 2.3.0 vs 2.4.2 vs 2.5.2
|
||||
This release is based on ESP8266/Arduino library core 2.3.0 as some people encountered wifi related issues on core 2.4.2 and 2.5.2. For others core 2.4.2 or 2.5.2 is working just fine. All version are available from http://thehackbox.org/tasmota/release/
|
||||
|
||||
## Change in default initial configuration tool
|
||||
Firmware binary **sonoff-classic.bin** supports **WifiManager, Wps and SmartConfig** for initial configuration. The default tool is **Wps**.
|
||||
|
@ -92,6 +97,7 @@ Module | Description
|
|||
67 SP10 | Tuya SP10 Wifi Smart Switch with Energy Monitoring
|
||||
68 WAGA CHCZ02MB | WAGA life CHCZ02MB Wifi Smart Switch with Energy Monitoring
|
||||
69 SYF05 | Sunyesmart SYF05 RGBWW Wifi Led Bulb
|
||||
70 Sonoff L1 | Sonoff L1 light strip
|
||||
|
||||
## Provided Binary Downloads
|
||||
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.3.0**.
|
||||
|
@ -107,6 +113,8 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
|
|||
|
||||
Core version **2.4.2** binaries can be found at http://thehackbox.org/tasmota/release/020402/
|
||||
|
||||
Core version **2.5.2** binaries can be found at http://thehackbox.org/tasmota/release/020502/
|
||||
|
||||
## Available Features and Sensors
|
||||
|
||||
| Feature or Sensor | minimal | basic | classic | sonoff | knx | sensors | display | Remarks
|
||||
|
@ -118,9 +126,12 @@ Core version **2.4.2** binaries can be found at http://thehackbox.org/tasmota/re
|
|||
| USE_DOMOTICZ | - | - | x | x | x | x | - |
|
||||
| USE_HOME_ASSISTANT | - | - | - | x | x | x | - |
|
||||
| USE_MQTT_TLS | - | - | - | - | - | - | - |
|
||||
| USE_MQTT_TLS_CA_CERT | - | - | - | - | - | - | - |
|
||||
| USE_MQTT_AWS_IOT | - | - | - | - | - | - | - |
|
||||
| USE_KNX | - | - | - | - | x | - | - |
|
||||
| USE_WEBSERVER | x | x | x | x | x | x | x | WifiManager
|
||||
| USE_EMULATION | - | x | x | x | - | x | - |
|
||||
| USE_EMULATION_HUE | - | x | x | x | - | x | - |
|
||||
| USE_EMULATION_WEMO | - | x | x | x | - | x | - |
|
||||
| USE_DISCOVERY | - | - | x | x | x | x | x |
|
||||
| WEBSERVER_ADVERTISE | - | - | x | x | x | x | x |
|
||||
| MQTT_HOST_DISCOVERY | - | - | x | x | x | x | x |
|
||||
|
@ -128,12 +139,15 @@ Core version **2.4.2** binaries can be found at http://thehackbox.org/tasmota/re
|
|||
| USE_TIMERS_WEB | - | x | - | x | x | x | x |
|
||||
| USE_SUNRISE | - | x | - | x | x | x | x |
|
||||
| USE_RULES | - | x | - | x | x | x | x |
|
||||
| USE_SCRIPT | - | - | - | - | - | - | - |
|
||||
| USE_EXPRESSION | - | - | - | - | - | - | - |
|
||||
| | | | | | | | |
|
||||
| USE_ADC_VCC | x | x | x | x | x | - | x |
|
||||
| USE_ADC_VCC | x | x | x | - | - | - | - |
|
||||
| USE_COUNTER | - | - | - | x | x | x | x |
|
||||
| USE_DS18B20 | - | - | - | - | - | - | - | Single sensor
|
||||
| USE_DS18x20 | - | - | x | x | x | x | x | Multiple sensors
|
||||
| USE_DS18x20_LEGACY | - | - | - | - | - | - | - | Multiple sensors
|
||||
| USE_DHT | - | - | x | x | x | x | x |
|
||||
| | | | | | | | |
|
||||
| Feature or Sensor | minimal | basic | classic | sonoff | knx | sensors | display | Remarks
|
||||
| USE_I2C | - | - | - | x | x | x | x |
|
||||
|
@ -162,6 +176,10 @@ Core version **2.4.2** binaries can be found at http://thehackbox.org/tasmota/re
|
|||
| USE_MGC3130 | - | - | - | - | - | - | - |
|
||||
| USE_MAX44009 | - | - | - | - | - | - | - |
|
||||
| USE_SCD30 | - | - | - | - | - | x | - |
|
||||
| USE_SPS30 | - | - | - | - | - | - | - |
|
||||
| USE_ADE7953 | - | - | - | x | x | x | x |
|
||||
| USE_VL53L0X | - | - | - | - | - | - | - |
|
||||
| USE_MLX90614 | - | - | - | - | - | - | - |
|
||||
| | | | | | | | |
|
||||
| Feature or Sensor | minimal | basic | classic | sonoff | knx | sensors | display | Remarks
|
||||
| USE_SPI | - | - | - | - | - | - | x |
|
||||
|
@ -206,81 +224,80 @@ Core version **2.4.2** binaries can be found at http://thehackbox.org/tasmota/re
|
|||
| USE_DISPLAY_EPAPER_29 | - | - | - | - | - | - | x | Disabled for core 2.3.0
|
||||
|
||||
## Changelog
|
||||
Version 6.5.0 20190319
|
||||
* Remove commands SetOption14 and SetOption63 as it has been superseded by command Interlock
|
||||
* Remove command SetOption35 0-255 for mDNS start-up delay (#4793)
|
||||
* Remove support for MQTT_LIBRARY_TYPE, MQTT_ARDUINOMQTT and MQTT_TASMOTAMQTT (#5474)
|
||||
* Change webserver content handling from single String to small Chunks increasing RAM
|
||||
* Change code use of boolean to bool and byte to uint8_t
|
||||
* Change code uint8_t flags to bool flags
|
||||
* Change sonoff_template.h layout regarding optional module flags like ADC0
|
||||
* Change sonoff_template.h module lay-out by removing non-configurable GPIOs
|
||||
* Change button driver making it modular
|
||||
* Change switch driver making it modular and introduce input filter (#4665, #4724)
|
||||
* Change switch input detection by optimizing switch debounce (#4724)
|
||||
* Change web authentication (#4865)
|
||||
* Change image name BE_MINIMAL to FIRMWARE_MINIMAL and USE_xyz to FIRMWARE_xyz (#5106)
|
||||
* Change GUI weblog from XML to plain text solving possible empty screens (#5154)
|
||||
* Fix most compiler warnings
|
||||
* Fix Display exception 28 when JSON value is NULL received
|
||||
* Fix epaper driver (#4785)
|
||||
* Fix HAss Sensor Discovery Software Watchdog restart (#4831, #4988)
|
||||
* Fix allowable MAX_RULE_VARS to 16 (#4933)
|
||||
* Fix mDNS addService (#4938, #4951)
|
||||
* Fix HAss discovery of MHZ19(B) sensors (#4992)
|
||||
* Fix some exceptions and watchdogs due to lack of stack space (#5215)
|
||||
* Fix GUI wifi password acception starting with asteriks (*) (#5231, #5242)
|
||||
* Fix command WebSend intermittent results (#5273, #5304)
|
||||
* Fix additional characters in fallbacktopic, hostname and mqttclient on core 2.5.0 (#5359, #5417)
|
||||
* Fix Energy TotalStartTime when commands EnergyReset0 and/or EnergyReset3 used (#5373)
|
||||
* Fix DS18S20 temperature calculation (#5375)
|
||||
* Fix float calculations in range from 0 to -1 (#5386)
|
||||
* Fix exception on GUI Configure Logging and Configure Other (#5424)
|
||||
* Add commands PowerCal, VoltageCal and CurrentCal for HLW8012, HJL01 and BL0937 based energy sensors
|
||||
* Add command SerialDelimiter 128 to filter reception of only characters between ASCII 32 and 127 (#5131)
|
||||
* Add command SSerialSend5 \<hexdata\> to SerialBridge
|
||||
* Add command Interlock 0 / 1 / 1,2 3,4 .. to control interlock ON/OFF and add up to 8 relays in 1 to 4 interlock groups (#4910, #5014)
|
||||
* Add command Template 255 to copy module configuration over to current active template and store as user template named Merged (#5371)
|
||||
* Add command WifiConfig 7 to allow reset of device in AP mode without admin password (#5297)
|
||||
* Add command SetOption36 to control boot loop default restoration (#4645, #5063)
|
||||
* Add command SetOption37 for RGBCW color mapping (#5326)
|
||||
* Add command SetOption55 0/1 and define MDNS_ENABLE to disable/enable mDNS (#4793, #4923)
|
||||
* Add command SetOption62 0/1 to disable retain on Button or Switch hold messages (#5299)
|
||||
* Add support for Smanergy KA10 Smart Wall Socket with Energy monitoring
|
||||
* Add support for commands in sensor drivers
|
||||
* Add support for MAX31855 K-Type thermocouple sensor using softSPI (#4764)
|
||||
* Add support for Near Field Communication (NFC) controller PN532 using Serial (#4791, #5162)
|
||||
* Add support for OBI Power Socket 2 (#4829)
|
||||
* Add support for YTF IR Bridge (#4855)
|
||||
* Add support for Mi LED Desk Lamp with rotary switch (#4887)
|
||||
* Add support for Digoo DG-SP202 Smart Socket with Energy monitoring (#4891)
|
||||
* Add support for MAX44009 Ambient Light sensor (#4907)
|
||||
* Add support for inverted buttons and inverted buttons without pullup (#4914)
|
||||
* Add support for Luminea ZX2820 Smart Socket with Energy monitoring (#4921)
|
||||
* Add support for multiple ADS1115 I2C devices (#5083)
|
||||
* Add support for online template change using command Template or GUI Configure Other (#5177)
|
||||
* Add support for Korean language translations (#5344)
|
||||
* Add support for sensor SCD30 (#5434)
|
||||
* Add parameter CFG_HOLDER to status 1 message (#5206)
|
||||
* Add SetOption32 until SetOption49 diagnostic information to Status 3 report as replacement for second property value in SetOption property name
|
||||
* Add Resolution property to Status 3 report providing previous SetOption second value property
|
||||
* Add property MqttCount to status 6 message representing number of Mqtt re-connections
|
||||
* Add property LinkCount to state and status 11 message representing number of Wifi Link re-connections
|
||||
* Add property Downtime to state and status 11 message representing the duration of wifi connection loss
|
||||
* Add variable %timestamp% to rules (#4749)
|
||||
* Add rule support for "==", "!=" ">=" and "<=" (#5122)
|
||||
* Add rule expression enabled by define USE_EXPRESSION in my_user_config.h (#5210)
|
||||
* Add Power status functionality to LED2 when configured leaving LED1 for Link status indication
|
||||
* Add user configuration of HLW8012 and HJL-01/BL0937 Energy Monitoring as used in Sonoff Pow and many Tuya based devices
|
||||
* Add user configuration of MCP39F501 Energy Monitoring as used in Shelly2
|
||||
* Add online template configuration using both commands and Configure Template menu option in GUI
|
||||
* Add (S)SerialSend3 escape sequence \x to allow hexadecimal byte value (#3560, #4947)
|
||||
* Add define DS18B20_INTERNAL_PULLUP to select internal input pullup when only one DS18B20 sensor is connected eliminating external resistor (#4738)
|
||||
* Add button control when no relay configured (#4682)
|
||||
* Add startup delay of 4 seconds to button control (#4829)
|
||||
* Add core version conditional compile options to provided PWM files (#4917)
|
||||
* Add resiliency to saved Settings (#5065)
|
||||
* Add MHZ19 Temperature as Domoticz Temperature selection (#5128)
|
||||
* Add HAss status sensor (#5139)
|
||||
* Add status message to former declined group commands (#5145)
|
||||
* Add 0x to IRRemote (SetOption29) and RCSwitch (SetOption28) received hexadecimal data (#5431)
|
||||
Version 6.6.0 20190707
|
||||
* Remove support of TLS on core 2.3.0 and extent support on core 2.4.2 and up
|
||||
* Remove MQTT uptime message every hour
|
||||
* Refactor some defines to const
|
||||
* Refactor webserver HTML input, button, textarea, and select name based on id
|
||||
* Refactor webserver sensor data collection
|
||||
* Refactor TLS based on BearSSL, warning breaking change for fingerprints validation
|
||||
* Refactor management of lights, using classes and integers instead of floats
|
||||
* Refactor UDP initial message handling from string to char using static memory and add debug info (#5505)
|
||||
* Refactor ``IRsend`` and receive for 64-bit support (#5523)
|
||||
* Refactor MQTT which might solve issue (#5755)
|
||||
* Refactor ``IRSend`` by using heap when more than 199 values need to be send. May need increase of define MQTT_MAX_PACKET_SIZE too (#5950)
|
||||
* Refactor double to float in rules, and replaced trigonometric functions from stdlib with smaller versions (#6005)
|
||||
* Change pubsubclient MQTT_KEEPALIVE from 10 to 30 seconds for AWS IoT support
|
||||
* Change gamma correction as default behavior, ie "Ledtable 1"
|
||||
* Change PWM resolution from 8 to 10 bits for low brightness lights
|
||||
* Change ``IRSend`` Panasonic protocol to 64-bit (#5523)
|
||||
* Change ADC0 to enabled by default in my_user_config.h (#5671)
|
||||
* Change define USE_EMULATION by USE_EMULATION_HUE and USE_EMULATION_WEMO (#5826)
|
||||
* Change default ``PowerDelta`` from 80% to 0% on new installations (#5858, #5028, #4813, #4130, #4145, #3795, #3778, #3660, #3648)
|
||||
* Fix display Bug in KNX webmenu for Physical Address
|
||||
* Fix the Unescape() function and the ``SendSerial3`` behaviour
|
||||
* Fix webserver multiple Javascript window.onload functionality
|
||||
* Fix TasmotaSerial at 9600 bps solving DFPlayer comms (#5528)
|
||||
* Fix Configure Timer Web GUI (#5568)
|
||||
* Fix Shelly 2.5 I2C address priority issue when VEML6070 code is present by disabling VEML6070 for Shelly 2.5 (#5592)
|
||||
* Fix use of ``SerialDelimiter`` value 128 (#5634)
|
||||
* Fix Sonoff Pow R2 / S31 invalid energy increments (#5789)
|
||||
* Fix core 2.5.x ISR not in IRAM exception (#5837)
|
||||
* Fix Philips Hue emulation Alexa issue by using part of MAC address for LightId (#5849)
|
||||
* Fix missing white channel for WS2812 (#5869)
|
||||
* Fix PZem startup issue (#5875)
|
||||
* Fix exception 9 when syslog is enabled and NTP is just synced (#5917)
|
||||
* Fix Toggle functionality to button double press when one button and two devices are detected (#5935)
|
||||
* Fix command ``Channel`` for dual dimmers (#5940)
|
||||
* Fix not restoring white value on power off/power on (#5993)
|
||||
* Add command ``AdcParam`` to control ADC0 Temperature and Light formula parameters
|
||||
* Add command ``LedMask`` to assign which relay has access to power LED (#5602, #5612)
|
||||
* Add extended LED power control using command ``LedPowerX`` where X is 1 to 4. Enabled when "LedLink(i)" is configured too (#5709)
|
||||
* Add command ``Sensor20 1..255`` to change Nova Fitness SDS01 working period in minutes (#5452)
|
||||
* Add command ``SetOption38 6..255`` to set IRReceive protocol detection sensitivity mimizing UNKNOWN protocols (#5853)
|
||||
* Add command ``SetOption39 1..255`` to control CSE7766 (Pow R2) or HLW8032 (Blitzwolf SHP5) handling of power loads below 6W. Default setting is 128 (#5756)
|
||||
* Add command ``SetOption40 0..250`` to disable button functionality if activated for over 0.1 second. Needs SetOption1 1 and SetOption13 0 (#5449)
|
||||
* Add command ``SetOption63 0/1`` to disable relay state feedback scan at restart (#5594, #5663)
|
||||
* Add command ``SetOption64 0/1`` to switch between "-" or "_" as sensor index separator impacting DS18X20, DHT, BMP and SHT3X sensor names (#5689)
|
||||
* Add command ``SetOption65 0/1`` and more Tuya Serial based device support (#5815)
|
||||
* Add command ``WebColor`` to change GUI colors on the fly
|
||||
* Add support for AWS IoT with TLS 1.2 on core 2.4.2 and up. Full doc here: https://github.com/arendst/Sonoff-Tasmota/wiki/AWS-IoT
|
||||
* Add support for Badger HR-E Water Meter (#5539)
|
||||
* Add support for Shelly 2.5 Energy and overtemp Monitoring (#5592)
|
||||
* Add support for color and colortone for Philips Hue emulation via Alexa (#5600 #4809)
|
||||
* Add support for Scripts as replacement for Rules. Default disabled but can be enabled in my_user_config.h (#5689)
|
||||
* Add support for up to four LEDs related to four power outputs. Enabled when "LedLink(i)" is configured too (#5709)
|
||||
* Add support for Shelly 1PM Template ``{"NAME":"Shelly 1PM","GPIO":[56,0,0,0,82,134,0,0,0,0,0,21,0],"FLAG":2,"BASE":18}`` (#5716)
|
||||
* Add support for SPS30 Particle sensor thanks to Gerhard Mutz (#5830)
|
||||
* Add support for VL53L0x time of flight sensor. Might interfere with TSL2561 using same I2C address (#5845)
|
||||
* Add support for Sonoff L1 thanks to reef-actor (#6002)
|
||||
* Add rule Http#Initialized
|
||||
* Add rule System#Save executed just before a planned restart
|
||||
* Add rule support for single JSON value pair like {"SSerialReceived":"on"} by expanding it to {"SSerialReceived":{"Data":"on"}} allowing for trigger SSerialReceived#Data=on (#5638)
|
||||
* Add define USE_COUNTER to my_user_config.h to save space in sonoff-basic.bin and sonoff-minimal.bin
|
||||
* Add define USE_DHT to my_user_config.h to save space in sonoff-basic.bin
|
||||
* Add defines USE_EMULATION_WEMO and USE_EMULATION_HUE to my_user_config.h to control emulation features at compile time (#5826)
|
||||
* Add Toggle functionality to button double press when more devices are detected
|
||||
* Add device OverTemp (>73 Celsius) detection to Energy Monitoring devices with temperature sensor powering off all outputs
|
||||
* Add Tuya Dimmer 10 second heartbeat serial packet required by some Tuya dimmer secondary MCUs
|
||||
* Add all temperature, humidity and pressure for global access
|
||||
* Add validation check when loading settings from flash
|
||||
* Add HX711 weight restore after controlled restart or after power restore just before executing command Sensor34 7 (#5367, #5786)
|
||||
* Add GUI hexadecimal color options in my_user_config.h (#5586)
|
||||
* Add alternative ``IRSend`` command syntax ``IRSend raw,<freq>,<header mark>,<header space>,<bit mark>,<zero space>,<one space>,<bit stream>`` (#5610)
|
||||
* Add user configurable ADC0 to Module and Template configuration compatible with current FLAG options (#5671)
|
||||
* Add AriLux RF control GPIO option "ALux IrSel" (159) replacing "Led4i" (59) for full LED control (#5709)
|
||||
* Add LED GPIO option "LedLink" (157) and "LedLinki" (158) to select dedicated link status LED (#5709)
|
||||
* Add all 5 PWM channels individually adressable with LEDs. (#5741)
|
||||
* Add reset of Energy values when connection to sensor is lost for over 4 seconds (#5874, #5881)
|
||||
* Add checkbox to GUI password field enabling visibility during password entry only (#5934)
|
|
@ -10,6 +10,7 @@
|
|||
[platformio]
|
||||
src_dir = sonoff
|
||||
build_dir = .pioenvs
|
||||
build_cache_dir = .cache
|
||||
|
||||
; *** Uncomment one of the lines below to build/upload only one environment
|
||||
;default_envs = sonoff
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
/*********************************************************************************************\
|
||||
* 6.6.0.14 20190925
|
||||
* Change command Tariffx to allow time entries like 23 (hours), 1320 (minutes) or 23:00. NOTE: As this is development branch previous tariffs are lost! (#6488)
|
||||
* Remove support for define USE_DS18x20_LEGACY and legacy DS18x20 driver (#6486)
|
||||
* Add initial support for MQTT logging using command MqttLog <loglevel> (#6498)
|
||||
* Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command
|
||||
* Add initial support for shutters by Stefan Bode (#288)
|
||||
*
|
||||
* 6.6.0.13 20190922
|
||||
* Add command EnergyReset4 x,x to initialize total usage for two tarrifs
|
||||
* Add command EnergyReset5 x,x to initialize total export (or production) for two tarrifs
|
||||
* Add command Sensor34 8,0 and Sensor34 8,1 to disable/enable JSON message on weight change over 4 gram
|
||||
* Add JSON array index support to rules evaluation allowing trigger on ENERGY#POWER[2]>0.60 from JSON ..,"Power":[0.00,0.68],.. (#6160)
|
||||
*
|
||||
* 6.6.0.12 20190910
|
||||
* Redesign command Tariff to now default to 0 (=disabled) and allowing to set both Standard Time (ST) and Daylight Savings Time (DST) start hour
|
||||
* Commands Tariff1 22,23 = Tariff1 (Off-Peak) ST,DST Tariff2 (Standard) 6,7 = Tariff2 ST,DST Tariff9 0/1 = Weekend toggle (1 = Off-Peak during weekend)
|
||||
|
|
|
@ -175,6 +175,8 @@
|
|||
#define D_JSON_PV2_CURRENT "Pv2Current"
|
||||
#define D_JSON_PV2_POWER "Pv2Power"
|
||||
#define D_JSON_SOLAR_POWER "SolarPower"
|
||||
#define D_JSON_USAGE "Usage"
|
||||
#define D_JSON_EXPORT "Export"
|
||||
|
||||
#define D_RSLT_ENERGY "ENERGY"
|
||||
#define D_RSLT_HASS_STATE "HASS_STATE"
|
||||
|
@ -289,6 +291,7 @@
|
|||
#define D_JSON_BASE "BASE"
|
||||
|
||||
// Commands xdrv_01_mqtt.ino
|
||||
#define D_CMND_MQTTLOG "MqttLog"
|
||||
#define D_CMND_MQTTHOST "MqttHost"
|
||||
#define D_CMND_MQTTPORT "MqttPort"
|
||||
#define D_CMND_MQTTRETRY "MqttRetry"
|
||||
|
@ -453,6 +456,7 @@
|
|||
|
||||
// Commands xdrv_23_zigbee.ino
|
||||
#define D_CMND_ZIGBEE_PERMITJOIN "ZigbeePermitJoin"
|
||||
#define D_CMND_ZIGBEE_DUMP "ZigbeeDump"
|
||||
#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend"
|
||||
#define D_JSON_ZIGBEE_STATUS "ZigbeeStatus"
|
||||
#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Използвана енергия вчера"
|
||||
#define D_ENERGY_TOTAL "Използвана енергия общо"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Датчикът DS18x20 е зает"
|
||||
#define D_SENSOR_CRC_ERROR "Датчик DS18x20 - грешка CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Spotřeba Včera"
|
||||
#define D_ENERGY_TOTAL "Celková spotřeba"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor DS18x20 obsazen"
|
||||
#define D_SENSOR_CRC_ERROR "Sensor DS18x20 chyba CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Energie gestern"
|
||||
#define D_ENERGY_TOTAL "Energie insgesamt"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor beschäftigt"
|
||||
#define D_SENSOR_CRC_ERROR "Sensor CRC-Fehler"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Ενέργεια χθες"
|
||||
#define D_ENERGY_TOTAL "Ενέργεια συνολικά"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Ο αισθητήρας είναι απασχολημένος"
|
||||
#define D_SENSOR_CRC_ERROR "Σφάλμα CRC αισθητήρα"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Energy Yesterday"
|
||||
#define D_ENERGY_TOTAL "Energy Total"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor busy"
|
||||
#define D_SENSOR_CRC_ERROR "Sensor CRC error"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Energía Ayer"
|
||||
#define D_ENERGY_TOTAL "Energía Total"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor ocupado"
|
||||
#define D_SENSOR_CRC_ERROR "Error CRC del Sensor"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Énergie hier"
|
||||
#define D_ENERGY_TOTAL "Énergie totale"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Capteur occupé"
|
||||
#define D_SENSOR_CRC_ERROR "Erreur CRC capteur"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "צריכה בעבר"
|
||||
#define D_ENERGY_TOTAL "צריכה כללית"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "שרת עסוק"
|
||||
#define D_SENSOR_CRC_ERROR "בחיישן CRC שגיאת"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Tegnapi energia"
|
||||
#define D_ENERGY_TOTAL "Összes energia"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Szenzor foglalt"
|
||||
#define D_SENSOR_CRC_ERROR "Szenzor CRC hiba"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Energia Ieri"
|
||||
#define D_ENERGY_TOTAL "Energia Totale"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensore occupato"
|
||||
#define D_SENSOR_CRC_ERROR "Sensore errore CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "어제 전력 사용량"
|
||||
#define D_ENERGY_TOTAL "총 전력 사용량"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "센서가 사용 중"
|
||||
#define D_SENSOR_CRC_ERROR "센서 CRC 에러"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Verbruik gisteren"
|
||||
#define D_ENERGY_TOTAL "Verbruik totaal"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor bezet"
|
||||
#define D_SENSOR_CRC_ERROR "Sensor CRC fout"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Energia Wczoraj"
|
||||
#define D_ENERGY_TOTAL "Energia suma"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Czujnik DS18x20 zajęty"
|
||||
#define D_SENSOR_CRC_ERROR "Czujnik DS18x20 błąd CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Consumo energético de ontem"
|
||||
#define D_ENERGY_TOTAL "Consumo total de energia"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor ocupado"
|
||||
#define D_SENSOR_CRC_ERROR "Erro sensor CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Consumo energético de ontem"
|
||||
#define D_ENERGY_TOTAL "Consumo total de energial"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor ocupado"
|
||||
#define D_SENSOR_CRC_ERROR "Erro Sensor CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Энергия Вчера"
|
||||
#define D_ENERGY_TOTAL "Энергия Всего"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Датчик DS18x20 занят"
|
||||
#define D_SENSOR_CRC_ERROR "Датчик DS18x20 - ошибка CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Spotreba včera"
|
||||
#define D_ENERGY_TOTAL "Celková spotreba"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor DS18x20 obsadený"
|
||||
#define D_SENSOR_CRC_ERROR "Sensor DS18x20 chyba CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Energi igår"
|
||||
#define D_ENERGY_TOTAL "Energi totalt"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensor upptagen"
|
||||
#define D_SENSOR_CRC_ERROR "Sensor CRC-fel"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Energy Yesterday"
|
||||
#define D_ENERGY_TOTAL "Energy Total"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Sensör başgül"
|
||||
#define D_SENSOR_CRC_ERROR "Sensor CRC hatası"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "Енергія Вчора"
|
||||
#define D_ENERGY_TOTAL "Енергія Всього"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "Датчик DS18x20 зайнятий"
|
||||
#define D_SENSOR_CRC_ERROR "Датчик DS18x20 - помилка CRC"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "昨日用电量"
|
||||
#define D_ENERGY_TOTAL "总用电量"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "传感器正忙"
|
||||
#define D_SENSOR_CRC_ERROR "传感器 CRC 校验错误"
|
||||
|
|
|
@ -442,6 +442,11 @@
|
|||
#define D_ENERGY_YESTERDAY "昨日用電量"
|
||||
#define D_ENERGY_TOTAL "總用電量"
|
||||
|
||||
// xdrv_27_shutter.ino
|
||||
#define D_OPEN "Open"
|
||||
#define D_CLOSE "Close"
|
||||
#define D_DOMOTICZ_SHUTTER "Shutter"
|
||||
|
||||
// xsns_05_ds18b20.ino
|
||||
#define D_SENSOR_BUSY "傳感器正忙"
|
||||
#define D_SENSOR_CRC_ERROR "傳感器 CRC 校驗錯誤"
|
||||
|
|
|
@ -313,6 +313,7 @@
|
|||
#define USE_ARMTRONIX_DIMMERS // Add support for Armtronix Dimmers (+1k4 code)
|
||||
#define USE_PS_16_DZ // Add support for PS-16-DZ Dimmer and Sonoff L1 (+2k code)
|
||||
//#define ROTARY_V1 // Add support for MI Desk Lamp
|
||||
//#define USE_SHUTTER // Add Shutter support for up to 4 shutter with different motortypes (+6k code)
|
||||
|
||||
// -- Counter input -------------------------------
|
||||
#define USE_COUNTER // Enable inputs as counter (+0k8 code)
|
||||
|
@ -321,11 +322,9 @@
|
|||
//#define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices
|
||||
|
||||
// -- One wire sensors ----------------------------
|
||||
// WARNING: Select none for default one DS18B20 sensor or enable one of the following two options for multiple sensors
|
||||
//#define USE_DS18x20_LEGACY // Optional for more than one DS18x20 sensors with dynamic scan using library OneWire (+1k5 code)
|
||||
#define USE_DS18x20 // Optional for more than one DS18x20 sensors with id sort, single scan and read retry (+1k3 code)
|
||||
// #define W1_PARASITE_POWER // If using USE_DS18x20 then optimize for parasite powered sensors
|
||||
// #define DS18B20_INTERNAL_PULLUP // Use INPUT_PULLUP internal pullup resistors for single DS18B20
|
||||
// #define W1_PARASITE_POWER // Optimize for parasite powered sensors
|
||||
// #define DS18B20_INTERNAL_PULLUP // Use INPUT_PULLUP internal pullup resistor
|
||||
|
||||
// -- I2C sensors ---------------------------------
|
||||
#define USE_I2C // I2C using library wire (+10k code, 0k2 mem, 124 iram)
|
||||
|
@ -498,10 +497,10 @@
|
|||
#define IR_RCV_MIN_UNKNOWN_SIZE 6 // Set the smallest sized "UNKNOWN" message packets we actually care about (default 6, max 255)
|
||||
|
||||
// -- Zigbee interface ----------------------------
|
||||
//#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP
|
||||
#define USE_ZIGBEE_PANID 0x1A63 // arbitrary PAN ID for Zigbee network, must be unique in the home
|
||||
//#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP
|
||||
#define USE_ZIGBEE_PANID 0x1A63 // arbitrary PAN ID for Zigbee network, must be unique in the home
|
||||
#define USE_ZIGBEE_EXTPANID 0xCCCCCCCCCCCCCCCCL // arbitrary extended PAN ID
|
||||
#define USE_ZIGBEE_CHANNEL 0x00000800 // Zigbee Channel (11)
|
||||
#define USE_ZIGBEE_CHANNEL 11 // Zigbee Channel (11-26)
|
||||
#define USE_ZIGBEE_PRECFGKEY_L 0x0F0D0B0907050301L // note: changing requires to re-pair all devices
|
||||
#define USE_ZIGBEE_PRECFGKEY_H 0x0D0C0A0806040200L // note: changing requires to re-pair all devices
|
||||
#define USE_ZIGBEE_PERMIT_JOIN false // don't allow joining by default
|
||||
|
|
|
@ -93,7 +93,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
|
|||
uint32_t spare27 : 1;
|
||||
uint32_t spare28 : 1;
|
||||
uint32_t spare29 : 1;
|
||||
uint32_t spare30 : 1;
|
||||
uint32_t shutter_mode : 1; // bit 30 (v6.6.0.15) - SetOption80 - Enable shutter support
|
||||
uint32_t spare31 : 1;
|
||||
};
|
||||
} SysBitfield3;
|
||||
|
@ -171,18 +171,18 @@ typedef union {
|
|||
uint8_t spare3 : 1;
|
||||
uint8_t spare4 : 1;
|
||||
uint8_t spare5 : 1;
|
||||
uint8_t spare6 : 1;
|
||||
uint8_t hx711_json_weight_change : 1; // Sensor34 8,x - Enable JSON message on weight change
|
||||
uint8_t mhz19b_abc_disable : 1; // Disable ABC (Automatic Baseline Correction for MHZ19(B) (0 = Enabled (default), 1 = Disabled with Sensor15 command)
|
||||
};
|
||||
} SensorCfg1;
|
||||
|
||||
typedef struct {
|
||||
uint32_t usage1_kWhtotal;
|
||||
uint32_t usage1_kWhtoday;
|
||||
uint32_t usage2_kWhtotal;
|
||||
uint32_t return1_kWhtotal;
|
||||
uint32_t return2_kWhtotal;
|
||||
uint32_t last_return_kWhtotal;
|
||||
uint32_t free;
|
||||
uint32_t last_usage_kWhtotal;
|
||||
} EnergyUsage;
|
||||
|
||||
|
||||
|
@ -227,7 +227,9 @@ struct SYSCFG {
|
|||
uint8_t weblog_level; // 1AC
|
||||
uint8_t mqtt_fingerprint[2][20]; // 1AD
|
||||
uint8_t adc_param_type; // 1D5
|
||||
uint8_t register8[18]; // 1D6 - 18 x 8-bit registers indexed by enum SettingsRegister8
|
||||
uint8_t register8[16]; // 1D6 - 16 x 8-bit registers indexed by enum SettingsRegister8
|
||||
uint8_t shutter_accuracy; // 1E6
|
||||
uint8_t mqttlog_level; // 1E7
|
||||
uint8_t sps30_inuse_hours; // 1E8
|
||||
char mqtt_host[33]; // 1E9 - Keep together with below as being copied as one chunck with reset 6
|
||||
uint16_t mqtt_port; // 20A - Keep together
|
||||
|
@ -373,8 +375,17 @@ struct SYSCFG {
|
|||
TuyaFnidDpidMap tuya_fnid_map[MAX_TUYA_FUNCTIONS]; // E00 32 bytes
|
||||
uint16_t ina226_r_shunt[4]; // E20
|
||||
uint16_t ina226_i_fs[4]; // E28
|
||||
uint16_t tariff[4][2]; // E30
|
||||
|
||||
uint8_t free_e30[456]; // E30
|
||||
uint16_t shutter_opentime[MAX_SHUTTERS]; // E40
|
||||
uint16_t shutter_closetime[MAX_SHUTTERS]; // E48
|
||||
int16_t shuttercoeff[5][MAX_SHUTTERS]; // E50
|
||||
uint8_t shutter_invert[MAX_SHUTTERS]; // E78
|
||||
uint8_t shutter_set50percent[MAX_SHUTTERS]; // E7C
|
||||
uint8_t shutter_position[MAX_SHUTTERS]; // E80
|
||||
uint8_t shutter_startrelay[MAX_SHUTTERS]; // E84
|
||||
|
||||
uint8_t free_e88[368]; // E88
|
||||
|
||||
uint32_t cfg_timestamp; // FF8
|
||||
uint32_t cfg_crc32; // FFC
|
||||
|
@ -425,7 +436,11 @@ struct XDRVMAILBOX {
|
|||
char *command;
|
||||
} XdrvMailbox;
|
||||
|
||||
#ifdef USE_SHUTTER
|
||||
const uint8_t MAX_RULES_FLAG = 10; // Number of bits used in RulesBitfield (tricky I know...)
|
||||
#else
|
||||
const uint8_t MAX_RULES_FLAG = 8; // Number of bits used in RulesBitfield (tricky I know...)
|
||||
#endif // USE_SHUTTER
|
||||
typedef union { // Restricted by MISRA-C Rule 18.4 but so useful...
|
||||
uint16_t data; // Allow bit manipulation
|
||||
struct {
|
||||
|
@ -437,8 +452,8 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
|
|||
uint16_t wifi_connected : 1;
|
||||
uint16_t wifi_disconnected : 1;
|
||||
uint16_t http_init : 1;
|
||||
uint16_t spare08 : 1;
|
||||
uint16_t spare09 : 1;
|
||||
uint16_t shutter_moved : 1;
|
||||
uint16_t shutter_moving : 1;
|
||||
uint16_t spare10 : 1;
|
||||
uint16_t spare11 : 1;
|
||||
uint16_t spare12 : 1;
|
||||
|
|
|
@ -67,6 +67,7 @@ const uint8_t MAX_XNRG_DRIVERS = 32; // Max number of allowed energy driv
|
|||
const uint8_t MAX_XDSP_DRIVERS = 32; // Max number of allowed display drivers
|
||||
const uint8_t MAX_XDRV_DRIVERS = 96; // Max number of allowed driver drivers
|
||||
const uint8_t MAX_XSNS_DRIVERS = 96; // Max number of allowed sensor drivers
|
||||
const uint8_t MAX_SHUTTERS = 4; // Max number of shutters
|
||||
const uint8_t MAX_RULE_MEMS = 5; // Max number of saved vars
|
||||
const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters
|
||||
const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules
|
||||
|
@ -113,7 +114,7 @@ const uint16_t SERIALLOG_TIMER = 600; // Seconds to disable SerialLog
|
|||
const uint8_t OTA_ATTEMPTS = 5; // Number of times to try fetching the new firmware
|
||||
|
||||
const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in (serial and http) command buffer
|
||||
const uint16_t FLOATSZ = 33; // Max number of characters in float result from dtostrfd
|
||||
const uint16_t FLOATSZ = 16; // Max number of characters in float result from dtostrfd (max 32)
|
||||
const uint16_t CMDSZ = 24; // Max number of characters in command
|
||||
const uint16_t TOPSZ = 100; // Max number of characters in topic string
|
||||
const uint16_t LOGSZ = 520; // Max number of characters in log
|
||||
|
@ -252,13 +253,13 @@ enum SettingsParamIndex { P_HOLD_TIME, P_MAX_POWER_RETRY, P_ex_TUYA_DIMMER_ID, P
|
|||
P_ex_ENERGY_TARIFF1, P_ex_ENERGY_TARIFF2, // SetOption47 .. SetOption48
|
||||
P_MAX_PARAM8 }; // Max is PARAM8_SIZE (18) - SetOption32 until SetOption49
|
||||
|
||||
enum SettingsRegister8 { R8_ENERGY_TARIFF1_ST, R8_ENERGY_TARIFF2_ST, R8_ENERGY_TARIFF1_DS, R8_ENERGY_TARIFF2_DS,
|
||||
enum SettingsRegister8 { R8_SPARE00, R8_SPARE01, R8_SPARE02, R8_SPARE03,
|
||||
R8_SPARE04, R8_SPARE05, R8_SPARE06, R8_SPARE07,
|
||||
R8_SPARE08, R8_SPARE09, R8_SPARE10, R8_SPARE11,
|
||||
R8_SPARE12, R8_SPARE13, R8_SPARE14, R8_SPARE15,
|
||||
R8_SPARE16, R8_SPARE17 }; // Max size is 18 (Settings.register8[])
|
||||
R8_SPARE12, R8_SPARE13, R8_SPARE14, R8_SPARE15 }; // Max size is 16 (Settings.register8[])
|
||||
|
||||
enum DomoticzSensors {DZ_TEMP, DZ_TEMP_HUM, DZ_TEMP_HUM_BARO, DZ_POWER_ENERGY, DZ_ILLUMINANCE, DZ_COUNT, DZ_VOLTAGE, DZ_CURRENT, DZ_AIRQUALITY, DZ_P1_SMART_METER, DZ_MAX_SENSORS};
|
||||
enum DomoticzSensors {DZ_TEMP, DZ_TEMP_HUM, DZ_TEMP_HUM_BARO, DZ_POWER_ENERGY, DZ_ILLUMINANCE, DZ_COUNT, DZ_VOLTAGE, DZ_CURRENT,
|
||||
DZ_AIRQUALITY, DZ_P1_SMART_METER, DZ_SHUTTER, DZ_MAX_SENSORS};
|
||||
|
||||
enum Ws2812ClockIndex { WS_SECOND, WS_MINUTE, WS_HOUR, WS_MARKER };
|
||||
enum Ws2812Color { WS_RED, WS_GREEN, WS_BLUE };
|
||||
|
@ -282,8 +283,10 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FU
|
|||
enum AddressConfigSteps { ADDR_IDLE, ADDR_RECEIVE, ADDR_SEND };
|
||||
|
||||
enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,
|
||||
SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_MAX };
|
||||
const char kCommandSource[] PROGMEM = "I|MQTT|Restart|Button|Switch|Backlog|Serial|WebGui|WebCommand|WebConsole|PulseTimer|Timer|Rule|MaxPower|MaxEnergy|Overtemp|Light|Knx|Display|Wemo|Hue|Retry|Remote";
|
||||
SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER,
|
||||
SRC_MAX };
|
||||
const char kCommandSource[] PROGMEM = "I|MQTT|Restart|Button|Switch|Backlog|Serial|WebGui|WebCommand|WebConsole|PulseTimer|"
|
||||
"Timer|Rule|MaxPower|MaxEnergy|Overtemp|Light|Knx|Display|Wemo|Hue|Retry|Remote|Shutter";
|
||||
|
||||
const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 };
|
||||
|
||||
|
|
|
@ -138,6 +138,8 @@ uint8_t seriallog_level; // Current copy of Settings.seriallo
|
|||
uint8_t syslog_level; // Current copy of Settings.syslog_level
|
||||
uint8_t my_module_type; // Current copy of Settings.module or user template type
|
||||
uint8_t my_adc0; // Active copy of Module ADC0
|
||||
uint8_t last_source = 0; // Last command source
|
||||
uint8_t shutters_present = 0; // Number of actual define shutters
|
||||
//uint8_t mdns_delayed_start = 0; // mDNS delayed start
|
||||
bool serial_local = false; // Handle serial locally;
|
||||
bool fallback_topic_flag = false; // Use Topic or FallbackTopic
|
||||
|
@ -745,10 +747,7 @@ bool MqttShowSensor(void)
|
|||
}
|
||||
}
|
||||
XsnsCall(FUNC_JSON_APPEND);
|
||||
|
||||
#ifdef USE_SCRIPT_JSON_EXPORT
|
||||
XdrvCall(FUNC_JSON_APPEND);
|
||||
#endif
|
||||
|
||||
bool json_data_available = (strlen(mqtt_data) - json_data_start);
|
||||
if (strstr_P(mqtt_data, PSTR(D_JSON_PRESSURE)) != nullptr) {
|
||||
|
|
|
@ -83,6 +83,8 @@ char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, c
|
|||
#undef CODE_IMAGE
|
||||
#define CODE_IMAGE 3
|
||||
|
||||
#undef USE_DISCOVERY // Disable mDNS (+8k code or +23.5k code with core 2_5_x, +0.3k mem)
|
||||
|
||||
// -- Optional modules -------------------------
|
||||
#define USE_SONOFF_IFAN // Add support for Sonoff iFan02 and iFan03 (+2k code)
|
||||
#define USE_TUYA_MCU // Add support for Tuya Serial MCU
|
||||
|
@ -96,7 +98,6 @@ char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, c
|
|||
#define USE_COUNTER // Enable counters
|
||||
#undef USE_ADC_VCC // Add Analog input on selected devices
|
||||
#define USE_DS18x20 // For more than one DS18x20 sensors with id sort, single scan and read retry (+1k3 code)
|
||||
//#define USE_DS18x20_LEGACY // For more than one DS18x20 sensors with dynamic scan using library OneWire (+1k5 code)
|
||||
|
||||
#define USE_I2C // I2C using library wire (+10k code, 0k2 mem, 124 iram)
|
||||
#define USE_SHT // Add I2C emulating code for SHT1X sensor (+1k4 code)
|
||||
|
@ -413,7 +414,7 @@ char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, c
|
|||
* Mandatory define for DS18x20 if changed by above image selections
|
||||
\*********************************************************************************************/
|
||||
|
||||
#if defined(USE_DS18x20) || defined(USE_DS18x20_LEGACY)
|
||||
#if defined(USE_DS18x20)
|
||||
#else
|
||||
#define USE_DS18B20 // Default DS18B20 sensor needs no external library
|
||||
#endif
|
||||
|
@ -459,7 +460,6 @@ char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, c
|
|||
|
||||
#undef USE_COUNTER // Disable counters
|
||||
#undef USE_DS18x20 // Disable DS18x20 sensor
|
||||
#undef USE_DS18x20_LEGACY // Disable DS18x20 sensor
|
||||
#undef USE_DS18B20 // Disable internal DS18B20 sensor
|
||||
#undef USE_I2C // Disable all I2C sensors and devices
|
||||
#undef USE_SPI // Disable all SPI devices
|
||||
|
@ -544,7 +544,6 @@ char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, c
|
|||
|
||||
#undef USE_COUNTER // Disable counters
|
||||
#undef USE_DS18x20 // Disable DS18x20 sensor
|
||||
#undef USE_DS18x20_LEGACY // Disable DS18x20 sensor
|
||||
#undef USE_DS18B20 // Disable internal DS18B20 sensor
|
||||
#undef USE_I2C // Disable all I2C sensors and devices
|
||||
#undef USE_SPI // Disable all SPI devices
|
||||
|
|
|
@ -531,7 +531,7 @@ const uint8_t kGpioNiceList[] PROGMEM = {
|
|||
GPIO_DHT22, // DHT21, DHT22, AM2301, AM2302, AM2321
|
||||
GPIO_SI7021, // iTead SI7021
|
||||
#endif
|
||||
#if defined(USE_DS18B20) || defined(USE_DS18x20) || defined(USE_DS18x20_LEGACY)
|
||||
#if defined(USE_DS18B20) || defined(USE_DS18x20)
|
||||
GPIO_DSB, // Single wire DS18B20 or DS18S20
|
||||
#endif
|
||||
|
||||
|
|
|
@ -20,6 +20,6 @@
|
|||
#ifndef _SONOFF_VERSION_H_
|
||||
#define _SONOFF_VERSION_H_
|
||||
|
||||
const uint32_t VERSION = 0x0606000C;
|
||||
const uint32_t VERSION = 0x0606000E;
|
||||
|
||||
#endif // _SONOFF_VERSION_H_
|
||||
|
|
|
@ -125,7 +125,7 @@ size_t strcspn(const char *str1, const char *str2)
|
|||
}
|
||||
|
||||
// https://clc-wiki.net/wiki/C_standard_library:string.h:strpbrk
|
||||
// Locate the first occurrence in the string pointed to by s1 of any character from the string pointed to by s2
|
||||
// Locate the first occurrence in the string pointed to by s1 of any character from the string pointed to by s2
|
||||
char* strpbrk(const char *s1, const char *s2)
|
||||
{
|
||||
while(*s1) {
|
||||
|
@ -1264,6 +1264,7 @@ void SetNextTimeInterval(unsigned long& timer, const unsigned long step)
|
|||
#ifdef USE_I2C
|
||||
const uint8_t I2C_RETRY_COUNTER = 3;
|
||||
|
||||
uint32_t i2c_active[4] = { 0 };
|
||||
uint32_t i2c_buffer = 0;
|
||||
|
||||
bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size)
|
||||
|
@ -1457,12 +1458,35 @@ void I2cScan(char *devs, unsigned int devs_len)
|
|||
}
|
||||
}
|
||||
|
||||
void I2cSetActive(uint32_t addr, uint32_t count = 1)
|
||||
{
|
||||
addr &= 0x7F;
|
||||
count &= 0x7F;
|
||||
while (count-- && (addr < 128)) {
|
||||
i2c_active[addr / 32] |= (1 << (addr % 32));
|
||||
addr++;
|
||||
}
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("I2C: Active %08X,%08X,%08X,%08X"), i2c_active[0], i2c_active[1], i2c_active[2], i2c_active[3]);
|
||||
}
|
||||
|
||||
bool I2cActive(uint32_t addr)
|
||||
{
|
||||
addr &= 0x7F;
|
||||
if (i2c_active[addr / 32] & (1 << (addr % 32))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool I2cDevice(uint8_t addr)
|
||||
{
|
||||
if (I2cActive(addr)) {
|
||||
return false; // If already active report as not present;
|
||||
}
|
||||
for (uint8_t address = 1; address <= 127; address++) {
|
||||
Wire.beginTransmission(address);
|
||||
if (!Wire.endTransmission() && (address == addr)) {
|
||||
return true;
|
||||
return true; // Report as present;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -1571,6 +1595,7 @@ void AddLog(uint32_t loglevel)
|
|||
if (!web_log_index) web_log_index++; // Index 0 is not allowed as it is the end of char string
|
||||
}
|
||||
#endif // USE_WEBSERVER
|
||||
if (!global_state.mqtt_down && (loglevel <= Settings.mqttlog_level)) { MqttPublishLogging(mxtime); }
|
||||
if (!global_state.wifi_down && (loglevel <= syslog_level)) { Syslog(); }
|
||||
}
|
||||
|
||||
|
|
|
@ -185,6 +185,18 @@ void CommandHandler(char* topic, uint8_t* data, uint32_t data_len)
|
|||
XdrvMailbox.topic = type;
|
||||
XdrvMailbox.data = dataBuf;
|
||||
|
||||
#ifdef USE_SCRIPT_SUB_COMMAND
|
||||
// allow overwrite tasmota cmds
|
||||
if (!Script_SubCmd()) {
|
||||
if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) {
|
||||
if (!XdrvCall(FUNC_COMMAND)) {
|
||||
if (!XsnsCall(FUNC_COMMAND)) {
|
||||
type = nullptr; // Unknown command
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else //USE_SCRIPT_SUB_COMMAND
|
||||
if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) {
|
||||
if (!XdrvCall(FUNC_COMMAND)) {
|
||||
if (!XsnsCall(FUNC_COMMAND)) {
|
||||
|
@ -192,6 +204,8 @@ void CommandHandler(char* topic, uint8_t* data, uint32_t data_len)
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif //USE_SCRIPT_SUB_COMMAND
|
||||
|
||||
}
|
||||
|
||||
if (type == nullptr) {
|
||||
|
|
|
@ -243,7 +243,7 @@ void GetFeatures(void)
|
|||
feature_sns1 |= 0x00000010; // xsns_05_ds18b20.ino
|
||||
#endif
|
||||
#ifdef USE_DS18x20_LEGACY
|
||||
feature_sns1 |= 0x00000020; // xsns_05_ds18x20_legacy.ino
|
||||
feature_sns1 |= 0x00000020; // xsns_05_ds18x20_legacy.ino - no more supported since 6.6.0.14
|
||||
#endif
|
||||
#ifdef USE_DS18x20
|
||||
feature_sns1 |= 0x00000040; // xsns_05_ds18x20.ino
|
||||
|
|
|
@ -120,10 +120,17 @@ String GetBuildDateAndTime(void)
|
|||
return String(bdt); // 2017-03-07T11:08:02
|
||||
}
|
||||
|
||||
String GetMinuteTime(uint32_t minutes)
|
||||
{
|
||||
char tm[6];
|
||||
snprintf_P(tm, sizeof(tm), PSTR("%02d:%02d"), minutes / 60, minutes % 60);
|
||||
|
||||
return String(tm); // 03:45
|
||||
}
|
||||
|
||||
String GetTimeZone(void)
|
||||
{
|
||||
char tz[7];
|
||||
|
||||
snprintf_P(tz, sizeof(tz), PSTR("%+03d:%02d"), Rtc.time_timezone / 60, abs(Rtc.time_timezone % 60));
|
||||
|
||||
return String(tz); // -03:45
|
||||
|
|
|
@ -91,8 +91,48 @@ const char HTTP_SCRIPT_COUNTER[] PROGMEM =
|
|||
"}"
|
||||
"wl(u);";
|
||||
|
||||
const char HTTP_SCRIPT_ROOT[] PROGMEM =
|
||||
|
||||
const char HTTP_SCRIPT_ROOT[] PROGMEM =
|
||||
#ifdef USE_SCRIPT_WEB_DISPLAY
|
||||
"var rfsh=1;"
|
||||
"function la(p){"
|
||||
"var a='';"
|
||||
"if(la.arguments.length==1){"
|
||||
"a=p;"
|
||||
"clearTimeout(lt);"
|
||||
"}"
|
||||
"if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1)
|
||||
"x=new XMLHttpRequest();"
|
||||
"x.onreadystatechange=function(){"
|
||||
"if(x.readyState==4&&x.status==200){"
|
||||
"var s=x.responseText.replace(/{t}/g,\"<table style='width:100%%'>\").replace(/{s}/g,\"<tr><th>\").replace(/{m}/g,\"</th><td>\").replace(/{e}/g,\"</td></tr>\").replace(/{c}/g,\"%%'><div style='text-align:center;font-weight:\");"
|
||||
"eb('l1').innerHTML=s;"
|
||||
"}"
|
||||
"};"
|
||||
"if (rfsh) {"
|
||||
"x.open('GET','.?m=1'+a,true);" // ?m related to WebServer->hasArg("m")
|
||||
"x.send();"
|
||||
"lt=setTimeout(la,%d);" // Settings.web_refresh
|
||||
"}"
|
||||
"}"
|
||||
"function seva(par,ivar){"
|
||||
"la('&sv='+ivar+'_'+par);"
|
||||
"}"
|
||||
"function siva(par,ivar){"
|
||||
"rfsh=1;"
|
||||
"la('&sv='+ivar+'_'+par);"
|
||||
"rfsh=0;"
|
||||
"}"
|
||||
"function pr(f){"
|
||||
"if (f) {"
|
||||
"lt=setTimeout(la,%d);"
|
||||
"rfsh=1;"
|
||||
"} else {"
|
||||
"clearTimeout(lt);"
|
||||
"rfsh=0;"
|
||||
"}"
|
||||
"}"
|
||||
#else // USE_SCRIPT_WEB_DISPLAY
|
||||
"function la(p){"
|
||||
"var a='';"
|
||||
"if(la.arguments.length==1){"
|
||||
|
@ -111,12 +151,7 @@ const char HTTP_SCRIPT_ROOT[] PROGMEM =
|
|||
"x.send();"
|
||||
"lt=setTimeout(la,%d);" // Settings.web_refresh
|
||||
"}"
|
||||
#ifdef USE_SCRIPT_WEB_DISPLAY
|
||||
"function seva(par,ivar){"
|
||||
"la('&sv='+ivar+'_'+par);"
|
||||
"}"
|
||||
#endif
|
||||
|
||||
#endif // USE_SCRIPT_WEB_DISPLAY
|
||||
|
||||
#ifdef USE_JAVASCRIPT_ES6
|
||||
"lb=p=>la('&d='+p);" // Dark - Bright &d related to lb(value) and WebGetArg("d", tmp, sizeof(tmp));
|
||||
|
@ -128,7 +163,29 @@ const char HTTP_SCRIPT_ROOT[] PROGMEM =
|
|||
"function lc(p){"
|
||||
"la('&t='+p);" // &t related to WebGetArg("t", tmp, sizeof(tmp));
|
||||
"}"
|
||||
#endif
|
||||
#endif // USE_JAVASCRIPT_ES6
|
||||
|
||||
#ifdef USE_SHUTTER
|
||||
#ifdef USE_JAVASCRIPT_ES6
|
||||
"ld1=p=>la('&u1='+p);"
|
||||
"ld2=p=>la('&u2='+p);"
|
||||
"ld3=p=>la('&u3='+p);"
|
||||
"ld4=p=>la('&u4='+p);"
|
||||
#else
|
||||
"function ld1(p){"
|
||||
"la('&u1='+p);"
|
||||
"}"
|
||||
"function ld2(p){"
|
||||
"la('&u2='+p);"
|
||||
"}"
|
||||
"function ld3(p){"
|
||||
"la('&u3='+p);"
|
||||
"}"
|
||||
"function ld4(p){"
|
||||
"la('&u4='+p);"
|
||||
"}"
|
||||
#endif // USE_JAVASCRIPT_ES6
|
||||
#endif // USE_SHUTTER
|
||||
|
||||
"wl(la);";
|
||||
|
||||
|
@ -344,6 +401,11 @@ const char HTTP_MSG_SLIDER1[] PROGMEM =
|
|||
const char HTTP_MSG_SLIDER2[] PROGMEM =
|
||||
"<div><span class='p'>" D_DARKLIGHT "</span><span class='q'>" D_BRIGHTLIGHT "</span></div>"
|
||||
"<div><input type='range' min='1' max='100' value='%d' onchange='lb(value)'></div>";
|
||||
#ifdef USE_SHUTTER
|
||||
const char HTTP_MSG_SLIDER3[] PROGMEM =
|
||||
"<div><span class='p'>" D_CLOSE "</span><span class='q'>" D_OPEN "</span></div>"
|
||||
"<div><input type='range' min='0' max='100' value='%d' onchange='ld%d(value)'></div>";
|
||||
#endif // USE_SHUTTER
|
||||
const char HTTP_MSG_RSTRT[] PROGMEM =
|
||||
"<br><div style='text-align:center;'>" D_DEVICE_WILL_RESTART "</div><br>";
|
||||
|
||||
|
@ -518,6 +580,7 @@ void ShowWebSource(uint32_t source)
|
|||
void ExecuteWebCommand(char* svalue, uint32_t source)
|
||||
{
|
||||
ShowWebSource(source);
|
||||
last_source = source;
|
||||
ExecuteCommand(svalue, SRC_IGNORE);
|
||||
}
|
||||
|
||||
|
@ -939,7 +1002,11 @@ void HandleRoot(void)
|
|||
char stemp[5];
|
||||
|
||||
WSContentStart_P(S_MAIN_MENU);
|
||||
#ifdef USE_SCRIPT_WEB_DISPLAY
|
||||
WSContentSend_P(HTTP_SCRIPT_ROOT, Settings.web_refresh, Settings.web_refresh);
|
||||
#else
|
||||
WSContentSend_P(HTTP_SCRIPT_ROOT, Settings.web_refresh);
|
||||
#endif
|
||||
WSContentSendStyle();
|
||||
|
||||
WSContentSend_P(PSTR("<div id='l1' name='l1'></div>"));
|
||||
|
@ -952,6 +1019,13 @@ void HandleRoot(void)
|
|||
WSContentSend_P(HTTP_MSG_SLIDER2, Settings.light_dimmer);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SHUTTER
|
||||
if (Settings.flag3.shutter_mode) {
|
||||
for (uint32_t i = 0; i < shutters_present; i++) {
|
||||
WSContentSend_P(HTTP_MSG_SLIDER3, Settings.shutter_position[i], i+1);
|
||||
}
|
||||
}
|
||||
#endif // USE_SHUTTER
|
||||
WSContentSend_P(HTTP_TABLE100);
|
||||
WSContentSend_P(PSTR("<tr>"));
|
||||
#ifdef USE_SONOFF_IFAN
|
||||
|
@ -1052,6 +1126,17 @@ bool HandleRootStatusRefresh(void)
|
|||
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_COLORTEMPERATURE " %s"), tmp);
|
||||
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
||||
}
|
||||
#ifdef USE_SHUTTER
|
||||
char webindex[5]; // WebGetArg name
|
||||
for (uint32_t j = 1; j < 5; j++) {
|
||||
snprintf_P(webindex, sizeof(webindex), PSTR("u%d"), j);
|
||||
WebGetArg(webindex, tmp, sizeof(tmp)); // 0 - 100 percent
|
||||
if (strlen(tmp)) {
|
||||
snprintf_P(svalue, sizeof(svalue), PSTR("ShutterPosition%d %s"), j, tmp);
|
||||
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
||||
}
|
||||
}
|
||||
#endif // USE_SHUTTER
|
||||
WebGetArg("k", tmp, sizeof(tmp)); // 1 - 16 Pre defined RF keys
|
||||
if (strlen(tmp)) {
|
||||
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RFKEY "%s"), tmp);
|
||||
|
|
|
@ -39,7 +39,7 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix
|
|||
D_CMND_TLSKEY "|"
|
||||
#endif
|
||||
D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTCLIENT "|"
|
||||
D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|"
|
||||
D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|" D_CMND_MQTTLOG "|"
|
||||
D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|" D_CMND_SENSORRETAIN ;
|
||||
|
||||
void (* const MqttCommand[])(void) PROGMEM = {
|
||||
|
@ -53,7 +53,7 @@ void (* const MqttCommand[])(void) PROGMEM = {
|
|||
&CmndTlsKey,
|
||||
#endif
|
||||
&CmndMqttHost, &CmndMqttPort, &CmndMqttRetry, &CmndStateText, &CmndMqttClient,
|
||||
&CmndFullTopic, &CmndPrefix, &CmndGroupTopic, &CmndTopic, &CmndPublish,
|
||||
&CmndFullTopic, &CmndPrefix, &CmndGroupTopic, &CmndTopic, &CmndPublish, &CmndMqttlog,
|
||||
&CmndButtonTopic, &CmndSwitchTopic, &CmndButtonRetain, &CmndSwitchRetain, &CmndPowerRetain, &CmndSensorRetain };
|
||||
|
||||
struct MQTT {
|
||||
|
@ -305,6 +305,35 @@ void MqttUnsubscribe(const char *topic)
|
|||
MqttUnsubscribeLib(topic);
|
||||
}
|
||||
|
||||
void MqttPublishLogging(const char *mxtime)
|
||||
{
|
||||
if (Settings.flag.mqtt_enabled) {
|
||||
if (MqttIsConnected()) {
|
||||
|
||||
char saved_mqtt_data[MESSZ];
|
||||
memcpy(saved_mqtt_data, mqtt_data, sizeof(saved_mqtt_data));
|
||||
// ResponseTime_P(PSTR(",\"Log\":{\"%s\"}}"), log_data); // Will fail as some messages contain JSON
|
||||
Response_P(PSTR("%s%s"), mxtime, log_data); // No JSON and ugly!!
|
||||
|
||||
char romram[33];
|
||||
char stopic[TOPSZ];
|
||||
snprintf_P(romram, sizeof(romram), PSTR("LOGGING"));
|
||||
GetTopic_P(stopic, STAT, mqtt_topic, romram);
|
||||
|
||||
char *me;
|
||||
if (!strcmp(Settings.mqtt_prefix[0], Settings.mqtt_prefix[1])) {
|
||||
me = strstr(stopic, Settings.mqtt_prefix[0]);
|
||||
if (me == stopic) {
|
||||
mqtt_cmnd_publish += 3;
|
||||
}
|
||||
}
|
||||
MqttPublishLib(stopic, false);
|
||||
|
||||
memcpy(mqtt_data, saved_mqtt_data, sizeof(saved_mqtt_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MqttPublishDirect(const char* topic, bool retained)
|
||||
{
|
||||
char sretained[CMDSZ];
|
||||
|
@ -724,6 +753,14 @@ void CmndMqttPassword(void)
|
|||
}
|
||||
#endif // USE_MQTT_AWS_IOT
|
||||
|
||||
void CmndMqttlog(void)
|
||||
{
|
||||
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_ALL)) {
|
||||
Settings.mqttlog_level = XdrvMailbox.payload;
|
||||
}
|
||||
ResponseCmndNumber(Settings.mqttlog_level);
|
||||
}
|
||||
|
||||
void CmndMqttHost(void)
|
||||
{
|
||||
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
||||
|
@ -876,7 +913,7 @@ void CmndButtonTopic(void)
|
|||
|
||||
void CmndSwitchTopic(void)
|
||||
{
|
||||
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.switch_topic))) {
|
||||
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.switch_topic))) {
|
||||
MakeValidMqtt(0, XdrvMailbox.data);
|
||||
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
|
||||
switch (Shortcut()) {
|
||||
|
|
|
@ -82,8 +82,7 @@ struct ENERGY {
|
|||
|
||||
float start_energy = 0; // 12345.12345 kWh total previous
|
||||
float daily = 0; // 123.123 kWh
|
||||
float total = 0; // 12345.12345 kWh tariff 1 + 2
|
||||
float total1 = 0; // 12345.12345 kWh tariff 1 - off-peak
|
||||
float total = 0; // 12345.12345 kWh total energy
|
||||
float export_active = NAN; // 123.123 KWh
|
||||
|
||||
unsigned long kWhtoday_delta = 0; // 1212312345 Wh 10^-5 (deca micro Watt hours) - Overflows to Energy.kWhtoday (HLW and CSE only)
|
||||
|
@ -93,7 +92,7 @@ struct ENERGY {
|
|||
|
||||
uint8_t fifth_second = 0;
|
||||
uint8_t command_code = 0;
|
||||
uint8_t data_valid = 0;
|
||||
uint8_t data_valid[3] = { 0, 0, 0 };
|
||||
|
||||
uint8_t phase_count = 1; // Number of phases active
|
||||
bool voltage_common = false; // Use single voltage
|
||||
|
@ -130,18 +129,23 @@ Ticker ticker_energy;
|
|||
|
||||
bool EnergyTariff1Active() // Off-Peak hours
|
||||
{
|
||||
uint8_t tariff1 = Settings.register8[R8_ENERGY_TARIFF1_ST];
|
||||
uint8_t tariff2 = Settings.register8[R8_ENERGY_TARIFF2_ST];
|
||||
if (IsDst() && (Settings.register8[R8_ENERGY_TARIFF1_DS] != Settings.register8[R8_ENERGY_TARIFF2_DS])) {
|
||||
tariff1 = Settings.register8[R8_ENERGY_TARIFF1_DS];
|
||||
tariff2 = Settings.register8[R8_ENERGY_TARIFF2_DS];
|
||||
uint8_t dst = 0;
|
||||
if (IsDst() && (Settings.tariff[0][1] != Settings.tariff[1][1])) {
|
||||
dst = 1;
|
||||
}
|
||||
if (tariff1 != tariff2) {
|
||||
return ((RtcTime.hour < tariff2) || // Tarrif1 = Off-Peak
|
||||
(RtcTime.hour >= tariff1) ||
|
||||
(Settings.flag3.energy_weekend && ((RtcTime.day_of_week == 1) ||
|
||||
(RtcTime.day_of_week == 7)))
|
||||
);
|
||||
if (Settings.tariff[0][dst] != Settings.tariff[1][dst]) {
|
||||
if (Settings.flag3.energy_weekend && ((RtcTime.day_of_week == 1) ||
|
||||
(RtcTime.day_of_week == 7))) {
|
||||
return true;
|
||||
}
|
||||
uint32_t minutes = MinutesPastMidnight();
|
||||
if (Settings.tariff[0][dst] > Settings.tariff[1][dst]) {
|
||||
// {"Tariff":{"Off-Peak":{"STD":"22:00","DST":"23:00"},"Standard":{"STD":"06:00","DST":"07:00"},"Weekend":"OFF"}}
|
||||
return ((minutes >= Settings.tariff[0][dst]) || (minutes < Settings.tariff[1][dst]));
|
||||
} else {
|
||||
// {"Tariff":{"Off-Peak":{"STD":"00:29","DST":"01:29"},"Standard":{"STD":"07:29","DST":"08:29"},"Weekend":"OFF"}}
|
||||
return ((minutes >= Settings.tariff[0][dst]) && (minutes < Settings.tariff[1][dst]));
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -155,24 +159,28 @@ void EnergyUpdateToday(void)
|
|||
Energy.kWhtoday += delta;
|
||||
}
|
||||
|
||||
uint32_t energy_diff = Energy.kWhtoday_offset + Energy.kWhtoday - RtcSettings.energy_kWhtoday;
|
||||
|
||||
uint32_t return_diff = 0;
|
||||
if (!isnan(Energy.export_active)) {
|
||||
return_diff = (uint32_t)(Energy.export_active * 1000) - RtcSettings.energy_usage.last_return_kWhtotal;
|
||||
RtcSettings.energy_usage.last_return_kWhtotal = (uint32_t)(Energy.export_active * 1000);
|
||||
}
|
||||
|
||||
RtcSettings.energy_kWhtoday = Energy.kWhtoday_offset + Energy.kWhtoday;
|
||||
Energy.daily = (float)(RtcSettings.energy_kWhtoday) / 100000;
|
||||
Energy.total = (float)(RtcSettings.energy_kWhtotal + RtcSettings.energy_kWhtoday) / 100000;
|
||||
|
||||
if (EnergyTariff1Active()) { // Tarrif1 = Off-Peak
|
||||
RtcSettings.energy_usage.usage1_kWhtoday += energy_diff;
|
||||
RtcSettings.energy_usage.return1_kWhtotal += return_diff;
|
||||
Energy.total1 = (float)(RtcSettings.energy_usage.usage1_kWhtotal + RtcSettings.energy_usage.usage1_kWhtoday) / 100000;
|
||||
} else {
|
||||
RtcSettings.energy_usage.return2_kWhtotal += return_diff;
|
||||
if (RtcTime.valid){ // We calc the difference only if we have a valid RTC time.
|
||||
|
||||
uint32_t energy_diff = (uint32_t)(Energy.total * 100000) - RtcSettings.energy_usage.last_usage_kWhtotal;
|
||||
RtcSettings.energy_usage.last_usage_kWhtotal = (uint32_t)(Energy.total * 100000);
|
||||
|
||||
uint32_t return_diff = 0;
|
||||
if (!isnan(Energy.export_active)) {
|
||||
return_diff = (uint32_t)(Energy.export_active * 100000) - RtcSettings.energy_usage.last_return_kWhtotal;
|
||||
RtcSettings.energy_usage.last_return_kWhtotal = (uint32_t)(Energy.export_active * 100000);
|
||||
}
|
||||
|
||||
if (EnergyTariff1Active()) { // Tarrif1 = Off-Peak
|
||||
RtcSettings.energy_usage.usage1_kWhtotal += energy_diff;
|
||||
RtcSettings.energy_usage.return1_kWhtotal += return_diff;
|
||||
} else {
|
||||
RtcSettings.energy_usage.usage2_kWhtotal += energy_diff;
|
||||
RtcSettings.energy_usage.return2_kWhtotal += return_diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,6 +198,14 @@ void EnergyUpdateTotal(float value, bool kwh)
|
|||
else if (value != Energy.start_energy) {
|
||||
Energy.kWhtoday = (unsigned long)((value - Energy.start_energy) * multiplier);
|
||||
}
|
||||
|
||||
if (Energy.total < (value - 0.01)){ // We subtract a little offset to avoid continuous updates
|
||||
RtcSettings.energy_kWhtotal = (unsigned long)((value * multiplier) - Energy.kWhtoday_offset - Energy.kWhtoday);
|
||||
Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal;
|
||||
Energy.total = (float)(RtcSettings.energy_kWhtotal + Energy.kWhtoday_offset + Energy.kWhtoday) / 100000;
|
||||
Settings.energy_kWhtotal_time = (!Energy.kWhtoday_offset) ? LocalTime() : Midnight();
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NRG: Energy Total updated with hardware value"));
|
||||
}
|
||||
EnergyUpdateToday();
|
||||
}
|
||||
|
||||
|
@ -216,10 +232,6 @@ void Energy200ms(void)
|
|||
RtcSettings.energy_kWhtoday = 0;
|
||||
Energy.start_energy = 0;
|
||||
|
||||
RtcSettings.energy_usage.usage1_kWhtotal += RtcSettings.energy_usage.usage1_kWhtoday;
|
||||
Settings.energy_usage.usage1_kWhtotal = RtcSettings.energy_usage.usage1_kWhtotal;
|
||||
RtcSettings.energy_usage.usage1_kWhtoday = 0;
|
||||
|
||||
Energy.kWhtoday_delta = 0;
|
||||
Energy.period = Energy.kWhtoday;
|
||||
EnergyUpdateToday();
|
||||
|
@ -416,18 +428,22 @@ void EnergyMqttShow(void)
|
|||
}
|
||||
#endif // USE_ENERGY_MARGIN_DETECTION
|
||||
|
||||
void EnergyOverTempCheck()
|
||||
void EnergyEverySecond()
|
||||
{
|
||||
// Overtemp check
|
||||
if (global_update) {
|
||||
if (power && (global_temperature != 9999) && (global_temperature > Settings.param[P_OVER_TEMP])) { // Device overtemp, turn off relays
|
||||
SetAllPower(POWER_ALL_OFF, SRC_OVERTEMP);
|
||||
}
|
||||
}
|
||||
if (Energy.data_valid <= ENERGY_WATCHDOG) {
|
||||
Energy.data_valid++;
|
||||
if (Energy.data_valid > ENERGY_WATCHDOG) {
|
||||
// Reset energy registers
|
||||
for (uint32_t i = 0; i < Energy.phase_count; i++) {
|
||||
|
||||
// Invalid data reset
|
||||
uint32_t data_valid = Energy.phase_count;
|
||||
for (uint32_t i = 0; i < Energy.phase_count; i++) {
|
||||
if (Energy.data_valid[i] <= ENERGY_WATCHDOG) {
|
||||
Energy.data_valid[i]++;
|
||||
if (Energy.data_valid[i] > ENERGY_WATCHDOG) {
|
||||
// Reset energy registers
|
||||
Energy.voltage[i] = 0;
|
||||
Energy.current[i] = 0;
|
||||
Energy.active_power[i] = 0;
|
||||
|
@ -435,13 +451,21 @@ void EnergyOverTempCheck()
|
|||
if (!isnan(Energy.reactive_power[i])) { Energy.reactive_power[i] = 0; }
|
||||
if (!isnan(Energy.frequency[i])) { Energy.frequency[i] = 0; }
|
||||
if (!isnan(Energy.power_factor[i])) { Energy.power_factor[i] = 0; }
|
||||
}
|
||||
if (!isnan(Energy.export_active)) { Energy.export_active = 0; }
|
||||
Energy.start_energy = 0;
|
||||
|
||||
XnrgCall(FUNC_ENERGY_RESET);
|
||||
data_valid--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!data_valid) {
|
||||
if (!isnan(Energy.export_active)) { Energy.export_active = 0; }
|
||||
Energy.start_energy = 0;
|
||||
|
||||
XnrgCall(FUNC_ENERGY_RESET);
|
||||
}
|
||||
|
||||
#ifdef USE_ENERGY_MARGIN_DETECTION
|
||||
EnergyMarginCheck();
|
||||
#endif // USE_ENERGY_MARGIN_DETECTION
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
|
@ -493,60 +517,117 @@ void CmndEnergyReset(void)
|
|||
Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal;
|
||||
Energy.total = (float)(RtcSettings.energy_kWhtotal + Energy.kWhtoday_offset + Energy.kWhtoday) / 100000;
|
||||
Settings.energy_kWhtotal_time = (!Energy.kWhtoday_offset) ? LocalTime() : Midnight();
|
||||
RtcSettings.energy_usage.last_usage_kWhtotal = (uint32_t)(Energy.total * 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (RtcSettings.energy_usage.usage1_kWhtoday > (Energy.kWhtoday_offset + Energy.kWhtoday)) {
|
||||
RtcSettings.energy_usage.usage1_kWhtoday = Energy.kWhtoday_offset + Energy.kWhtoday;
|
||||
}
|
||||
if (Settings.energy_usage.usage1_kWhtoday > Settings.energy_kWhtoday) {
|
||||
Settings.energy_usage.usage1_kWhtoday = Settings.energy_kWhtoday;
|
||||
RtcSettings.energy_usage.usage1_kWhtoday = Settings.energy_kWhtoday;
|
||||
}
|
||||
if (Settings.energy_usage.usage1_kWhtotal > Settings.energy_kWhtotal) {
|
||||
Settings.energy_usage.usage1_kWhtotal = Settings.energy_kWhtotal;
|
||||
RtcSettings.energy_usage.usage1_kWhtotal = Settings.energy_kWhtotal;
|
||||
}
|
||||
|
||||
char energy_total_chr[FLOATSZ];
|
||||
dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr);
|
||||
char energy_daily_chr[FLOATSZ];
|
||||
dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr);
|
||||
char energy_yesterday_chr[FLOATSZ];
|
||||
dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
|
||||
|
||||
Response_P(PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s}}"),
|
||||
XdrvMailbox.command, energy_total_chr, energy_yesterday_chr, energy_daily_chr);
|
||||
}
|
||||
|
||||
if ((XdrvMailbox.index > 3) && (XdrvMailbox.index <= 5)) {
|
||||
char *p;
|
||||
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
|
||||
int32_t position = -1;
|
||||
uint32_t values[2];
|
||||
|
||||
while ((str != nullptr) && (position < 1)) {
|
||||
uint32_t value = strtoul(str, nullptr, 10);
|
||||
position++;
|
||||
values[position] = value *100;
|
||||
str = strtok_r(nullptr, ", ", &p);
|
||||
}
|
||||
|
||||
switch (XdrvMailbox.index)
|
||||
{
|
||||
case 4:
|
||||
// Reset energy_usage.usage totals
|
||||
if (position > -1) {
|
||||
RtcSettings.energy_usage.usage1_kWhtotal = values[0];
|
||||
}
|
||||
if (position > 0) {
|
||||
RtcSettings.energy_usage.usage2_kWhtotal = values[1];
|
||||
}
|
||||
Settings.energy_usage.usage1_kWhtotal = RtcSettings.energy_usage.usage1_kWhtotal;
|
||||
Settings.energy_usage.usage2_kWhtotal = RtcSettings.energy_usage.usage2_kWhtotal;
|
||||
break;
|
||||
case 5:
|
||||
// Reset energy_usage.return totals
|
||||
if (position > -1) {
|
||||
RtcSettings.energy_usage.return1_kWhtotal = values[0];
|
||||
}
|
||||
if (position > 0) {
|
||||
RtcSettings.energy_usage.return2_kWhtotal = values[1];
|
||||
}
|
||||
Settings.energy_usage.return1_kWhtotal = RtcSettings.energy_usage.return1_kWhtotal;
|
||||
Settings.energy_usage.return2_kWhtotal = RtcSettings.energy_usage.return2_kWhtotal;
|
||||
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char energy_total_chr[FLOATSZ];
|
||||
dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr);
|
||||
char energy_daily_chr[FLOATSZ];
|
||||
dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr);
|
||||
char energy_yesterday_chr[FLOATSZ];
|
||||
dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
|
||||
|
||||
char energy_usage1_chr[FLOATSZ];
|
||||
dtostrfd((float)Settings.energy_usage.usage1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_usage1_chr);
|
||||
char energy_usage2_chr[FLOATSZ];
|
||||
dtostrfd((float)Settings.energy_usage.usage2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_usage2_chr);
|
||||
char energy_return1_chr[FLOATSZ];
|
||||
dtostrfd((float)Settings.energy_usage.return1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_return1_chr);
|
||||
char energy_return2_chr[FLOATSZ];
|
||||
dtostrfd((float)Settings.energy_usage.return2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_return2_chr);
|
||||
|
||||
Response_P(PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s,\"" D_JSON_USAGE "\":[%s,%s],\"" D_JSON_EXPORT "\":[%s,%s]}}"),
|
||||
XdrvMailbox.command, energy_total_chr, energy_yesterday_chr, energy_daily_chr, energy_usage1_chr, energy_usage2_chr, energy_return1_chr, energy_return2_chr);
|
||||
}
|
||||
|
||||
void CmndTariff(void)
|
||||
{
|
||||
// Tariff1 22,23 - Tariff1 start hour for Standard Time and Daylight Savings Time
|
||||
// Tariff2 6,7 - Tariff2 start hour for Standard Time and Daylight Savings Time
|
||||
// Tariff1 22:00,23:00 - Tariff1 start hour for Standard Time and Daylight Savings Time
|
||||
// Tariff2 6:00,7:00 - Tariff2 start hour for Standard Time and Daylight Savings Time
|
||||
// Tariffx 1320, 1380 = minutes and also 22:00, 23:00
|
||||
// Tariffx 22, 23 = hours and also 22:00, 23:00
|
||||
// Tariff9 0/1
|
||||
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
|
||||
char *p;
|
||||
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
|
||||
uint32_t tariff = XdrvMailbox.index -1;
|
||||
uint32_t time_type = 0;
|
||||
while ((str != nullptr) && (time_type <= 2)) {
|
||||
uint8_t value = strtol(str, nullptr, 10);
|
||||
if ((value >= 0) && (value < 24)) {
|
||||
Settings.register8[R8_ENERGY_TARIFF1_ST + (XdrvMailbox.index -1) + time_type] = value;
|
||||
char *p;
|
||||
char *str = strtok_r(XdrvMailbox.data, ", ", &p); // 23:15, 22:30
|
||||
while ((str != nullptr) && (time_type < 2)) {
|
||||
char *q;
|
||||
uint32_t value = strtol(str, &q, 10); // 23 or 22
|
||||
Settings.tariff[tariff][time_type] = value;
|
||||
if (value < 24) { // Below 24 is hours
|
||||
Settings.tariff[tariff][time_type] *= 60; // Multiply hours by 60 minutes
|
||||
char *minute = strtok_r(nullptr, ":", &q);
|
||||
if (minute) {
|
||||
value = strtol(minute, nullptr, 10); // 15 or 30
|
||||
if (value > 59) {
|
||||
value = 59;
|
||||
}
|
||||
Settings.tariff[tariff][time_type] += value;
|
||||
}
|
||||
}
|
||||
if (Settings.tariff[tariff][time_type] > 1439) {
|
||||
Settings.tariff[tariff][time_type] = 1439; // Max is 23:59
|
||||
}
|
||||
str = strtok_r(nullptr, ", ", &p);
|
||||
time_type += 2;
|
||||
time_type++;
|
||||
}
|
||||
}
|
||||
else if (XdrvMailbox.index == 9) {
|
||||
Settings.flag3.energy_weekend = XdrvMailbox.payload & 1;
|
||||
}
|
||||
Response_P(PSTR("{\"%s\":{\"Off-Peak\":[%d,%d],\"Standard\":[%d,%d],\"Weekend\":\"%s\"}}"),
|
||||
Response_P(PSTR("{\"%s\":{\"Off-Peak\":{\"STD\":\"%s\",\"DST\":\"%s\"},\"Standard\":{\"STD\":\"%s\",\"DST\":\"%s\"},\"Weekend\":\"%s\"}}"),
|
||||
XdrvMailbox.command,
|
||||
Settings.register8[R8_ENERGY_TARIFF1_ST], Settings.register8[R8_ENERGY_TARIFF1_DS],
|
||||
Settings.register8[R8_ENERGY_TARIFF2_ST], Settings.register8[R8_ENERGY_TARIFF2_DS],
|
||||
GetMinuteTime(Settings.tariff[0][0]).c_str(),GetMinuteTime(Settings.tariff[0][1]).c_str(),
|
||||
GetMinuteTime(Settings.tariff[1][0]).c_str(),GetMinuteTime(Settings.tariff[1][1]).c_str(),
|
||||
GetStateText(Settings.flag3.energy_weekend));
|
||||
}
|
||||
|
||||
|
@ -766,11 +847,9 @@ void EnergySnsInit(void)
|
|||
}
|
||||
else if (RtcTime.day_of_year == Settings.energy_kWhdoy) {
|
||||
Energy.kWhtoday_offset = Settings.energy_kWhtoday;
|
||||
RtcSettings.energy_usage.usage1_kWhtoday = Settings.energy_usage.usage1_kWhtoday;
|
||||
}
|
||||
else {
|
||||
Energy.kWhtoday_offset = 0;
|
||||
RtcSettings.energy_usage.usage1_kWhtoday = 0;
|
||||
}
|
||||
Energy.kWhtoday = 0;
|
||||
Energy.kWhtoday_delta = 0;
|
||||
|
@ -890,18 +969,22 @@ void EnergyShow(bool json)
|
|||
dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr);
|
||||
char energy_yesterday_chr[FLOATSZ];
|
||||
dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
|
||||
|
||||
char energy_total_chr[3][FLOATSZ];
|
||||
dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr[0]);
|
||||
char export_active_chr[3][FLOATSZ];
|
||||
dtostrfd(Energy.export_active, Settings.flag2.energy_resolution, export_active_chr[0]);
|
||||
uint8_t energy_total_fields = 1;
|
||||
if (Settings.register8[R8_ENERGY_TARIFF1_ST] != Settings.register8[R8_ENERGY_TARIFF2_ST]) {
|
||||
dtostrfd(Energy.total1, Settings.flag2.energy_resolution, energy_total_chr[1]); // Tariff1
|
||||
dtostrfd(Energy.total - Energy.total1, Settings.flag2.energy_resolution, energy_total_chr[2]); // Tariff2
|
||||
|
||||
if (Settings.tariff[0][0] != Settings.tariff[1][0]) {
|
||||
dtostrfd((float)RtcSettings.energy_usage.usage1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_total_chr[1]); // Tariff1
|
||||
dtostrfd((float)RtcSettings.energy_usage.usage2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_total_chr[2]); // Tariff2
|
||||
dtostrfd((float)RtcSettings.energy_usage.return1_kWhtotal / 100000, Settings.flag2.energy_resolution, export_active_chr[1]); // Tariff1
|
||||
dtostrfd((float)RtcSettings.energy_usage.return2_kWhtotal / 100000, Settings.flag2.energy_resolution, export_active_chr[2]); // Tariff2
|
||||
energy_total_fields = 3;
|
||||
}
|
||||
char export_active_chr[FLOATSZ];
|
||||
dtostrfd(Energy.export_active, Settings.flag2.energy_resolution, export_active_chr);
|
||||
|
||||
char value_chr[FLOATSZ *3];
|
||||
char value_chr[FLOATSZ *3]; // Used by EnergyFormatIndex
|
||||
char value2_chr[FLOATSZ *3];
|
||||
char value3_chr[FLOATSZ *3];
|
||||
|
||||
|
@ -915,7 +998,8 @@ void EnergyShow(bool json)
|
|||
energy_daily_chr);
|
||||
|
||||
if (!isnan(Energy.export_active)) {
|
||||
ResponseAppend_P(PSTR(",\"" D_JSON_EXPORT_ACTIVE "\":%s"), export_active_chr);
|
||||
ResponseAppend_P(PSTR(",\"" D_JSON_EXPORT_ACTIVE "\":%s"),
|
||||
EnergyFormatIndex(value_chr, export_active_chr[0], json, energy_total_fields));
|
||||
}
|
||||
|
||||
if (show_energy_period) {
|
||||
|
@ -958,13 +1042,11 @@ void EnergyShow(bool json)
|
|||
dtostrfd(Energy.total * 1000, 1, energy_total_chr[0]);
|
||||
DomoticzSensorPowerEnergy((int)Energy.active_power[0], energy_total_chr[0]); // PowerUsage, EnergyToday
|
||||
|
||||
dtostrfd(Energy.total1 * 1000, 1, energy_total_chr[1]); // Tariff1
|
||||
dtostrfd((Energy.total - Energy.total1) * 1000, 1, energy_total_chr[2]); // Tariff2
|
||||
char return1_total_chr[FLOATSZ];
|
||||
dtostrfd(RtcSettings.energy_usage.return1_kWhtotal, 1, return1_total_chr);
|
||||
char return2_total_chr[FLOATSZ];
|
||||
dtostrfd(RtcSettings.energy_usage.return2_kWhtotal, 1, return2_total_chr);
|
||||
DomoticzSensorP1SmartMeter(energy_total_chr[1], energy_total_chr[2], return1_total_chr, return2_total_chr, (int)Energy.active_power[0]);
|
||||
dtostrfd((float)RtcSettings.energy_usage.usage1_kWhtotal / 100, 1, energy_total_chr[1]); // Tariff1
|
||||
dtostrfd((float)RtcSettings.energy_usage.usage2_kWhtotal / 100, 1, energy_total_chr[2]); // Tariff2
|
||||
dtostrfd((float)RtcSettings.energy_usage.return1_kWhtotal / 100, 1, export_active_chr[1]);
|
||||
dtostrfd((float)RtcSettings.energy_usage.return2_kWhtotal / 100, 1, export_active_chr[2]);
|
||||
DomoticzSensorP1SmartMeter(energy_total_chr[1], energy_total_chr[2], export_active_chr[1], export_active_chr[2], (int)Energy.active_power[0]);
|
||||
|
||||
if (Energy.voltage_available) {
|
||||
DomoticzSensor(DZ_VOLTAGE, voltage_chr[0]); // Voltage
|
||||
|
@ -1016,7 +1098,7 @@ void EnergyShow(bool json)
|
|||
}
|
||||
WSContentSend_PD(HTTP_ENERGY_SNS2, energy_daily_chr, energy_yesterday_chr, energy_total_chr[0]);
|
||||
if (!isnan(Energy.export_active)) {
|
||||
WSContentSend_PD(HTTP_ENERGY_SNS3, export_active_chr);
|
||||
WSContentSend_PD(HTTP_ENERGY_SNS3, export_active_chr[0]);
|
||||
}
|
||||
|
||||
XnrgCall(FUNC_WEB_SENSOR);
|
||||
|
@ -1066,10 +1148,7 @@ bool Xsns03(uint8_t function)
|
|||
if (energy_flg) {
|
||||
switch (function) {
|
||||
case FUNC_EVERY_SECOND:
|
||||
#ifdef USE_ENERGY_MARGIN_DETECTION
|
||||
EnergyMarginCheck();
|
||||
#endif // USE_ENERGY_MARGIN_DETECTION
|
||||
EnergyOverTempCheck();
|
||||
EnergyEverySecond();
|
||||
break;
|
||||
case FUNC_JSON_APPEND:
|
||||
EnergyShow(true);
|
||||
|
|
|
@ -2281,7 +2281,7 @@ void CmndChannel(void)
|
|||
light_controller.changeChannels(Light.current_color);
|
||||
coldim = true;
|
||||
}
|
||||
ResponseCmndIdxNumber(Light.current_color[XdrvMailbox.index -1] * 100 / 255);
|
||||
ResponseCmndIdxNumber(changeUIntScale(Light.current_color[XdrvMailbox.index -1],0,255,0,100));
|
||||
if (coldim) {
|
||||
LightPreparePower();
|
||||
}
|
||||
|
|
|
@ -169,15 +169,15 @@ String sendIRJsonState(const struct decode_results &results) {
|
|||
char hvalue[64];
|
||||
if (UNKNOWN != results.decode_type) {
|
||||
Uint64toHex(results.value, hvalue, results.bits); // Get 64bit value as hex 0x00123456
|
||||
json += "\"";
|
||||
json += "\"0x";
|
||||
json += hvalue;
|
||||
json += "\",\"" D_JSON_IR_DATALSB "\":\"";
|
||||
json += "\",\"" D_JSON_IR_DATALSB "\":\"0x";
|
||||
Uint64toHex(reverseBitsInBytes64(results.value), hvalue, results.bits); // Get 64bit value as hex 0x00123456, LSB
|
||||
json += hvalue;
|
||||
json += "\"";
|
||||
} else { // UNKNOWN
|
||||
Uint64toHex(results.value, hvalue, 32); // Unknown is always 32 bits
|
||||
json += "\"";
|
||||
json += "\"0x";
|
||||
json += hvalue;
|
||||
json += "\"";
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ void IrReceiveCheck(void)
|
|||
// if ((now - ir_lasttime > IR_TIME_AVOID_DUPLICATE) && (UNKNOWN != results.decode_type) && (results.bits > 0)) {
|
||||
if (!irsend_active && (now - ir_lasttime > IR_TIME_AVOID_DUPLICATE)) {
|
||||
ir_lasttime = now;
|
||||
ResponseTime_P(PSTR(",\"" D_JSON_IRRECEIVED "\":%s"), sendIRJsonState(results).c_str());
|
||||
Response_P(PSTR("{\"" D_JSON_IRRECEIVED "\":%s"), sendIRJsonState(results).c_str());
|
||||
|
||||
if (Settings.flag3.receive_raw) {
|
||||
ResponseAppend_P(PSTR(",\"" D_JSON_IR_RAWDATA "\":["));
|
||||
|
@ -438,7 +438,7 @@ uint32_t IrRemoteCmndIrSendJson(void)
|
|||
|
||||
char dvalue[32];
|
||||
char hvalue[32];
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol %d, bits %d, data %s (%s), repeat %d"),
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol %d, bits %d, data 0x%s (%s), repeat %d"),
|
||||
protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat);
|
||||
|
||||
irsend_active = true; // deactivate receive
|
||||
|
|
|
@ -42,7 +42,7 @@ const char DOMOTICZ_MESSAGE[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"
|
|||
|
||||
const char kDomoticzSensors[] PROGMEM =
|
||||
D_DOMOTICZ_TEMP "|" D_DOMOTICZ_TEMP_HUM "|" D_DOMOTICZ_TEMP_HUM_BARO "|" D_DOMOTICZ_POWER_ENERGY "|" D_DOMOTICZ_ILLUMINANCE "|"
|
||||
D_DOMOTICZ_COUNT "|" D_DOMOTICZ_VOLTAGE "|" D_DOMOTICZ_CURRENT "|" D_DOMOTICZ_AIRQUALITY "|" D_DOMOTICZ_P1_SMART_METER ;
|
||||
D_DOMOTICZ_COUNT "|" D_DOMOTICZ_VOLTAGE "|" D_DOMOTICZ_CURRENT "|" D_DOMOTICZ_AIRQUALITY "|" D_DOMOTICZ_P1_SMART_METER "|" D_DOMOTICZ_SHUTTER ;
|
||||
|
||||
char domoticz_in_topic[] = DOMOTICZ_IN_TOPIC;
|
||||
char domoticz_out_topic[] = DOMOTICZ_OUT_TOPIC;
|
||||
|
@ -347,8 +347,15 @@ void DomoticzSensor(uint8_t idx, char *data)
|
|||
Response_P(PSTR("{\"idx\":%d,\"nvalue\":%s,\"Battery\":%d,\"RSSI\":%d}"),
|
||||
Settings.domoticz_sensor_idx[idx], data, DomoticzBatteryQuality(), DomoticzRssiQuality());
|
||||
} else {
|
||||
uint8_t nvalue = 0;
|
||||
#ifdef USE_SHUTTER
|
||||
if (DZ_SHUTTER == idx) {
|
||||
uint8_t position = atoi(data);
|
||||
nvalue = position < 2 ? 0 : (position == 100 ? 1 : 2);
|
||||
}
|
||||
#endif // USE_SHUTTER
|
||||
Response_P(DOMOTICZ_MESSAGE,
|
||||
Settings.domoticz_sensor_idx[idx], 0, data, DomoticzBatteryQuality(), DomoticzRssiQuality());
|
||||
Settings.domoticz_sensor_idx[idx], nvalue, data, DomoticzBatteryQuality(), DomoticzRssiQuality());
|
||||
}
|
||||
MqttPublish(domoticz_in_topic);
|
||||
memcpy(mqtt_data, dmess, sizeof(dmess));
|
||||
|
|
|
@ -97,7 +97,7 @@ const char kCompareOperators[] PROGMEM = "=\0>\0<\0|\0==!=>=<=";
|
|||
#ifdef USE_EXPRESSION
|
||||
#include <LinkedList.h> // Import LinkedList library
|
||||
|
||||
const char kExpressionOperators[] PROGMEM = "+-*/%^";
|
||||
const char kExpressionOperators[] PROGMEM = "+-*/%^\0";
|
||||
#define EXPRESSION_OPERATOR_ADD 0
|
||||
#define EXPRESSION_OPERATOR_SUBTRACT 1
|
||||
#define EXPRESSION_OPERATOR_MULTIPLY 2
|
||||
|
@ -259,8 +259,17 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
|
|||
JsonObject &root = jsonBuf.parseObject(event);
|
||||
if (!root.success()) { return false; } // No valid JSON data
|
||||
|
||||
float value = 0;
|
||||
const char* str_value = root[rule_task][rule_name];
|
||||
const char* str_value;
|
||||
if ((pos = rule_name.indexOf("[")) > 0) { // "CURRENT[1]"
|
||||
int rule_name_idx = rule_name.substring(pos +1).toInt();
|
||||
if ((rule_name_idx < 1) || (rule_name_idx > 6)) { // Allow indexes 1 to 6
|
||||
rule_name_idx = 1;
|
||||
}
|
||||
rule_name = rule_name.substring(0, pos); // "CURRENT"
|
||||
str_value = root[rule_task][rule_name][rule_name_idx -1]; // "ENERGY" and "CURRENT" and 0
|
||||
} else {
|
||||
str_value = root[rule_task][rule_name]; // "INA219" and "CURRENT"
|
||||
}
|
||||
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
|
||||
// rule_task.c_str(), rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none");
|
||||
|
@ -271,6 +280,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
|
|||
Rules.event_value = str_value; // Prepare %value%
|
||||
|
||||
// Step 3: Compare rule (value)
|
||||
float value = 0;
|
||||
if (str_value) {
|
||||
value = CharToFloat((char*)str_value);
|
||||
int int_value = int(value);
|
||||
|
@ -595,6 +605,10 @@ void RulesEvery50ms(void)
|
|||
case 5: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Connected\":1}}"), sizeof(json_event)); break;
|
||||
case 6: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Disconnected\":1}}"), sizeof(json_event)); break;
|
||||
case 7: strncpy_P(json_event, PSTR("{\"HTTP\":{\"Initialized\":1}}"), sizeof(json_event)); break;
|
||||
#ifdef USE_SHUTTER
|
||||
case 8: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moved\":1}}"), sizeof(json_event)); break;
|
||||
case 9: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moving\":1}}"), sizeof(json_event)); break;
|
||||
#endif // USE_SHUTTER
|
||||
}
|
||||
if (json_event[0]) {
|
||||
RulesProcessEvent(json_event);
|
||||
|
@ -909,6 +923,10 @@ bool findNextNumber(char * &pNumber, float &value)
|
|||
{
|
||||
bool bSucceed = false;
|
||||
String sNumber = "";
|
||||
if (*pNumber == '-') {
|
||||
sNumber = "-";
|
||||
pNumber++;
|
||||
}
|
||||
while (*pNumber) {
|
||||
if (isdigit(*pNumber) || (*pNumber == '.')) {
|
||||
sNumber += *pNumber;
|
||||
|
@ -986,7 +1004,7 @@ bool findNextVariableValue(char * &pVarname, float &value)
|
|||
/*
|
||||
* Find next object in expression and evaluate it
|
||||
* An object could be:
|
||||
* - A float number start with a digit, like 0.787
|
||||
* - A float number start with a digit or minus, like 0.787, -3
|
||||
* - A variable name, like VAR1, MEM3
|
||||
* - An expression enclosed with a pair of round brackets, (.....)
|
||||
* Input:
|
||||
|
@ -1008,7 +1026,7 @@ bool findNextObjectValue(char * &pointer, float &value)
|
|||
pointer++;
|
||||
continue;
|
||||
}
|
||||
if (isdigit(*pointer)) { //This object is a number
|
||||
if (isdigit(*pointer) || (*pointer) == '-') { //This object is a number
|
||||
bSucceed = findNextNumber(pointer, value);
|
||||
break;
|
||||
} else if (isalpha(*pointer)) { //Should be a variable like VAR12, MEM1
|
||||
|
@ -1017,7 +1035,7 @@ bool findNextObjectValue(char * &pointer, float &value)
|
|||
} else if (*pointer == '(') { //It is a sub expression bracketed with ()
|
||||
char * closureBracket = findClosureBracket(pointer); //Get the position of closure bracket ")"
|
||||
if (closureBracket != nullptr) {
|
||||
value = evaluateExpression(pointer+1, closureBracket - pointer - 2);
|
||||
value = evaluateExpression(pointer+1, closureBracket - pointer - 1);
|
||||
pointer = closureBracket + 1;
|
||||
bSucceed = true;
|
||||
}
|
||||
|
@ -1052,10 +1070,16 @@ bool findNextOperator(char * &pointer, int8_t &op)
|
|||
pointer++;
|
||||
continue;
|
||||
}
|
||||
if (char *pch = strchr(kExpressionOperators, *pointer)) { //If it is an operator
|
||||
op = (int8_t)(pch - kExpressionOperators);
|
||||
pointer++;
|
||||
bSucceed = true;
|
||||
op = EXPRESSION_OPERATOR_ADD;
|
||||
const char *pch = kExpressionOperators;
|
||||
char ch;
|
||||
while ((ch = pgm_read_byte(pch++)) != '\0') {
|
||||
if (ch == *pointer) {
|
||||
bSucceed = true;
|
||||
pointer++;
|
||||
break;
|
||||
}
|
||||
op++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1163,7 +1187,7 @@ float evaluateExpression(const char * expression, unsigned int len)
|
|||
for (int32_t priority = MAX_EXPRESSION_OPERATOR_PRIORITY; priority>0; priority--) {
|
||||
int index = 0;
|
||||
while (index < operators.size()) {
|
||||
if (priority == kExpressionOperatorsPriorities[(operators.get(index))]) { //need to calculate the operator first
|
||||
if (priority == pgm_read_byte(kExpressionOperatorsPriorities + operators.get(index))) { //need to calculate the operator first
|
||||
//get current object value and remove the next object with current operator
|
||||
va = calculateTwoValues(object_values.get(index), object_values.remove(index + 1), operators.remove(index));
|
||||
//Replace the current value with the result
|
||||
|
@ -1313,7 +1337,7 @@ bool findNextLogicObjectValue(char * &pointer, bool &value)
|
|||
} else if (*pointer == '(') { //It is a sub expression bracketed with ()
|
||||
char * closureBracket = findClosureBracket(pointer); //Get the position of closure bracket ")"
|
||||
if (closureBracket != nullptr) {
|
||||
value = evaluateLogicalExpression(pointer+1, closureBracket - pointer - 2);
|
||||
value = evaluateLogicalExpression(pointer+1, closureBracket - pointer - 1);
|
||||
pointer = closureBracket + 1;
|
||||
bSucceed = true;
|
||||
}
|
||||
|
@ -1861,9 +1885,6 @@ bool Xdrv10(uint8_t function)
|
|||
bool result = false;
|
||||
|
||||
switch (function) {
|
||||
case FUNC_PRE_INIT:
|
||||
RulesInit();
|
||||
break;
|
||||
case FUNC_EVERY_50_MSECOND:
|
||||
RulesEvery50ms();
|
||||
break;
|
||||
|
@ -1890,6 +1911,9 @@ bool Xdrv10(uint8_t function)
|
|||
result = RulesMqttData();
|
||||
break;
|
||||
#endif // SUPPORT_MQTT_EVENT
|
||||
case FUNC_PRE_INIT:
|
||||
RulesInit();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -41,9 +41,15 @@ keywords if then else endif, or, and are better readable for beginners (others m
|
|||
|
||||
#define SCRIPT_DEBUG 0
|
||||
|
||||
|
||||
#ifndef MAXVARS
|
||||
#define MAXVARS 50
|
||||
#define MAXNVARS 45
|
||||
#endif
|
||||
#ifndef MAXSVARS
|
||||
#define MAXSVARS 5
|
||||
#endif
|
||||
#define MAXNVARS MAXVARS-MAXSVARS
|
||||
|
||||
#define MAXFILT 5
|
||||
#define SCRIPT_SVARSIZE 20
|
||||
#define SCRIPT_MAXSSIZE 48
|
||||
|
@ -61,7 +67,9 @@ enum {SCRIPT_LOGLEVEL=1,SCRIPT_TELEPERIOD};
|
|||
#ifdef USE_SCRIPT_FATFS
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#ifndef FAT_SCRIPT_SIZE
|
||||
#define FAT_SCRIPT_SIZE 4096
|
||||
#endif
|
||||
#define FAT_SCRIPT_NAME "script.txt"
|
||||
#if USE_LONG_FILE_NAMES==1
|
||||
#warning ("FATFS long filenames not supported");
|
||||
|
@ -214,7 +222,11 @@ void RulesTeleperiod(void) {
|
|||
#include <Eeprom24C128_256.h>
|
||||
#define EEPROM_ADDRESS 0x50
|
||||
// strange bug, crashes with powers of 2 ??? 4096 crashes
|
||||
#ifndef EEP_SCRIPT_SIZE
|
||||
#define EEP_SCRIPT_SIZE 4095
|
||||
#endif
|
||||
|
||||
|
||||
static Eeprom24C128_256 eeprom(EEPROM_ADDRESS);
|
||||
// eeprom.writeBytes(address, length, buffer);
|
||||
#define EEP_WRITE(A,B,C) eeprom.writeBytes(A,B,(uint8_t*)C);
|
||||
|
@ -465,7 +477,13 @@ char *script;
|
|||
}
|
||||
namep++;
|
||||
index++;
|
||||
if (index>255) {
|
||||
free(glob_script_mem.script_mem);
|
||||
return -5;
|
||||
}
|
||||
}
|
||||
// variables usage info
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("Script: nv=%d, tv=%d, vns=%d, ram=%d"), nvars, svars, index, glob_script_mem.script_mem_size);
|
||||
|
||||
// copy string variables
|
||||
char *cp1=glob_script_mem.glob_snp;
|
||||
|
@ -718,7 +736,7 @@ float DoMedian5(uint8_t index, float in) {
|
|||
}
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
#ifdef USE_WS2812
|
||||
//#ifdef USE_WS2812
|
||||
uint32_t HSVToRGB(uint16_t hue, uint8_t saturation, uint8_t value) {
|
||||
float r = 0, g = 0, b = 0;
|
||||
struct HSV {
|
||||
|
@ -801,7 +819,7 @@ if (hsv.S == 0) {
|
|||
return rgb;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
// vtype => ff=nothing found, fe=constant number,fd = constant string else bit 7 => 80 = string, 0 = number
|
||||
// no flash strings here for performance reasons!!!
|
||||
|
@ -1103,7 +1121,7 @@ chknext:
|
|||
fvar=cnt;
|
||||
glob_script_mem.file_flags[cnt].is_open=1;
|
||||
} else {
|
||||
toLog("file open failed");
|
||||
AddLog_P(LOG_LEVEL_INFO,PSTR("file open failed"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1339,8 +1357,17 @@ chknext:
|
|||
}
|
||||
goto strexit;
|
||||
}
|
||||
if (!strncmp(vname,"hx(",3)) {
|
||||
lp=GetNumericResult(lp+3,OPER_EQU,&fvar,0);
|
||||
lp++;
|
||||
len=0;
|
||||
if (sp) {
|
||||
sprintf(sp,"%08x",(uint32_t)fvar);
|
||||
}
|
||||
goto strexit;
|
||||
}
|
||||
#ifdef USE_LIGHT
|
||||
#ifdef USE_WS2812
|
||||
//#ifdef USE_WS2812
|
||||
if (!strncmp(vname,"hsvrgb(",7)) {
|
||||
lp=GetNumericResult(lp+7,OPER_EQU,&fvar,0);
|
||||
if (fvar<0 || fvar>360) fvar=0;
|
||||
|
@ -1361,7 +1388,7 @@ chknext:
|
|||
len=0;
|
||||
goto exit;
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
#endif
|
||||
break;
|
||||
case 'i':
|
||||
|
@ -1619,6 +1646,20 @@ chknext:
|
|||
goto exit;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SHUTTER
|
||||
if (!strncmp(vname,"sht[",4)) {
|
||||
GetNumericResult(vname+4,OPER_EQU,&fvar,0);
|
||||
uint8_t index=fvar;
|
||||
if (index<=shutters_present) {
|
||||
fvar=Settings.shutter_position[index-1];
|
||||
} else {
|
||||
fvar=-1;
|
||||
}
|
||||
len+=1;
|
||||
goto exit;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case 't':
|
||||
if (!strncmp(vname,"time",4)) {
|
||||
|
@ -1903,6 +1944,11 @@ char *GetStringResult(char *lp,uint8_t lastop,char *cp,JsonObject *jo) {
|
|||
char str[SCRIPT_MAXSSIZE],str1[SCRIPT_MAXSSIZE];
|
||||
while (1) {
|
||||
lp=isvar(lp,&vtype,&ind,0,str1,jo);
|
||||
if (vtype!=STR_RES && !(vtype&STYPE)) {
|
||||
// numeric type
|
||||
glob_script_mem.glob_error=1;
|
||||
return lp;
|
||||
}
|
||||
switch (lastop) {
|
||||
case OPER_EQU:
|
||||
strlcpy(str,str1,sizeof(str));
|
||||
|
@ -2015,13 +2061,13 @@ struct T_INDEX ind;
|
|||
char *ForceStringVar(char *lp,char *dstr) {
|
||||
float fvar;
|
||||
char *slp=lp;
|
||||
glob_script_mem.var_not_found=0;
|
||||
glob_script_mem.glob_error=0;
|
||||
lp=GetStringResult(lp,OPER_EQU,dstr,0);
|
||||
if (glob_script_mem.var_not_found) {
|
||||
if (glob_script_mem.glob_error) {
|
||||
// mismatch
|
||||
lp=GetNumericResult(slp,OPER_EQU,&fvar,0);
|
||||
dtostrfd(fvar,6,dstr);
|
||||
glob_script_mem.var_not_found=0;
|
||||
glob_script_mem.glob_error=0;
|
||||
}
|
||||
return lp;
|
||||
}
|
||||
|
@ -2089,6 +2135,7 @@ void toLog(const char *str) {
|
|||
AddLog(LOG_LEVEL_INFO);
|
||||
}
|
||||
|
||||
|
||||
void toLogN(const char *cp,uint8_t len) {
|
||||
if (!cp) return;
|
||||
char str[32];
|
||||
|
@ -2281,12 +2328,10 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
|
|||
if (section) {
|
||||
// we are in section
|
||||
if (*lp=='>') {
|
||||
section=0;
|
||||
break;
|
||||
return 0;
|
||||
}
|
||||
if (*lp=='#') {
|
||||
section=0;
|
||||
break;
|
||||
return 0;
|
||||
}
|
||||
glob_script_mem.var_not_found=0;
|
||||
|
||||
|
@ -2718,14 +2763,15 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
|
|||
sindex=index;
|
||||
// string result
|
||||
char str[SCRIPT_MAXSSIZE];
|
||||
char *slp=lp;
|
||||
lp=getop(lp,&lastop);
|
||||
char *slp=lp;
|
||||
glob_script_mem.glob_error=0;
|
||||
lp=GetStringResult(lp,OPER_EQU,str,jo);
|
||||
if (!js && glob_script_mem.var_not_found) {
|
||||
if (!js && glob_script_mem.glob_error) {
|
||||
// mismatch
|
||||
lp=GetNumericResult(slp,OPER_EQU,&fvar,0);
|
||||
dtostrfd(fvar,6,str);
|
||||
glob_script_mem.var_not_found=0;
|
||||
glob_script_mem.glob_error=0;
|
||||
}
|
||||
|
||||
if (!glob_script_mem.var_not_found) {
|
||||
|
@ -2764,19 +2810,20 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
|
|||
return 99;
|
||||
}
|
||||
// check for subroutine
|
||||
if (*type=='#') {
|
||||
char *ctype=(char*)type;
|
||||
if (*ctype=='#') {
|
||||
// check for parameter
|
||||
type+=tlen;
|
||||
if (*type=='(') {
|
||||
ctype+=tlen;
|
||||
if (*ctype=='(' && *(lp+tlen)=='(') {
|
||||
float fparam;
|
||||
numeric=1;
|
||||
glob_script_mem.glob_error=0;
|
||||
GetNumericResult((char*)type,OPER_EQU,&fparam,0);
|
||||
GetNumericResult((char*)ctype,OPER_EQU,&fparam,0);
|
||||
if (glob_script_mem.glob_error==1) {
|
||||
// was string, not number
|
||||
numeric=0;
|
||||
// get the string
|
||||
GetStringResult((char*)type+1,OPER_EQU,cmpstr,0);
|
||||
GetStringResult((char*)ctype+1,OPER_EQU,cmpstr,0);
|
||||
}
|
||||
lp+=tlen;
|
||||
if (*lp=='(') {
|
||||
|
@ -2807,6 +2854,12 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lp+=tlen;
|
||||
if (*ctype=='(' || (*lp!=SCRIPT_EOL && *lp!='?')) {
|
||||
// revert
|
||||
section=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2817,11 +2870,17 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
|
|||
lp++;
|
||||
} else {
|
||||
lp = strchr(lp, SCRIPT_EOL);
|
||||
if (!lp) break;
|
||||
if (!lp) {
|
||||
if (section) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
lp++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t script_xsns_index = 0;
|
||||
|
@ -3137,13 +3196,13 @@ uint8_t DownloadFile(char *file) {
|
|||
WiFiClient download_Client;
|
||||
|
||||
if (!SD.exists(file)) {
|
||||
toLog("file not found");
|
||||
AddLog_P(LOG_LEVEL_INFO,PSTR("file not found"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
download_file=SD.open(file,FILE_READ);
|
||||
if (!download_file) {
|
||||
toLog("could not open file");
|
||||
AddLog_P(LOG_LEVEL_INFO,PSTR("could not open file"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -3306,6 +3365,386 @@ void ScriptSaveSettings(void) {
|
|||
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT)
|
||||
|
||||
/*
|
||||
"state": {
|
||||
"temperature": 2674,
|
||||
"lastupdated": "2017-08-04T12:13:04"
|
||||
},
|
||||
"config": {
|
||||
"on": true,
|
||||
"battery": 100,
|
||||
"reachable": true,
|
||||
"alert": "none",
|
||||
"ledindication": false,
|
||||
"usertest": false,
|
||||
"pending": []
|
||||
},
|
||||
"name": "Hue temperature sensor 1",
|
||||
"type": "ZLLTemperature",
|
||||
"modelid": "SML001",
|
||||
"manufacturername": "Philips",
|
||||
"swversion": "6.1.0.18912",
|
||||
"uniqueid": "xxx"
|
||||
}
|
||||
*/
|
||||
|
||||
#define HUE_DEV_MVNUM 5
|
||||
#define HUE_DEV_NSIZE 16
|
||||
struct HUE_SCRIPT {
|
||||
char name[HUE_DEV_NSIZE];
|
||||
uint8_t type;
|
||||
uint8_t index[HUE_DEV_MVNUM];
|
||||
uint8_t vindex[HUE_DEV_MVNUM];
|
||||
} hue_script[32];
|
||||
|
||||
|
||||
const char SCRIPT_HUE_LIGHTS_STATUS_JSON1[] PROGMEM =
|
||||
"{\"state\":"
|
||||
"{\"on\":{state},"
|
||||
"{light_status}"
|
||||
"\"alert\":\"none\","
|
||||
"\"effect\":\"none\","
|
||||
"\"reachable\":true}"
|
||||
",\"type\":\"{type}\","
|
||||
"\"name\":\"{j1\","
|
||||
"\"modelid\":\"LCT007\","
|
||||
"\"uniqueid\":\"{j2\","
|
||||
"\"swversion\":\"5.50.1.19085\"}";
|
||||
|
||||
|
||||
void Script_HueStatus(String *response, uint16_t hue_devs) {
|
||||
*response+=FPSTR(SCRIPT_HUE_LIGHTS_STATUS_JSON1);
|
||||
uint8_t pwr=glob_script_mem.fvars[hue_script[hue_devs].index[0]-1];
|
||||
response->replace("{state}", (pwr ? "true" : "false"));
|
||||
String light_status = "";
|
||||
if (hue_script[hue_devs].index[1]>0) {
|
||||
// bri
|
||||
light_status += "\"bri\":";
|
||||
uint32_t bri=glob_script_mem.fvars[hue_script[hue_devs].index[1]-1];
|
||||
if (bri > 254) bri = 254;
|
||||
if (bri < 1) bri = 1;
|
||||
light_status += String(bri);
|
||||
light_status += ",";
|
||||
}
|
||||
if (hue_script[hue_devs].index[2]>0) {
|
||||
// hue
|
||||
uint32_t hue=glob_script_mem.fvars[hue_script[hue_devs].index[2]-1];
|
||||
//hue = changeUIntScale(hue, 0, 359, 0, 65535);
|
||||
light_status += "\"hue\":";
|
||||
light_status += String(hue);
|
||||
light_status += ",";
|
||||
}
|
||||
if (hue_script[hue_devs].index[3]>0) {
|
||||
// sat
|
||||
uint32_t sat=glob_script_mem.fvars[hue_script[hue_devs].index[3]-1] ;
|
||||
if (sat > 254) sat = 254;
|
||||
if (sat < 1) sat = 1;
|
||||
light_status += "\"sat\":";
|
||||
light_status += String(sat);
|
||||
light_status += ",";
|
||||
}
|
||||
if (hue_script[hue_devs].index[4]>0) {
|
||||
// ct
|
||||
uint32_t ct=glob_script_mem.fvars[hue_script[hue_devs].index[4]-1];
|
||||
light_status += "\"ct\":";
|
||||
light_status += String(ct);
|
||||
light_status += ",";
|
||||
}
|
||||
|
||||
response->replace("{light_status}", light_status);
|
||||
response->replace("{j1",hue_script[hue_devs].name);
|
||||
response->replace("{j2", GetHueDeviceId(hue_devs<<8));
|
||||
|
||||
if (hue_script[hue_devs].type=='E') {
|
||||
response->replace("{type}","Extended color light");
|
||||
} else {
|
||||
response->replace("{type}","color light");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Script_Check_Hue(String *response) {
|
||||
if (!bitRead(Settings.rule_enabled, 0)) return;
|
||||
|
||||
uint8_t hue_script_found=Run_Scripter(">H",-2,0);
|
||||
if (hue_script_found!=99) return;
|
||||
|
||||
char line[128];
|
||||
char tmp[128];
|
||||
uint8_t hue_devs=0;
|
||||
uint8_t vindex=0;
|
||||
char *cp;
|
||||
char *lp=glob_script_mem.section_ptr+2;
|
||||
while (lp) {
|
||||
SCRIPT_SKIP_SPACES
|
||||
while (*lp==SCRIPT_EOL) {
|
||||
lp++;
|
||||
}
|
||||
if (!*lp || *lp=='#' || *lp=='>') {
|
||||
break;
|
||||
}
|
||||
if (*lp!=';') {
|
||||
// check this line
|
||||
memcpy(line,lp,sizeof(line));
|
||||
line[sizeof(line)-1]=0;
|
||||
cp=line;
|
||||
for (uint32_t i=0; i<sizeof(line); i++) {
|
||||
if (!*cp || *cp=='\n' || *cp=='\r') {
|
||||
*cp=0;
|
||||
break;
|
||||
}
|
||||
cp++;
|
||||
}
|
||||
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
|
||||
// check for hue defintions
|
||||
// NAME, TYPE , vars
|
||||
cp=tmp;
|
||||
cp=strchr(cp,',');
|
||||
if (!cp) break;
|
||||
*cp=0;
|
||||
// copy name
|
||||
strlcpy(hue_script[hue_devs].name,tmp,HUE_DEV_NSIZE);
|
||||
cp++;
|
||||
while (*cp==' ') cp++;
|
||||
// get type
|
||||
hue_script[hue_devs].type=*cp;
|
||||
|
||||
for (vindex=0;vindex<HUE_DEV_MVNUM;vindex++) {
|
||||
hue_script[hue_devs].index[vindex]=0;
|
||||
}
|
||||
vindex=0;
|
||||
while (1) {
|
||||
cp=strchr(cp,',');
|
||||
if (!cp) break;
|
||||
// get vars, on,hue,sat,bri,ct,
|
||||
cp++;
|
||||
while (*cp==' ') cp++;
|
||||
|
||||
vindex==0xff;
|
||||
if (!strncmp(cp,"on=",3)) {
|
||||
cp+=3;
|
||||
vindex=0;
|
||||
} else if (!strncmp(cp,"bri=",4)) {
|
||||
cp+=4;
|
||||
vindex=1;
|
||||
} else if (!strncmp(cp,"hue=",4)) {
|
||||
cp+=4;
|
||||
vindex=2;
|
||||
} else if (!strncmp(cp,"sat=",4)) {
|
||||
cp+=4;
|
||||
vindex=3;
|
||||
} else if (!strncmp(cp,"ct=",3)) {
|
||||
cp+=3;
|
||||
vindex=4;
|
||||
} else {
|
||||
// error
|
||||
vindex==0xff;
|
||||
break;
|
||||
}
|
||||
if (vindex!=0xff) {
|
||||
struct T_INDEX ind;
|
||||
uint8_t vtype;
|
||||
char vname[16];
|
||||
for (uint32_t cnt=0;cnt<sizeof(vname)-1;cnt++) {
|
||||
if (*cp==',' || *cp==0) {
|
||||
vname[cnt]=0;
|
||||
break;
|
||||
}
|
||||
vname[cnt]=*cp++;
|
||||
}
|
||||
isvar(vname,&vtype,&ind,0,0,0);
|
||||
if (vtype!=VAR_NV) {
|
||||
// found variable as result
|
||||
if (vtype==NUM_RES || (vtype&STYPE)==0) {
|
||||
hue_script[hue_devs].vindex[vindex]=ind.index;
|
||||
hue_script[hue_devs].index[vindex]=glob_script_mem.type[ind.index].index+1;
|
||||
} else {
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// append response
|
||||
if (response) {
|
||||
*response+=",\""+String(EncodeLightId(hue_devs+devices_present+1))+"\":";
|
||||
Script_HueStatus(response,hue_devs);
|
||||
}
|
||||
|
||||
hue_devs++;
|
||||
}
|
||||
if (*lp==SCRIPT_EOL) {
|
||||
lp++;
|
||||
} else {
|
||||
lp = strchr(lp, SCRIPT_EOL);
|
||||
if (!lp) break;
|
||||
lp++;
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Hue: %d"), hue_devs);
|
||||
toLog(">>>>");
|
||||
toLog(response->c_str());
|
||||
toLog(response->c_str()+LOGSZ);
|
||||
#endif
|
||||
}
|
||||
|
||||
const char sHUE_LIGHT_RESPONSE_JSON[] PROGMEM =
|
||||
"{\"success\":{\"/lights/{id/state/{cm\":{re}}";
|
||||
const char sHUE_ERROR_JSON[] PROGMEM =
|
||||
"[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
|
||||
|
||||
|
||||
// get alexa arguments
|
||||
void Script_Handle_Hue(String *path) {
|
||||
String response;
|
||||
int code = 200;
|
||||
uint16_t tmp = 0;
|
||||
uint16_t hue = 0;
|
||||
uint8_t sat = 0;
|
||||
uint8_t bri = 254;
|
||||
uint16_t ct = 0;
|
||||
bool resp = false;
|
||||
|
||||
uint8_t device = DecodeLightId(atoi(path->c_str()));
|
||||
uint8_t index = device-devices_present-1;
|
||||
|
||||
if (WebServer->args()) {
|
||||
response = "[";
|
||||
|
||||
StaticJsonBuffer<400> jsonBuffer;
|
||||
JsonObject &hue_json = jsonBuffer.parseObject(WebServer->arg((WebServer->args())-1));
|
||||
if (hue_json.containsKey("on")) {
|
||||
|
||||
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
||||
response.replace("{id", String(EncodeLightId(device)));
|
||||
response.replace("{cm", "on");
|
||||
|
||||
bool on = hue_json["on"];
|
||||
switch(on)
|
||||
{
|
||||
case false : glob_script_mem.fvars[hue_script[index].index[0]-1]=0;
|
||||
response.replace("{re", "false");
|
||||
break;
|
||||
case true : glob_script_mem.fvars[hue_script[index].index[0]-1]=1;
|
||||
response.replace("{re", "true");
|
||||
break;
|
||||
}
|
||||
glob_script_mem.type[hue_script[index].vindex[0]].bits.changed=1;
|
||||
resp = true;
|
||||
}
|
||||
if (hue_json.containsKey("bri")) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
|
||||
tmp = hue_json["bri"];
|
||||
bri=tmp;
|
||||
if (254 <= bri) { bri = 255; }
|
||||
if (resp) { response += ","; }
|
||||
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
||||
response.replace("{id", String(EncodeLightId(device)));
|
||||
response.replace("{cm", "bri");
|
||||
response.replace("{re", String(tmp));
|
||||
glob_script_mem.fvars[hue_script[index].index[1]-1]=bri;
|
||||
glob_script_mem.type[hue_script[index].vindex[1]].bits.changed=1;
|
||||
resp = true;
|
||||
}
|
||||
if (hue_json.containsKey("hue")) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
|
||||
tmp = hue_json["hue"];
|
||||
//hue = changeUIntScale(tmp, 0, 65535, 0, 359);
|
||||
//tmp = changeUIntScale(hue, 0, 359, 0, 65535);
|
||||
hue=tmp;
|
||||
if (resp) { response += ","; }
|
||||
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
||||
response.replace("{id", String(EncodeLightId(device)));
|
||||
response.replace("{cm", "hue");
|
||||
response.replace("{re", String(tmp));
|
||||
glob_script_mem.fvars[hue_script[index].index[2]-1]=hue;
|
||||
glob_script_mem.type[hue_script[index].vindex[2]].bits.changed=1;
|
||||
resp = true;
|
||||
}
|
||||
if (hue_json.containsKey("sat")) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
|
||||
tmp = hue_json["sat"];
|
||||
sat=tmp;
|
||||
if (254 <= sat) { sat = 255; }
|
||||
if (resp) { response += ","; }
|
||||
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
||||
response.replace("{id", String(EncodeLightId(device)));
|
||||
response.replace("{cm", "sat");
|
||||
response.replace("{re", String(tmp));
|
||||
glob_script_mem.fvars[hue_script[index].index[3]-1]=sat;
|
||||
glob_script_mem.type[hue_script[index].vindex[3]].bits.changed=1;
|
||||
resp = true;
|
||||
}
|
||||
if (hue_json.containsKey("ct")) { // Color temperature 153 (Cold) to 500 (Warm)
|
||||
ct = hue_json["ct"];
|
||||
if (resp) { response += ","; }
|
||||
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
||||
response.replace("{id", String(EncodeLightId(device)));
|
||||
response.replace("{cm", "ct");
|
||||
response.replace("{re", String(ct));
|
||||
glob_script_mem.fvars[hue_script[index].index[4]-1]=ct;
|
||||
glob_script_mem.type[hue_script[index].vindex[4]].bits.changed=1;
|
||||
resp = true;
|
||||
}
|
||||
response += "]";
|
||||
|
||||
} else {
|
||||
response = FPSTR(sHUE_ERROR_JSON);
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
|
||||
WSSend(code, CT_JSON, response);
|
||||
if (resp) {
|
||||
Run_Scripter(">E",2,0);
|
||||
}
|
||||
}
|
||||
#endif // hue interface
|
||||
|
||||
|
||||
#ifdef USE_SCRIPT_SUB_COMMAND
|
||||
bool Script_SubCmd(void) {
|
||||
if (!bitRead(Settings.rule_enabled, 0)) return false;
|
||||
|
||||
if (tasm_cmd_activ) return false;
|
||||
|
||||
char command[CMDSZ];
|
||||
strlcpy(command,XdrvMailbox.topic,CMDSZ);
|
||||
uint32_t pl=XdrvMailbox.payload;
|
||||
char pld[64];
|
||||
strlcpy(pld,XdrvMailbox.data,sizeof(pld));
|
||||
|
||||
char cmdbuff[128];
|
||||
char *cp=cmdbuff;
|
||||
*cp++='#';
|
||||
strcpy(cp,XdrvMailbox.topic);
|
||||
uint8_t tlen=strlen(XdrvMailbox.topic);
|
||||
cp+=tlen;
|
||||
if (XdrvMailbox.index > 0) {
|
||||
*cp++=XdrvMailbox.index|0x30;
|
||||
tlen++;
|
||||
}
|
||||
if ((XdrvMailbox.payload>0) || (XdrvMailbox.data_len>0)) {
|
||||
*cp++='(';
|
||||
strncpy(cp,XdrvMailbox.data,XdrvMailbox.data_len);
|
||||
cp+=XdrvMailbox.data_len;
|
||||
*cp++=')';
|
||||
*cp=0;
|
||||
}
|
||||
//toLog(cmdbuff);
|
||||
uint32_t res=Run_Scripter(cmdbuff,tlen+1,0);
|
||||
//AddLog_P2(LOG_LEVEL_INFO,">>%d",res);
|
||||
if (res) return false;
|
||||
else {
|
||||
if (pl>=0) {
|
||||
Response_P(S_JSON_COMMAND_NVALUE, command, pl);
|
||||
} else {
|
||||
Response_P(S_JSON_COMMAND_SVALUE, command, pld);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void execute_script(char *script) {
|
||||
char *svd_sp=glob_script_mem.scriptptr;
|
||||
strcat(script,"\n#");
|
||||
|
@ -3325,6 +3764,8 @@ bool ScriptCommand(void) {
|
|||
bool serviced = true;
|
||||
uint8_t index = XdrvMailbox.index;
|
||||
|
||||
if (tasm_cmd_activ) return false;
|
||||
|
||||
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kScriptCommands);
|
||||
if (-1 == command_code) {
|
||||
serviced = false; // Unknown command
|
||||
|
@ -3351,15 +3792,15 @@ bool ScriptCommand(void) {
|
|||
return serviced;
|
||||
}
|
||||
snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\",\"Free\":%d}"),command, GetStateText(bitRead(Settings.rule_enabled,0)),glob_script_mem.script_size-strlen(glob_script_mem.script_ram));
|
||||
#ifdef SUPPORT_MQTT_EVENT
|
||||
#ifdef SUPPORT_MQTT_EVENT
|
||||
} else if (CMND_SUBSCRIBE == command_code) { //MQTT Subscribe command. Subscribe <Event>, <Topic> [, <Key>]
|
||||
String result = ScriptSubscribe(XdrvMailbox.data, XdrvMailbox.data_len);
|
||||
Response_P(S_JSON_COMMAND_SVALUE, command, result.c_str());
|
||||
} else if (CMND_UNSUBSCRIBE == command_code) { //MQTT Un-subscribe command. UnSubscribe <Event>
|
||||
String result = ScriptUnsubscribe(XdrvMailbox.data, XdrvMailbox.data_len);
|
||||
Response_P(S_JSON_COMMAND_SVALUE, command, result.c_str());
|
||||
#endif //SUPPORT_MQTT_EVENT
|
||||
}
|
||||
#endif //SUPPORT_MQTT_EVENT
|
||||
}
|
||||
return serviced;
|
||||
}
|
||||
|
||||
|
@ -3614,24 +4055,43 @@ void Script_Check_HTML_Setvars(void) {
|
|||
|
||||
//toLog(cmdbuf);
|
||||
execute_script(cmdbuf);
|
||||
Run_Scripter(">E",2,0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char SCRIPT_MSG_BUTTONa[] PROGMEM =
|
||||
"<button type='submit' style=\"width:%d%%\" onclick='seva(%d,\"%s\")'>%s</button>";
|
||||
|
||||
const char SCRIPT_MSG_BUTTONa_TBL[] PROGMEM =
|
||||
"<td style=\"width:%d%%\"><button type='submit' onclick='seva(%d,\"%s\")'>%s</button></td>";
|
||||
|
||||
const char SCRIPT_MSG_BUTTONb[] PROGMEM =
|
||||
"<img width=\"%d%%\"><\img>";
|
||||
|
||||
const char SCRIPT_MSG_BUT_START[] PROGMEM =
|
||||
"<div>";
|
||||
const char SCRIPT_MSG_BUT_START_TBL[] PROGMEM =
|
||||
"<table style='width:100%%'><tr>";
|
||||
|
||||
const char SCRIPT_MSG_BUT_STOP[] PROGMEM =
|
||||
"</div>";
|
||||
const char SCRIPT_MSG_BUT_STOP_TBL[] PROGMEM =
|
||||
"</tr></table>";
|
||||
|
||||
const char SCRIPT_MSG_SLIDER[] PROGMEM =
|
||||
"<div><span class='p'>%s</span><center><b>%s</b><span class='q'>%s</span></div>"
|
||||
"<div><input type='range' min='%d' max='%d' value='%d' onchange='seva(value,\"%s\")'></div>";
|
||||
|
||||
const char SCRIPT_MSG_BUTTON[] PROGMEM =
|
||||
"<div><button type='submit' onclick='seva(%d,\"%s\")'>%s</button></div>";
|
||||
|
||||
const char SCRIPT_MSG_CHKBOX[] PROGMEM =
|
||||
"<div><center><label><b>%s</b><input type='checkbox' %s onchange='seva(%d,\"%s\")'></label></div>";
|
||||
|
||||
const char SCRIPT_MSG_TEXTINP[] PROGMEM =
|
||||
"<div><center><label><b>%s</b><input type='text' value='%s' style='width:200px' onchange='seva(value,\"%s\")'></label></div>";
|
||||
"<div><center><label><b>%s</b><input type='text' value='%s' style='width:200px' onfocusin='pr(0)' onfocusout='pr(1)' onchange='siva(value,\"%s\")'></label></div>";
|
||||
|
||||
const char SCRIPT_MSG_NUMINP[] PROGMEM =
|
||||
"<div><center><label><b>%s</b><input min='%s' max='%s' step='%s' value='%s' type='number' style='width:200px' onfocusin='pr(0)' onfocusout='pr(1)' onchange='siva(value,\"%s\")'></label></div>";
|
||||
|
||||
//<input onkeypress="if(event.key == 'Enter') {console.log('Test')}">
|
||||
//<input onBlur="if (this.value == '') { var field = this; setTimeout(function() { field.focus(); }, 0); }" type="text">
|
||||
|
||||
void ScriptGetVarname(char *nbuf,char *sp, uint32_t blen) {
|
||||
uint32_t cnt;
|
||||
|
@ -3649,6 +4109,7 @@ void ScriptWebShow(void) {
|
|||
if (web_script==99) {
|
||||
char line[128];
|
||||
char tmp[128];
|
||||
uint8_t optflg=0;
|
||||
char *lp=glob_script_mem.section_ptr+2;
|
||||
while (lp) {
|
||||
while (*lp==SCRIPT_EOL) {
|
||||
|
@ -3669,10 +4130,17 @@ void ScriptWebShow(void) {
|
|||
}
|
||||
cp++;
|
||||
}
|
||||
char *lin=line;
|
||||
if (*lin=='@') {
|
||||
lin++;
|
||||
optflg=1;
|
||||
} else {
|
||||
optflg=0;
|
||||
}
|
||||
// check for input elements
|
||||
if (!strncmp(line,"sl(",3)) {
|
||||
if (!strncmp(lin,"sl(",3)) {
|
||||
// insert slider sl(min max var left mid right)
|
||||
char *lp=line;
|
||||
char *lp=lin;
|
||||
float min;
|
||||
lp=GetNumericResult(lp+3,OPER_EQU,&min,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
|
@ -3701,8 +4169,8 @@ void ScriptWebShow(void) {
|
|||
WSContentSend_PD(SCRIPT_MSG_SLIDER,left,mid,right,(uint32_t)min,(uint32_t)max,(uint32_t)val,vname);
|
||||
|
||||
|
||||
} else if (!strncmp(line,"ck(",3)) {
|
||||
char *lp=line+3;
|
||||
} else if (!strncmp(lin,"ck(",3)) {
|
||||
char *lp=lin+3;
|
||||
char *slp=lp;
|
||||
float val;
|
||||
lp=GetNumericResult(lp,OPER_EQU,&val,0);
|
||||
|
@ -3713,7 +4181,7 @@ void ScriptWebShow(void) {
|
|||
|
||||
char label[SCRIPT_MAXSSIZE];
|
||||
lp=GetStringResult(lp,OPER_EQU,label,0);
|
||||
char *cp;
|
||||
const char *cp;
|
||||
uint8_t uval;
|
||||
if (val>0) {
|
||||
cp="checked='checked'";
|
||||
|
@ -3722,38 +4190,64 @@ void ScriptWebShow(void) {
|
|||
cp="";
|
||||
uval=1;
|
||||
}
|
||||
WSContentSend_PD(SCRIPT_MSG_CHKBOX,label,cp,uval,vname);
|
||||
WSContentSend_PD(SCRIPT_MSG_CHKBOX,label,(char*)cp,uval,vname);
|
||||
|
||||
} else if (!strncmp(line,"bu(",3)) {
|
||||
char *lp=line+3;
|
||||
char *slp=lp;
|
||||
float val;
|
||||
lp=GetNumericResult(lp,OPER_EQU,&val,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
|
||||
char vname[16];
|
||||
ScriptGetVarname(vname,slp,sizeof(vname));
|
||||
|
||||
SCRIPT_SKIP_SPACES
|
||||
char ontxt[SCRIPT_MAXSSIZE];
|
||||
lp=GetStringResult(lp,OPER_EQU,ontxt,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
char offtxt[SCRIPT_MAXSSIZE];
|
||||
lp=GetStringResult(lp,OPER_EQU,offtxt,0);
|
||||
|
||||
char *cp;
|
||||
uint8_t uval;
|
||||
if (val>0) {
|
||||
cp=ontxt;
|
||||
uval=0;
|
||||
} else {
|
||||
cp=offtxt;
|
||||
uval=1;
|
||||
} else if (!strncmp(lin,"bu(",3)) {
|
||||
char *lp=lin+3;
|
||||
uint8_t bcnt=0;
|
||||
char *found=lin;
|
||||
while (bcnt<4) {
|
||||
found=strstr(found,"bu(");
|
||||
if (!found) break;
|
||||
found+=3;
|
||||
bcnt++;
|
||||
}
|
||||
WSContentSend_PD(SCRIPT_MSG_BUTTON,uval,vname,cp);
|
||||
uint8_t proz=100/bcnt;
|
||||
if (!optflg && bcnt>1) proz-=2;
|
||||
if (optflg) WSContentSend_PD(SCRIPT_MSG_BUT_START_TBL);
|
||||
else WSContentSend_PD(SCRIPT_MSG_BUT_START);
|
||||
for (uint32_t cnt=0;cnt<bcnt;cnt++) {
|
||||
float val;
|
||||
char *slp=lp;
|
||||
lp=GetNumericResult(lp,OPER_EQU,&val,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
|
||||
} else if (!strncmp(line,"tx(",3)) {
|
||||
char *lp=line+3;
|
||||
char vname[16];
|
||||
ScriptGetVarname(vname,slp,sizeof(vname));
|
||||
|
||||
SCRIPT_SKIP_SPACES
|
||||
char ontxt[SCRIPT_MAXSSIZE];
|
||||
lp=GetStringResult(lp,OPER_EQU,ontxt,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
char offtxt[SCRIPT_MAXSSIZE];
|
||||
lp=GetStringResult(lp,OPER_EQU,offtxt,0);
|
||||
|
||||
char *cp;
|
||||
uint8_t uval;
|
||||
if (val>0) {
|
||||
cp=ontxt;
|
||||
uval=0;
|
||||
} else {
|
||||
cp=offtxt;
|
||||
uval=1;
|
||||
}
|
||||
if (bcnt>1 && cnt==bcnt-1) {
|
||||
if (!optflg) proz+=2;
|
||||
}
|
||||
if (!optflg) {
|
||||
WSContentSend_PD(SCRIPT_MSG_BUTTONa,proz,uval,vname,cp);
|
||||
} else {
|
||||
WSContentSend_PD(SCRIPT_MSG_BUTTONa_TBL,proz,uval,vname,cp);
|
||||
}
|
||||
if (bcnt>1 && cnt<bcnt-1) {
|
||||
if (!optflg) WSContentSend_PD(SCRIPT_MSG_BUTTONb,2);
|
||||
}
|
||||
lp+=4;
|
||||
}
|
||||
if (optflg) WSContentSend_PD(SCRIPT_MSG_BUT_STOP_TBL);
|
||||
else WSContentSend_PD(SCRIPT_MSG_BUT_STOP);
|
||||
} else if (!strncmp(lin,"tx(",3)) {
|
||||
char *lp=lin+3;
|
||||
char *slp=lp;
|
||||
char str[SCRIPT_MAXSSIZE];
|
||||
lp=ForceStringVar(lp,str);
|
||||
|
@ -3766,11 +4260,38 @@ void ScriptWebShow(void) {
|
|||
|
||||
WSContentSend_PD(SCRIPT_MSG_TEXTINP,label,str,vname);
|
||||
|
||||
}
|
||||
else {
|
||||
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
|
||||
if (tmp[0]=='@') {
|
||||
WSContentSend_PD(PSTR("<div>%s</div>"),&tmp[1]);
|
||||
} else if (!strncmp(lin,"nm(",3)) {
|
||||
char *lp=lin;
|
||||
float min;
|
||||
lp=GetNumericResult(lp+3,OPER_EQU,&min,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
float max;
|
||||
lp=GetNumericResult(lp,OPER_EQU,&max,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
float step;
|
||||
lp=GetNumericResult(lp,OPER_EQU,&step,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
float val;
|
||||
char *slp=lp;
|
||||
lp=GetNumericResult(lp,OPER_EQU,&val,0);
|
||||
SCRIPT_SKIP_SPACES
|
||||
char vname[16];
|
||||
ScriptGetVarname(vname,slp,sizeof(vname));
|
||||
|
||||
char label[SCRIPT_MAXSSIZE];
|
||||
lp=GetStringResult(lp,OPER_EQU,label,0);
|
||||
|
||||
char vstr[16],minstr[16],maxstr[16],stepstr[16];
|
||||
dtostrfd(val,4,vstr);
|
||||
dtostrfd(min,4,minstr);
|
||||
dtostrfd(max,4,maxstr);
|
||||
dtostrfd(step,4,stepstr);
|
||||
WSContentSend_PD(SCRIPT_MSG_NUMINP,label,minstr,maxstr,stepstr,vstr,vname);
|
||||
|
||||
} else {
|
||||
Replace_Cmd_Vars(lin,tmp,sizeof(tmp));
|
||||
if (optflg) {
|
||||
WSContentSend_PD(PSTR("<div>%s</div>"),tmp);
|
||||
} else {
|
||||
WSContentSend_PD(PSTR("{s}%s{e}"),tmp);
|
||||
}
|
||||
|
@ -3916,6 +4437,7 @@ bool Xdrv10(uint8_t function)
|
|||
if (bitRead(Settings.rule_enabled, 0)) {
|
||||
Run_Scripter(">B",2,0);
|
||||
fast_script=Run_Scripter(">F",-2,0);
|
||||
Script_Check_Hue(0);
|
||||
}
|
||||
break;
|
||||
case FUNC_EVERY_100_MSECOND:
|
||||
|
@ -3954,12 +4476,16 @@ bool Xdrv10(uint8_t function)
|
|||
break;
|
||||
#ifdef SUPPORT_MQTT_EVENT
|
||||
case FUNC_MQTT_DATA:
|
||||
result = ScriptMqttData();
|
||||
if (bitRead(Settings.rule_enabled, 0)) {
|
||||
result = ScriptMqttData();
|
||||
}
|
||||
break;
|
||||
#endif //SUPPORT_MQTT_EVENT
|
||||
#ifdef USE_SCRIPT_WEB_DISPLAY
|
||||
case FUNC_WEB_SENSOR:
|
||||
ScriptWebShow();
|
||||
if (bitRead(Settings.rule_enabled, 0)) {
|
||||
ScriptWebShow();
|
||||
}
|
||||
break;
|
||||
#endif //USE_SCRIPT_WEB_DISPLAY
|
||||
|
||||
|
|
|
@ -276,6 +276,12 @@ void HueLightStatus1(uint8_t device, String *response)
|
|||
if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
|
||||
if (bri < 1) bri = 1;
|
||||
|
||||
#ifdef USE_SHUTTER
|
||||
if (ShutterState(device)) {
|
||||
bri = (float)(Settings.shutter_invert[device-1] ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (light_type) {
|
||||
light_state.getHSB(&hue, &sat, nullptr);
|
||||
|
||||
|
@ -364,30 +370,51 @@ void HueLightStatus2(uint8_t device, String *response)
|
|||
char fname[33];
|
||||
strcpy(fname, Settings.friendlyname[MAX_FRIENDLYNAMES-1]);
|
||||
uint32_t fname_len = strlen(fname);
|
||||
if (fname_len >= 33-3) {
|
||||
fname[33-3] = 0x00;
|
||||
fname_len = 33-3;
|
||||
}
|
||||
if (fname_len > 30) { fname_len = 30; }
|
||||
fname[fname_len++] = '-';
|
||||
fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
|
||||
if (device - MAX_FRIENDLYNAMES < 10) {
|
||||
fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
|
||||
} else {
|
||||
fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10;
|
||||
}
|
||||
fname[fname_len] = 0x00;
|
||||
|
||||
response->replace("{j1", fname);
|
||||
}
|
||||
response->replace("{j2", GetHueDeviceId(device));
|
||||
}
|
||||
|
||||
// generate a unique lightId mixing local IP address and device number
|
||||
// it is limited to 16 devices.
|
||||
// last 24 bits of Mac address + 4 bits of local light
|
||||
uint32_t EncodeLightId(uint8_t idx)
|
||||
// it is limited to 32 devices.
|
||||
// last 24 bits of Mac address + 4 bits of local light + high bit for relays 16-31, relay 32 is mapped to 0
|
||||
uint32_t EncodeLightId(uint8_t relay_id)
|
||||
{
|
||||
uint8_t mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4) | (idx & 0xF);
|
||||
uint32_t id = 0;
|
||||
|
||||
if (relay_id >= 32) { // for Relay #32, we encode as 0
|
||||
relay_id = 0;
|
||||
}
|
||||
if (relay_id > 15) {
|
||||
id = (1 << 28);
|
||||
}
|
||||
|
||||
id |= (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4) | (relay_id & 0xF);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t DecodeLightId(uint32_t id) {
|
||||
return id & 0xF;
|
||||
// get hue_id and decode the relay_id
|
||||
// 4 LSB decode to 1-15, if bit 28 is set, it encodes 16-31, if 0 then 32
|
||||
uint32_t DecodeLightId(uint32_t hue_id) {
|
||||
uint8_t relay_id = hue_id & 0xF;
|
||||
if (hue_id & (1 << 28)) { // check if bit 25 is set, if so we have
|
||||
relay_id += 16;
|
||||
}
|
||||
if (0 == relay_id) { // special value 0 is actually relay #32
|
||||
relay_id = 32;
|
||||
}
|
||||
return relay_id;
|
||||
}
|
||||
|
||||
static const char * FIRST_GEN_UA[] = { // list of User-Agents signature
|
||||
|
@ -476,12 +503,22 @@ void HueLights(String *path)
|
|||
response += ",\"";
|
||||
}
|
||||
}
|
||||
#ifdef USE_SCRIPT
|
||||
Script_Check_Hue(&response);
|
||||
#endif
|
||||
response += "}";
|
||||
}
|
||||
else if (path->endsWith("/state")) { // Got ID/state
|
||||
path->remove(0,8); // Remove /lights/
|
||||
path->remove(path->indexOf("/state")); // Remove /state
|
||||
device = DecodeLightId(atoi(path->c_str()));
|
||||
|
||||
#ifdef USE_SCRIPT
|
||||
if (device>devices_present) {
|
||||
return Script_Handle_Hue(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((device < 1) || (device > maxhue)) {
|
||||
device = 1;
|
||||
}
|
||||
|
@ -498,19 +535,32 @@ void HueLights(String *path)
|
|||
response.replace("{id", String(EncodeLightId(device)));
|
||||
response.replace("{cm", "on");
|
||||
|
||||
on = hue_json["on"];
|
||||
switch(on)
|
||||
{
|
||||
case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE);
|
||||
response.replace("{re", "false");
|
||||
break;
|
||||
case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE);
|
||||
response.replace("{re", "true");
|
||||
break;
|
||||
default : response.replace("{re", (power & (1 << (device-1))) ? "true" : "false");
|
||||
break;
|
||||
#ifdef USE_SHUTTER
|
||||
if (ShutterState(device)) {
|
||||
if (!change) {
|
||||
on = hue_json["on"];
|
||||
bri = on ? 1.0f : 0.0f; // when bri is not part of this request then calculate it
|
||||
change = true;
|
||||
}
|
||||
response.replace("{re", on ? "true" : "false");
|
||||
} else {
|
||||
#endif
|
||||
on = hue_json["on"];
|
||||
switch(on)
|
||||
{
|
||||
case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE);
|
||||
response.replace("{re", "false");
|
||||
break;
|
||||
case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE);
|
||||
response.replace("{re", "true");
|
||||
break;
|
||||
default : response.replace("{re", (power & (1 << (device-1))) ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
resp = true;
|
||||
#ifdef USE_SHUTTER
|
||||
}
|
||||
resp = true;
|
||||
#endif // USE_SHUTTER
|
||||
}
|
||||
|
||||
if (light_type && (local_light_subtype >= LST_SINGLE)) {
|
||||
|
@ -616,6 +666,12 @@ void HueLights(String *path)
|
|||
resp = true;
|
||||
}
|
||||
if (change) {
|
||||
#ifdef USE_SHUTTER
|
||||
if (ShutterState(device)) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_invert[device-1]);
|
||||
ShutterSetPosition(device, bri * 100.0f );
|
||||
} else
|
||||
#endif
|
||||
if (light_type && (local_light_subtype > LST_NONE)) { // not relay
|
||||
if (!Settings.flag3.pwm_multi_channels) {
|
||||
if (g_gotct) {
|
||||
|
@ -649,6 +705,14 @@ void HueLights(String *path)
|
|||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "/lights path=%s", path->c_str());
|
||||
path->remove(0,8); // Remove /lights/
|
||||
device = DecodeLightId(atoi(path->c_str()));
|
||||
|
||||
#ifdef USE_SCRIPT
|
||||
if (device>devices_present) {
|
||||
Script_HueStatus(&response,device-devices_present-1);
|
||||
goto exit;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((device < 1) || (device > maxhue)) {
|
||||
device = 1;
|
||||
}
|
||||
|
@ -660,6 +724,7 @@ void HueLights(String *path)
|
|||
response = "{}";
|
||||
code = 406;
|
||||
}
|
||||
exit:
|
||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
|
||||
WSSend(code, CT_JSON, response);
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public:
|
|||
XdrvRulesProcess();
|
||||
}
|
||||
|
||||
static ZCLFrame parseRawFrame(SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid) { // parse a raw frame and build the ZCL frame object
|
||||
static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid) { // parse a raw frame and build the ZCL frame object
|
||||
uint32_t i = offset;
|
||||
ZCLHeaderFrameControl_t frame_control;
|
||||
uint16_t manuf_code = 0;
|
||||
|
@ -133,6 +133,16 @@ private:
|
|||
SBuffer _payload;
|
||||
};
|
||||
|
||||
char Hex36Char(uint8_t value) {
|
||||
// convert an integer from 0 to 46, to a single digit 0-9A-Z
|
||||
if (value < 10) {
|
||||
return '0' + value;
|
||||
} else if (value < 46) {
|
||||
return 'A' + value - 10;
|
||||
} else {
|
||||
return '?'; // out of range
|
||||
}
|
||||
}
|
||||
|
||||
// Zigbee ZCL converters
|
||||
|
||||
|
@ -374,17 +384,17 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
|
|||
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
||||
uint32_t i = offset;
|
||||
uint32_t len = _payload.len();
|
||||
uint32_t attrid = _cluster_id << 16; // set high 16 bits with cluster id
|
||||
|
||||
while (len + offset - i >= 3) {
|
||||
attrid = (attrid & 0xFFFF0000) | _payload.get16(i); // get lower 16 bits
|
||||
while (len - i >= 3) {
|
||||
uint16_t attrid = _payload.get16(i);
|
||||
i += 2;
|
||||
|
||||
char shortaddr[12];
|
||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%08X"), attrid);
|
||||
char shortaddr[16];
|
||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("%c_%04X_%04X"),
|
||||
Hex36Char(_cmd_id), _cluster_id, attrid);
|
||||
|
||||
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
|
||||
if (0x0000FF01 == attrid) {
|
||||
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
|
||||
if (0x42 == _payload.get8(i)) {
|
||||
_payload.set8(i, 0x41); // change type from 0x42 to 0x41
|
||||
}
|
||||
|
@ -394,14 +404,13 @@ void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
|||
}
|
||||
|
||||
// Parse non-normalized attributes
|
||||
// The key is 24 bits, high 16 bits is cluserid, low 8 bits is command id
|
||||
// The key is "s_" followed by 16 bits clusterId, "_" followed by 8 bits command id
|
||||
void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
|
||||
uint32_t i = offset;
|
||||
uint32_t len = _payload.len();
|
||||
uint32_t attrid = _cluster_id << 8 | _cmd_id;
|
||||
|
||||
char attrid_str[12];
|
||||
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("0x%06X"), attrid); // 24 bits
|
||||
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("s_%04X_%02X"), _cluster_id, _cmd_id);
|
||||
|
||||
char hex_char[_payload.len()*2+2];
|
||||
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
||||
|
@ -409,211 +418,333 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
|
|||
json[attrid_str] = hex_char;
|
||||
}
|
||||
|
||||
#define ZCL_MODELID "0x00000005" // Cluster 0x0000, attribute 0x05
|
||||
#define ZCL_TEMPERATURE "0x04020000" // Cluster 0x0402, attribute 0x00
|
||||
#define ZCL_PRESSURE "0x04030000" // Cluster 0x0403, attribute 0x00
|
||||
#define ZCL_PRESSURE_SCALED "0x04030010" // Cluster 0x0403, attribute 0x10
|
||||
#define ZCL_PRESSURE_SCALE "0x04030014" // Cluster 0x0403, attribute 0x14
|
||||
#define ZCL_HUMIDITY "0x04050000" // Cluster 0x0403, attribute 0x00
|
||||
#define ZCL_LUMI_WEATHER "0x0000FF01" // Cluster 0x0000, attribute 0xFF01 - proprietary
|
||||
// return value:
|
||||
// 0 = keep initial value
|
||||
// 1 = remove initial value
|
||||
typedef int32_t (*Z_AttrConverter)(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param);
|
||||
typedef struct Z_AttributeConverter {
|
||||
const char * filter;
|
||||
const char * name;
|
||||
Z_AttrConverter func;
|
||||
void * param;
|
||||
} Z_AttributeConverter;
|
||||
|
||||
#define ZCL_OO_OFF "0x000600" // Cluster 0x0006, cmd 0x00 - On/Off - Off
|
||||
#define ZCL_OO_ON "0x000601" // Cluster 0x0006, cmd 0x01 - On/Off - On
|
||||
#define ZCL_COLORTEMP_MOVE "0x03000A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp
|
||||
#define ZCL_LC_MOVE "0x000800" // Cluster 0x0008, cmd 0x00, Level Control Move to Level
|
||||
#define ZCL_LC_MOVE_1 "0x000801" // Cluster 0x0008, cmd 0x01, Level Control Move
|
||||
#define ZCL_LC_STEP "0x000802" // Cluster 0x0008, cmd 0x02, Level Control Step
|
||||
#define ZCL_LC_STOP "0x000803" // Cluster 0x0008, cmd 0x03, Level Control Stop
|
||||
#define ZCL_LC_MOVE_WOO "0x000804" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off
|
||||
#define ZCL_LC_MOVE_1_WOO "0x000805" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off
|
||||
#define ZCL_LC_STEP_WOO "0x000806" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off
|
||||
#define ZCL_LC_STOP_WOO "0x000807" // Cluster 0x0008, cmd 0x07, Level Control Stop
|
||||
const float Z_100 PROGMEM = 100.0f;
|
||||
const float Z_10 PROGMEM = 10.0f;
|
||||
|
||||
void ZCLFrame::postProcessAttributes(JsonObject& json) {
|
||||
const __FlashStringHelper *key;
|
||||
// list of post-processing directives
|
||||
const Z_AttributeConverter Z_PostProcess[] = {
|
||||
{ "A_0000_0005", D_JSON_MODEL D_JSON_ID, &Z_Copy, nullptr }, // ModelID
|
||||
|
||||
// ModelID ZCL 3.2
|
||||
key = F(ZCL_MODELID);
|
||||
if (json.containsKey(key)) {
|
||||
json[F(D_JSON_MODEL D_JSON_ID)] = json[key];
|
||||
json.remove(key);
|
||||
}
|
||||
{ "A_0400_0000", D_JSON_ILLUMINANCE, &Z_Copy, nullptr }, // Illuminance (in Lux)
|
||||
{ "A_0400_0004", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
|
||||
{ "A_0400_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||
|
||||
// Temperature ZCL 4.4
|
||||
key = F(ZCL_TEMPERATURE);
|
||||
if (json.containsKey(key)) {
|
||||
// parse temperature
|
||||
int32_t temperature = json[key];
|
||||
json.remove(key);
|
||||
json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
|
||||
}
|
||||
{ "A_0401_0000", "LevelStatus", &Z_Copy, nullptr }, // Illuminance (in Lux)
|
||||
{ "A_0401_0001", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
|
||||
{ "A_0401_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||
|
||||
// Pressure ZCL 4.5
|
||||
key = F(ZCL_PRESSURE);
|
||||
if (json.containsKey(key)) {
|
||||
json[F(D_JSON_PRESSURE)] = json[key];
|
||||
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
|
||||
json.remove(key);
|
||||
}
|
||||
json.remove(F(ZCL_PRESSURE_SCALE));
|
||||
json.remove(F(ZCL_PRESSURE_SCALED));
|
||||
{ "A_0402_0000", D_JSON_TEMPERATURE, &Z_ConvFloatDivider, (void*) &Z_100 }, // Temperature
|
||||
{ "A_0402_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||
|
||||
// Humidity ZCL 4.7
|
||||
key = F(ZCL_HUMIDITY);
|
||||
if (json.containsKey(key)) {
|
||||
// parse temperature
|
||||
uint32_t humidity = json[key];
|
||||
json.remove(key);
|
||||
json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
|
||||
}
|
||||
{ "A_0403_0000", D_JSON_PRESSURE_UNIT, &Z_Const_Keep, (void*) D_UNIT_PRESSURE}, // Pressure Unit
|
||||
{ "A_0403_0000", D_JSON_PRESSURE, &Z_Copy, nullptr }, // Pressure
|
||||
{ "A_0403_????", nullptr, &Z_Remove, nullptr }, // Remove all other Pressure values
|
||||
|
||||
// Osram Mini Switch
|
||||
key = F(ZCL_OO_OFF);
|
||||
if (json.containsKey(key)) {
|
||||
json.remove(key);
|
||||
json[F(D_CMND_POWER)] = F("Off");
|
||||
}
|
||||
key = F(ZCL_OO_ON);
|
||||
if (json.containsKey(key)) {
|
||||
json.remove(key);
|
||||
json[F(D_CMND_POWER)] = F("On");
|
||||
}
|
||||
key = F(ZCL_COLORTEMP_MOVE);
|
||||
if (json.containsKey(key)) {
|
||||
String hex = json[key];
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
uint16_t color_temp = buf2.get16(0);
|
||||
uint16_t transition_time = buf2.get16(2);
|
||||
json.remove(key);
|
||||
json[F("ColorTemp")] = color_temp;
|
||||
json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
}
|
||||
key = F(ZCL_LC_MOVE_WOO);
|
||||
if (json.containsKey(key)) {
|
||||
String hex = json[key];
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
uint8_t level = buf2.get8(0);
|
||||
uint16_t transition_time = buf2.get16(1);
|
||||
json.remove(key);
|
||||
json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
|
||||
json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
if (0 == level) {
|
||||
json[F(D_CMND_POWER)] = F("Off");
|
||||
} else {
|
||||
json[F(D_CMND_POWER)] = F("On");
|
||||
}
|
||||
}
|
||||
key = F(ZCL_LC_MOVE);
|
||||
if (json.containsKey(key)) {
|
||||
String hex = json[key];
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
uint8_t level = buf2.get8(0);
|
||||
uint16_t transition_time = buf2.get16(1);
|
||||
json.remove(key);
|
||||
json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
|
||||
json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
}
|
||||
key = F(ZCL_LC_MOVE_1);
|
||||
if (json.containsKey(key)) {
|
||||
String hex = json[key];
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
uint8_t move_mode = buf2.get8(0);
|
||||
uint8_t move_rate = buf2.get8(1);
|
||||
json.remove(key);
|
||||
json[F("Move")] = move_mode ? F("Down") : F("Up");
|
||||
json[F("Rate")] = move_rate;
|
||||
}
|
||||
key = F(ZCL_LC_MOVE_1_WOO);
|
||||
if (json.containsKey(key)) {
|
||||
String hex = json[key];
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
uint8_t move_mode = buf2.get8(0);
|
||||
uint8_t move_rate = buf2.get8(1);
|
||||
json.remove(key);
|
||||
json[F("Move")] = move_mode ? F("Down") : F("Up");
|
||||
json[F("Rate")] = move_rate;
|
||||
if (0 == move_mode) {
|
||||
json[F(D_CMND_POWER)] = F("On");
|
||||
}
|
||||
}
|
||||
key = F(ZCL_LC_STEP);
|
||||
if (json.containsKey(key)) {
|
||||
String hex = json[key];
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
uint8_t step_mode = buf2.get8(0);
|
||||
uint8_t step_size = buf2.get8(1);
|
||||
uint16_t transition_time = buf2.get16(2);
|
||||
json.remove(key);
|
||||
json[F("Step")] = step_mode ? F("Down") : F("Up");
|
||||
json[F("StepSize")] = step_size;
|
||||
json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
}
|
||||
key = F(ZCL_LC_STEP_WOO);
|
||||
if (json.containsKey(key)) {
|
||||
String hex = json[key];
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
uint8_t step_mode = buf2.get8(0);
|
||||
uint8_t step_size = buf2.get8(1);
|
||||
uint16_t transition_time = buf2.get16(2);
|
||||
json.remove(key);
|
||||
json[F("Step")] = step_mode ? F("Down") : F("Up");
|
||||
json[F("StepSize")] = step_size;
|
||||
json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
if (0 == step_mode) {
|
||||
json[F(D_CMND_POWER)] = F("On");
|
||||
}
|
||||
}
|
||||
key = F(ZCL_LC_STOP);
|
||||
if (json.containsKey(key)) {
|
||||
json.remove(key);
|
||||
json[F("Stop")] = 1;
|
||||
}
|
||||
key = F(ZCL_LC_STOP_WOO);
|
||||
if (json.containsKey(key)) {
|
||||
json.remove(key);
|
||||
json[F("Stop")] = 1;
|
||||
}
|
||||
{ "A_0404_0000", D_JSON_FLOWRATE, &Z_ConvFloatDivider, (void*) &Z_10 }, // Flow (in m3/h)
|
||||
{ "A_0404_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||
|
||||
// Lumi.weather proprietary field
|
||||
key = F(ZCL_LUMI_WEATHER);
|
||||
if (json.containsKey(key)) {
|
||||
String hex = json[key];
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json_lumi = jsonBuffer.createObject();
|
||||
uint32_t i = 0;
|
||||
uint32_t len = buf2.len();
|
||||
char shortaddr[8];
|
||||
{ "A_0405_0000", D_JSON_HUMIDITY, &Z_ConvFloatDivider, (void*) &Z_100 }, // Humidity
|
||||
{ "A_0405_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||
|
||||
while (len - i >= 2) {
|
||||
uint8_t attrid = buf2.get8(i++);
|
||||
{ "A_0406_0000", "Occupancy", &Z_Copy, nullptr }, // Occupancy (map8)
|
||||
{ "A_0406_0001", "OccupancySensorType", &Z_Copy, nullptr }, // OccupancySensorType
|
||||
{ "A_0406_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||
|
||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%02X"), attrid);
|
||||
|
||||
i += parseSingleAttribute(json_lumi, shortaddr, buf2, i, len);
|
||||
}
|
||||
// parse output
|
||||
if (json_lumi.containsKey("0x64")) { // Temperature
|
||||
int32_t temperature = json_lumi["0x64"];
|
||||
json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
|
||||
}
|
||||
if (json_lumi.containsKey("0x65")) { // Humidity
|
||||
uint32_t humidity = json_lumi["0x65"];
|
||||
json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
|
||||
}
|
||||
if (json_lumi.containsKey("0x66")) { // Pressure
|
||||
int32_t pressure = json_lumi["0x66"];
|
||||
json[F(D_JSON_PRESSURE)] = pressure / 100.0f;
|
||||
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
|
||||
}
|
||||
if (json_lumi.containsKey("0x01")) { // Battery Voltage
|
||||
uint32_t voltage = json_lumi["0x01"];
|
||||
json[F(D_JSON_VOLTAGE)] = voltage / 1000.0f;
|
||||
json[F("Battery")] = toPercentageCR2032(voltage);
|
||||
}
|
||||
json.remove(key);
|
||||
}
|
||||
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
|
||||
{ "A_0000_FF01", nullptr, &Z_AqaraSensor, nullptr }, // Occupancy (map8)
|
||||
// // 0x0b04 Electrical Measurement
|
||||
// { "A_0B04_0100", "DCVoltage", &Z_Copy, nullptr }, // Occupancy (map8)
|
||||
// { "A_0B04_0001", "OccupancySensorType", &Z_Copy, nullptr }, // OccupancySensorType
|
||||
// { "A_0B04_????", "", &Z_Remove, nullptr }, // Remove all other values
|
||||
};
|
||||
|
||||
// ======================================================================
|
||||
// Remove attribute
|
||||
int32_t Z_Remove(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
|
||||
return 1; // remove original key
|
||||
}
|
||||
|
||||
// Copy value as-is
|
||||
int32_t Z_Copy(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
|
||||
json[new_name] = value;
|
||||
return 1; // remove original key
|
||||
}
|
||||
|
||||
// Copy value as-is
|
||||
int32_t Z_Const_Keep(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
|
||||
json[new_name] = (char*)param;
|
||||
return 0; // keep original key
|
||||
}
|
||||
|
||||
// Convert int to float with divider
|
||||
int32_t Z_ConvFloatDivider(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
|
||||
float f_value = value;
|
||||
float *divider = (float*) param;
|
||||
json[new_name] = f_value / *divider;
|
||||
return 1; // remove original key
|
||||
}
|
||||
|
||||
int32_t Z_AqaraSensor(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
|
||||
String hex = value;
|
||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
uint32_t i = 0;
|
||||
uint32_t len = buf2.len();
|
||||
char shortaddr[8];
|
||||
char tmp[] = "tmp"; // for obscure reasons, it must be converted from const char* to char*, otherwise ArduinoJson gets confused
|
||||
|
||||
JsonVariant sub_value;
|
||||
|
||||
while (len - i >= 2) {
|
||||
uint8_t attrid = buf2.get8(i++);
|
||||
|
||||
i += parseSingleAttribute(json, tmp, buf2, i, len);
|
||||
float val = json[tmp];
|
||||
json.remove(tmp);
|
||||
if (0x64 == attrid) {
|
||||
json[F(D_JSON_TEMPERATURE)] = val / 100.0f;
|
||||
} else if (0x65 == attrid) {
|
||||
json[F(D_JSON_HUMIDITY)] = val / 100.0f;
|
||||
} else if (0x66 == attrid) {
|
||||
json[F(D_JSON_PRESSURE)] = val / 100.0f;
|
||||
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
|
||||
} else if (0x01 == attrid) {
|
||||
json[F(D_JSON_VOLTAGE)] = val / 1000.0f;
|
||||
json[F("Battery")] = toPercentageCR2032(val);
|
||||
}
|
||||
}
|
||||
return 1; // remove original key
|
||||
}
|
||||
// ======================================================================
|
||||
|
||||
#define ZCL_MODELID "A_0000_0005" // Cmd 0x0A - Cluster 0x0000, attribute 0x05
|
||||
#define ZCL_TEMPERATURE "A_0402_0000" // Cmd 0x0A - Cluster 0x0402, attribute 0x00
|
||||
#define ZCL_PRESSURE "A_0403_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
|
||||
#define ZCL_PRESSURE_SCALED "A_0403_0010" // Cmd 0x0A - Cluster 0x0403, attribute 0x10
|
||||
#define ZCL_PRESSURE_SCALE "A_0403_0014" // Cmd 0x0A - Cluster 0x0403, attribute 0x14
|
||||
#define ZCL_HUMIDITY "A_0405_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
|
||||
#define ZCL_LUMI_WEATHER "A_0000_FF01" // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
|
||||
|
||||
// Cluster Specific commands
|
||||
#define ZCL_OO_OFF "s_0006_00" // Cluster 0x0006, cmd 0x00 - On/Off - Off
|
||||
#define ZCL_OO_ON "s_0006_01" // Cluster 0x0006, cmd 0x01 - On/Off - On
|
||||
#define ZCL_COLORTEMP_MOVE "s_0300_0A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp
|
||||
#define ZCL_LC_MOVE "s_0008_00" // Cluster 0x0008, cmd 0x00, Level Control Move to Level
|
||||
#define ZCL_LC_MOVE_1 "s_0008_01" // Cluster 0x0008, cmd 0x01, Level Control Move
|
||||
#define ZCL_LC_STEP "s_0008_02" // Cluster 0x0008, cmd 0x02, Level Control Step
|
||||
#define ZCL_LC_STOP "s_0008_03" // Cluster 0x0008, cmd 0x03, Level Control Stop
|
||||
#define ZCL_LC_MOVE_WOO "s_0008_04" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off
|
||||
#define ZCL_LC_MOVE_1_WOO "s_0008_05" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off
|
||||
#define ZCL_LC_STEP_WOO "s_0008_06" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off
|
||||
#define ZCL_LC_STOP_WOO "s_0008_07" // Cluster 0x0008, cmd 0x07, Level Control Stop
|
||||
|
||||
// inspired from https://github.com/torvalds/linux/blob/master/lib/glob.c
|
||||
bool mini_glob_match(char const *pat, char const *str) {
|
||||
for (;;) {
|
||||
unsigned char c = *str++;
|
||||
unsigned char d = *pat++;
|
||||
|
||||
switch (d) {
|
||||
case '?': /* Wildcard: anything but nul */
|
||||
if (c == '\0')
|
||||
return false;
|
||||
break;
|
||||
case '\\':
|
||||
d = *pat++;
|
||||
/*FALLTHROUGH*/
|
||||
default: /* Literal character */
|
||||
if (c == d) {
|
||||
if (d == '\0')
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
return false; /* No point continuing */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZCLFrame::postProcessAttributes(JsonObject& json) {
|
||||
// iterate on json elements
|
||||
for (auto kv : json) {
|
||||
String key = kv.key;
|
||||
JsonVariant& value = kv.value;
|
||||
|
||||
// Iterate on filter
|
||||
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
|
||||
if (mini_glob_match(converter->filter, key.c_str())) {
|
||||
int32_t drop = (*converter->func)(json, key.c_str(), value, converter->name, converter->param);
|
||||
if (drop) {
|
||||
json.remove(key);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ZCLFrame::postProcessAttributes2(JsonObject& json) {
|
||||
// void postProcessAttributes2(JsonObject& json) {
|
||||
// const __FlashStringHelper *key;
|
||||
//
|
||||
// // Osram Mini Switch
|
||||
// key = F(ZCL_OO_OFF);
|
||||
// if (json.containsKey(key)) {
|
||||
// json.remove(key);
|
||||
// json[F(D_CMND_POWER)] = F("Off");
|
||||
// }
|
||||
// key = F(ZCL_OO_ON);
|
||||
// if (json.containsKey(key)) {
|
||||
// json.remove(key);
|
||||
// json[F(D_CMND_POWER)] = F("On");
|
||||
// }
|
||||
// key = F(ZCL_COLORTEMP_MOVE);
|
||||
// if (json.containsKey(key)) {
|
||||
// String hex = json[key];
|
||||
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
// uint16_t color_temp = buf2.get16(0);
|
||||
// uint16_t transition_time = buf2.get16(2);
|
||||
// json.remove(key);
|
||||
// json[F("ColorTemp")] = color_temp;
|
||||
// json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
// }
|
||||
// key = F(ZCL_LC_MOVE_WOO);
|
||||
// if (json.containsKey(key)) {
|
||||
// String hex = json[key];
|
||||
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
// uint8_t level = buf2.get8(0);
|
||||
// uint16_t transition_time = buf2.get16(1);
|
||||
// json.remove(key);
|
||||
// json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
|
||||
// json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
// if (0 == level) {
|
||||
// json[F(D_CMND_POWER)] = F("Off");
|
||||
// } else {
|
||||
// json[F(D_CMND_POWER)] = F("On");
|
||||
// }
|
||||
// }
|
||||
// key = F(ZCL_LC_MOVE);
|
||||
// if (json.containsKey(key)) {
|
||||
// String hex = json[key];
|
||||
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
// uint8_t level = buf2.get8(0);
|
||||
// uint16_t transition_time = buf2.get16(1);
|
||||
// json.remove(key);
|
||||
// json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
|
||||
// json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
// }
|
||||
// key = F(ZCL_LC_MOVE_1);
|
||||
// if (json.containsKey(key)) {
|
||||
// String hex = json[key];
|
||||
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
// uint8_t move_mode = buf2.get8(0);
|
||||
// uint8_t move_rate = buf2.get8(1);
|
||||
// json.remove(key);
|
||||
// json[F("Move")] = move_mode ? F("Down") : F("Up");
|
||||
// json[F("Rate")] = move_rate;
|
||||
// }
|
||||
// key = F(ZCL_LC_MOVE_1_WOO);
|
||||
// if (json.containsKey(key)) {
|
||||
// String hex = json[key];
|
||||
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
// uint8_t move_mode = buf2.get8(0);
|
||||
// uint8_t move_rate = buf2.get8(1);
|
||||
// json.remove(key);
|
||||
// json[F("Move")] = move_mode ? F("Down") : F("Up");
|
||||
// json[F("Rate")] = move_rate;
|
||||
// if (0 == move_mode) {
|
||||
// json[F(D_CMND_POWER)] = F("On");
|
||||
// }
|
||||
// }
|
||||
// key = F(ZCL_LC_STEP);
|
||||
// if (json.containsKey(key)) {
|
||||
// String hex = json[key];
|
||||
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
// uint8_t step_mode = buf2.get8(0);
|
||||
// uint8_t step_size = buf2.get8(1);
|
||||
// uint16_t transition_time = buf2.get16(2);
|
||||
// json.remove(key);
|
||||
// json[F("Step")] = step_mode ? F("Down") : F("Up");
|
||||
// json[F("StepSize")] = step_size;
|
||||
// json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
// }
|
||||
// key = F(ZCL_LC_STEP_WOO);
|
||||
// if (json.containsKey(key)) {
|
||||
// String hex = json[key];
|
||||
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
// uint8_t step_mode = buf2.get8(0);
|
||||
// uint8_t step_size = buf2.get8(1);
|
||||
// uint16_t transition_time = buf2.get16(2);
|
||||
// json.remove(key);
|
||||
// json[F("Step")] = step_mode ? F("Down") : F("Up");
|
||||
// json[F("StepSize")] = step_size;
|
||||
// json[F("TransitionTime")] = transition_time / 10.0f;
|
||||
// if (0 == step_mode) {
|
||||
// json[F(D_CMND_POWER)] = F("On");
|
||||
// }
|
||||
// }
|
||||
// key = F(ZCL_LC_STOP);
|
||||
// if (json.containsKey(key)) {
|
||||
// json.remove(key);
|
||||
// json[F("Stop")] = 1;
|
||||
// }
|
||||
// key = F(ZCL_LC_STOP_WOO);
|
||||
// if (json.containsKey(key)) {
|
||||
// json.remove(key);
|
||||
// json[F("Stop")] = 1;
|
||||
// }
|
||||
//
|
||||
// // Lumi.weather proprietary field
|
||||
// key = F(ZCL_LUMI_WEATHER);
|
||||
// if (json.containsKey(key)) {
|
||||
// String hex = json[key];
|
||||
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||
// DynamicJsonBuffer jsonBuffer;
|
||||
// JsonObject& json_lumi = jsonBuffer.createObject();
|
||||
// uint32_t i = 0;
|
||||
// uint32_t len = buf2.len();
|
||||
// char shortaddr[8];
|
||||
//
|
||||
// while (len - i >= 2) {
|
||||
// uint8_t attrid = buf2.get8(i++);
|
||||
//
|
||||
// snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%02X"), attrid);
|
||||
//
|
||||
// //json[shortaddr] = parseSingleAttribute(json_lumi, buf2, i, len, nullptr, 0);
|
||||
// }
|
||||
// // parse output
|
||||
// if (json_lumi.containsKey("0x64")) { // Temperature
|
||||
// int32_t temperature = json_lumi["0x64"];
|
||||
// json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
|
||||
// }
|
||||
// if (json_lumi.containsKey("0x65")) { // Humidity
|
||||
// uint32_t humidity = json_lumi["0x65"];
|
||||
// json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
|
||||
// }
|
||||
// if (json_lumi.containsKey("0x66")) { // Pressure
|
||||
// int32_t pressure = json_lumi["0x66"];
|
||||
// json[F(D_JSON_PRESSURE)] = pressure / 100.0f;
|
||||
// json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
|
||||
// }
|
||||
// if (json_lumi.containsKey("0x01")) { // Battery Voltage
|
||||
// uint32_t voltage = json_lumi["0x01"];
|
||||
// json[F(D_JSON_VOLTAGE)] = voltage / 1000.0f;
|
||||
// json[F("Battery")] = toPercentageCR2032(voltage);
|
||||
// }
|
||||
// json.remove(key);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
#endif // USE_ZIGBEE
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Theo Arends and Stephan Hadinger
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
typedef struct Z_Device {
|
||||
uint16_t shortaddr;
|
||||
uint64_t longaddr; // 0x00 means unspecified
|
||||
std::vector<uint8_t> endpoints;
|
||||
std::vector<uint32_t> clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
|
||||
std::vector<uint32_t> clusters_out; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
|
||||
} Z_Device;
|
||||
|
||||
std::map<uint16_t, Z_Device> zigbee_devices = {};
|
||||
|
||||
|
||||
template < typename T>
|
||||
bool findInVector(const std::vector<T> & vecOfElements, const T & element) {
|
||||
// Find given element in vector
|
||||
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
|
||||
|
||||
if (it != vecOfElements.end()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// insert an entry when it is known it is missing
|
||||
void Z_InsertShortAddrEntry(uint16_t shortaddr, uint64_t longaddr) {
|
||||
Z_Device device = { shortaddr, longaddr,
|
||||
std::vector<uint8_t>(),
|
||||
std::vector<uint32_t>(),
|
||||
std::vector<uint32_t>() };
|
||||
zigbee_devices[shortaddr] = device;
|
||||
}
|
||||
|
||||
void Z_AddDeviceLongAddr(uint16_t shortaddr, uint64_t longaddr) {
|
||||
// is the short address already recorded?
|
||||
if (0 == zigbee_devices.count(shortaddr)) {
|
||||
// No, add an entry
|
||||
Z_InsertShortAddrEntry(shortaddr, longaddr);
|
||||
} else {
|
||||
// Yes, update the longaddr if necessary
|
||||
Z_Device &device = zigbee_devices[shortaddr];
|
||||
uint64_t prev_longaddr = device.longaddr;
|
||||
if (prev_longaddr != longaddr) {
|
||||
// new device, i.e. collision
|
||||
device.longaddr = longaddr;
|
||||
device.endpoints.clear();
|
||||
device.clusters_in.clear();
|
||||
device.clusters_out.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Z_AddDeviceEndpoint(uint16_t shortaddr, uint8_t endpoint) {
|
||||
if (0 == zigbee_devices.count(shortaddr)) {
|
||||
// No entry
|
||||
Z_InsertShortAddrEntry(shortaddr, 0);
|
||||
}
|
||||
Z_Device &device = zigbee_devices[shortaddr];
|
||||
if (!findInVector(device.endpoints, endpoint)) {
|
||||
device.endpoints.push_back(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
void Z_AddDeviceCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out) {
|
||||
if (0 == zigbee_devices.count(shortaddr)) {
|
||||
// No entry
|
||||
Z_InsertShortAddrEntry(shortaddr, 0);
|
||||
}
|
||||
Z_Device &device = zigbee_devices[shortaddr];
|
||||
if (!findInVector(device.endpoints, endpoint)) {
|
||||
device.endpoints.push_back(endpoint);
|
||||
}
|
||||
uint32_t ep_cluster = (endpoint << 16) | cluster;
|
||||
if (!out) {
|
||||
if (!findInVector(device.clusters_in, ep_cluster)) {
|
||||
device.clusters_in.push_back(ep_cluster);
|
||||
}
|
||||
} else { // out
|
||||
if (!findInVector(device.clusters_out, ep_cluster)) {
|
||||
device.clusters_out.push_back(ep_cluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String Z_DumpDevices(void) {
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json = jsonBuffer.createObject();
|
||||
JsonObject& devices = json.createNestedObject(F("ZigbeeDevices"));
|
||||
|
||||
for (std::map<uint16_t, Z_Device>::iterator it = zigbee_devices.begin(); it != zigbee_devices.end(); ++it) {
|
||||
uint16_t shortaddr = it->first;
|
||||
Z_Device& device = it->second;
|
||||
char hex[20];
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
||||
JsonObject& dev = devices.createNestedObject(hex);
|
||||
dev[F("ShortAddr")] = hex;
|
||||
|
||||
Uint64toHex(device.longaddr, hex, 64);
|
||||
dev[F("IEEEAddr")] = hex;
|
||||
|
||||
JsonArray& dev_endpoints = dev.createNestedArray(F("Endpoints"));
|
||||
for (std::vector<uint8_t>::iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) {
|
||||
uint8_t endpoint = *ite;
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
||||
dev_endpoints.add(hex);
|
||||
}
|
||||
|
||||
JsonObject& dev_clusters_in = dev.createNestedObject(F("Clusters_in"));
|
||||
for (std::vector<uint32_t>::iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) {
|
||||
uint16_t cluster = *itc & 0xFFFF;
|
||||
uint8_t endpoint = (*itc >> 16) & 0xFF;
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
||||
if (!dev_clusters_in.containsKey(hex)) {
|
||||
dev_clusters_in.createNestedArray(hex);
|
||||
}
|
||||
JsonArray &cluster_arr = dev_clusters_in[hex];
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster);
|
||||
cluster_arr.add(hex);
|
||||
}
|
||||
|
||||
JsonObject& dev_clusters_out = dev.createNestedObject(F("Clusters_out"));
|
||||
for (std::vector<uint32_t>::iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) {
|
||||
uint16_t cluster = *itc & 0xFFFF;
|
||||
uint8_t endpoint = (*itc >> 16) & 0xFF;
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
||||
if (!dev_clusters_out.containsKey(hex)) {
|
||||
dev_clusters_out.createNestedArray(hex);
|
||||
}
|
||||
JsonArray &cluster_arr = dev_clusters_out[hex];
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster);
|
||||
cluster_arr.add(hex);
|
||||
}
|
||||
}
|
||||
String payload = "";
|
||||
payload.reserve(200);
|
||||
json.printTo(payload);
|
||||
return payload;
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Theo Arends and Stephan Hadinger
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
// Status code used for ZigbeeStatus MQTT message
|
||||
// Ex: {"ZigbeeStatus":{"Status": 3,"Message":"Configured, starting coordinator"}}
|
||||
const uint8_t ZIGBEE_STATUS_OK = 0; // Zigbee started and working
|
||||
const uint8_t ZIGBEE_STATUS_BOOT = 1; // CC2530 booting
|
||||
const uint8_t ZIGBEE_STATUS_RESET_CONF = 2; // Resetting CC2530 configuration
|
||||
const uint8_t ZIGBEE_STATUS_STARTING = 3; // Starting CC2530 as coordinator
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_CLOSE = 20; // Disable PermitJoin
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_60 = 21; // Enable PermitJoin for 60 seconds
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_XX = 22; // Enable PermitJoin until next boot
|
||||
const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; // Device announces its address
|
||||
const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor
|
||||
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor
|
||||
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters)
|
||||
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version
|
||||
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
|
||||
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||
const uint8_t ZIGBEE_STATUS_ABORT = 99; // Fatal error, Zigbee not working
|
||||
|
||||
typedef int32_t (*ZB_Func)(uint8_t value);
|
||||
typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, class SBuffer &buf);
|
||||
|
||||
typedef union Zigbee_Instruction {
|
||||
struct {
|
||||
uint8_t i; // instruction
|
||||
uint8_t d8; // 8 bits data
|
||||
uint16_t d16; // 16 bits data
|
||||
} i;
|
||||
const void *p; // pointer
|
||||
// const void *m; // for type checking only, message
|
||||
// const ZB_Func f;
|
||||
// const ZB_RecvMsgFunc fr;
|
||||
} Zigbee_Instruction;
|
||||
//
|
||||
// Zigbee_Instruction z1 = { .i = {1,2,3}};
|
||||
// Zigbee_Instruction z3 = { .p = nullptr };
|
||||
|
||||
typedef struct Zigbee_Instruction_Type {
|
||||
uint8_t instr;
|
||||
uint8_t data;
|
||||
} Zigbee_Instruction_Type;
|
||||
|
||||
enum Zigbee_StateMachine_Instruction_Set {
|
||||
// 2 bytes instructions
|
||||
ZGB_INSTR_4_BYTES = 0,
|
||||
ZGB_INSTR_NOOP = 0, // do nothing
|
||||
ZGB_INSTR_LABEL, // define a label
|
||||
ZGB_INSTR_GOTO, // goto label
|
||||
ZGB_INSTR_ON_ERROR_GOTO, // goto label if error
|
||||
ZGB_INSTR_ON_TIMEOUT_GOTO, // goto label if timeout
|
||||
ZGB_INSTR_WAIT, // wait for x ms (in chunks of 100ms)
|
||||
ZGB_INSTR_WAIT_FOREVER, // wait forever but state machine still active
|
||||
ZGB_INSTR_STOP, // stop state machine with optional error code
|
||||
|
||||
// 6 bytes instructions
|
||||
ZGB_INSTR_8_BYTES = 0x80,
|
||||
ZGB_INSTR_CALL = 0x80, // call a function
|
||||
ZGB_INSTR_LOG, // log a message, if more detailed logging required, call a function
|
||||
ZGB_INSTR_MQTT_STATUS, // send MQTT status string with code
|
||||
ZGB_INSTR_SEND, // send a ZNP message
|
||||
ZGB_INSTR_WAIT_UNTIL, // wait until the specified message is received, ignore all others
|
||||
ZGB_INSTR_WAIT_RECV, // wait for a message according to the filter
|
||||
ZGB_ON_RECV_UNEXPECTED, // function to handle unexpected messages, or nullptr
|
||||
|
||||
// 10 bytes instructions
|
||||
ZGB_INSTR_12_BYTES = 0xF0,
|
||||
ZGB_INSTR_WAIT_RECV_CALL, // wait for a filtered message and call function upon receive
|
||||
};
|
||||
|
||||
#define ZI_NOOP() { .i = { ZGB_INSTR_NOOP, 0x00, 0x0000} },
|
||||
#define ZI_LABEL(x) { .i = { ZGB_INSTR_LABEL, (x), 0x0000} },
|
||||
#define ZI_GOTO(x) { .i = { ZGB_INSTR_GOTO, (x), 0x0000} },
|
||||
#define ZI_ON_ERROR_GOTO(x) { .i = { ZGB_INSTR_ON_ERROR_GOTO, (x), 0x0000} },
|
||||
#define ZI_ON_TIMEOUT_GOTO(x) { .i = { ZGB_INSTR_ON_TIMEOUT_GOTO, (x), 0x0000} },
|
||||
#define ZI_WAIT(x) { .i = { ZGB_INSTR_WAIT, 0x00, (x)} },
|
||||
#define ZI_WAIT_FOREVER() { .i = { ZGB_INSTR_WAIT_FOREVER, 0x00, 0x0000} },
|
||||
#define ZI_STOP(x) { .i = { ZGB_INSTR_STOP, (x), 0x0000} },
|
||||
|
||||
#define ZI_CALL(f, x) { .i = { ZGB_INSTR_CALL, (x), 0x0000} }, { .p = (const void*)(f) },
|
||||
#define ZI_LOG(x, m) { .i = { ZGB_INSTR_LOG, (x), 0x0000 } }, { .p = ((const void*)(m)) },
|
||||
#define ZI_MQTT_STATUS(x, m) { .i = { ZGB_INSTR_MQTT_STATUS, (x), 0x0000 } }, { .p = ((const void*)(m)) },
|
||||
#define ZI_ON_RECV_UNEXPECTED(f) { .i = { ZGB_ON_RECV_UNEXPECTED, 0x00, 0x0000} }, { .p = (const void*)(f) },
|
||||
#define ZI_SEND(m) { .i = { ZGB_INSTR_SEND, sizeof(m), 0x0000} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_RECV(x, m) { .i = { ZGB_INSTR_WAIT_RECV, sizeof(m), (x)} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_UNTIL(x, m) { .i = { ZGB_INSTR_WAIT_UNTIL, sizeof(m), (x)} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_RECV_FUNC(x, m, f) { .i = { ZGB_INSTR_WAIT_RECV_CALL, sizeof(m), (x)} }, { .p = (const void*)(m) }, { .p = (const void*)(f) },
|
||||
|
||||
// Labels used in the State Machine -- internal only
|
||||
const uint8_t ZIGBEE_LABEL_START = 10; // Start ZNP
|
||||
const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop
|
||||
const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_CLOSE = 30; // disable permit join
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60 = 31; // enable permit join for 60 seconds
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX = 32; // enable permit join for 60 seconds
|
||||
// errors
|
||||
const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error
|
||||
const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||
|
||||
struct ZigbeeStatus {
|
||||
bool active = true; // is Zigbee active for this device, i.e. GPIOs configured
|
||||
bool state_machine = false; // the state machine is running
|
||||
bool state_waiting = false; // the state machine is waiting for external event or timeout
|
||||
bool state_no_timeout = false; // the current wait loop does not generate a timeout but only continues running
|
||||
bool ready = false; // cc2530 initialization is complet, ready to operate
|
||||
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort
|
||||
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort
|
||||
int16_t pc = 0; // program counter, -1 means abort
|
||||
uint32_t next_timeout = 0; // millis for the next timeout
|
||||
|
||||
uint8_t *recv_filter = nullptr; // receive filter message
|
||||
bool recv_until = false; // ignore all messages until the received frame fully matches
|
||||
size_t recv_filter_len = 0;
|
||||
ZB_RecvMsgFunc recv_func = nullptr; // function to call when message is expected
|
||||
ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received
|
||||
|
||||
bool init_phase = true; // initialization phase, before accepting zigbee traffic
|
||||
};
|
||||
struct ZigbeeStatus zigbee;
|
||||
|
||||
SBuffer *zigbee_buffer = nullptr;
|
||||
|
||||
/*********************************************************************************************\
|
||||
* State Machine
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define Z_B0(a) (uint8_t)( ((a) ) & 0xFF )
|
||||
#define Z_B1(a) (uint8_t)( ((a) >> 8) & 0xFF )
|
||||
#define Z_B2(a) (uint8_t)( ((a) >> 16) & 0xFF )
|
||||
#define Z_B3(a) (uint8_t)( ((a) >> 24) & 0xFF )
|
||||
#define Z_B4(a) (uint8_t)( ((a) >> 32) & 0xFF )
|
||||
#define Z_B5(a) (uint8_t)( ((a) >> 40) & 0xFF )
|
||||
#define Z_B6(a) (uint8_t)( ((a) >> 48) & 0xFF )
|
||||
#define Z_B7(a) (uint8_t)( ((a) >> 56) & 0xFF )
|
||||
// Macro to define message to send and receive
|
||||
#define ZBM(n, x...) const uint8_t n[] PROGMEM = { x };
|
||||
|
||||
#define USE_ZIGBEE_CHANNEL_MASK (1 << (USE_ZIGBEE_CHANNEL))
|
||||
|
||||
// ZBS_* Zigbee Send
|
||||
// ZBR_* Zigbee Recv
|
||||
ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 ) // 410001 SYS_RESET_REQ Hardware reset
|
||||
ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND ) // 4180 SYS_RESET_REQ Hardware reset response
|
||||
|
||||
ZBM(ZBS_VERSION, Z_SREQ | Z_SYS, SYS_VERSION ) // 2102 Z_SYS:version
|
||||
ZBM(ZBR_VERSION, Z_SRSP | Z_SYS, SYS_VERSION ) // 6102 Z_SYS:version
|
||||
|
||||
// Check if ZNP_HAS_CONFIGURED is set
|
||||
ZBM(ZBS_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, 0x00 /* offset */ ) // 2108000F00 - 6108000155
|
||||
ZBM(ZBR_ZNPHC, Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_Success, 0x01 /* len */, 0x55) // 6108000155
|
||||
// If not set, the response is 61-08-02-00 = Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_InvalidParameter, 0x00 /* len */
|
||||
|
||||
ZBM(ZBS_PAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PANID ) // 260483
|
||||
ZBM(ZBR_PAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PANID, 0x02 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 6604008302xxxx
|
||||
|
||||
ZBM(ZBS_EXTPAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_EXTENDED_PAN_ID ) // 26042D
|
||||
ZBM(ZBR_EXTPAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_EXTENDED_PAN_ID,
|
||||
0x08 /* len */,
|
||||
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
|
||||
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID),
|
||||
) // 6604002D08xxxxxxxxxxxxxxxx
|
||||
|
||||
ZBM(ZBS_CHANN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_CHANLIST ) // 260484
|
||||
ZBM(ZBR_CHANN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_CHANLIST,
|
||||
0x04 /* len */,
|
||||
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
|
||||
) // 6604008404xxxxxxxx
|
||||
|
||||
ZBM(ZBS_PFGK, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEY ) // 260462
|
||||
ZBM(ZBR_PFGK, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEY,
|
||||
0x10 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
|
||||
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 660400621001030507090B0D0F00020406080A0C0D
|
||||
|
||||
ZBM(ZBS_PFGKEN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEYS_ENABLE ) // 260463
|
||||
ZBM(ZBR_PFGKEN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEYS_ENABLE,
|
||||
0x01 /* len */, 0x00 ) // 660400630100
|
||||
|
||||
// commands to "format" the device
|
||||
// Write configuration - write success
|
||||
ZBM(ZBR_W_OK, Z_SRSP | Z_SAPI, SAPI_WRITE_CONFIGURATION, Z_Success ) // 660500 - Write Configuration
|
||||
ZBM(ZBR_WNV_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Success ) // 610900 - NV Write
|
||||
|
||||
// Factory reset
|
||||
ZBM(ZBS_FACTRES, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x02 ) // 2605030102
|
||||
// Write PAN ID
|
||||
ZBM(ZBS_W_PAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PANID, 0x02 /* len */, Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 26058302xxxx
|
||||
// Write EXT PAN ID
|
||||
ZBM(ZBS_W_EXTPAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_EXTENDED_PAN_ID, 0x08 /* len */,
|
||||
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
|
||||
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID)
|
||||
) // 26052D086263151D004B1200
|
||||
// Write Channel ID
|
||||
ZBM(ZBS_W_CHANN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_CHANLIST, 0x04 /* len */,
|
||||
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
|
||||
/*0x00, 0x08, 0x00, 0x00*/ ) // 26058404xxxxxxxx
|
||||
// Write Logical Type = 00 = coordinator
|
||||
ZBM(ZBS_W_LOGTYP, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_LOGICAL_TYPE, 0x01 /* len */, 0x00 ) // 2605870100
|
||||
// Write precfgkey
|
||||
ZBM(ZBS_W_PFGK, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEY,
|
||||
0x10 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
|
||||
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 2605621001030507090B0D0F00020406080A0C0D
|
||||
// Write precfgkey enable
|
||||
ZBM(ZBS_W_PFGKEN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEYS_ENABLE, 0x01 /* len */, 0x00 ) // 2605630100
|
||||
// Write Security Mode
|
||||
ZBM(ZBS_WNV_SECMODE, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(CONF_TCLK_TABLE_START), Z_B1(CONF_TCLK_TABLE_START),
|
||||
0x00 /* offset */, 0x20 /* len */,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c,
|
||||
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // 2109010100200FFFFFFFFFFFFFFFF5A6967426565416C6C69616E636530390000000000000000
|
||||
// Write Z_ZDO Direct CB
|
||||
ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 0x01 /* len */, 0x01 ) // 26058F0101
|
||||
// NV Init ZNP Has Configured
|
||||
ZBM(ZBS_WNV_INITZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_ITEM_INIT, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8,
|
||||
0x01, 0x00 /* InitLen 16 bits */, 0x01 /* len */, 0x00 ) // 2107000F01000100 - 610709
|
||||
// Init succeeded
|
||||
//ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT, Z_Created ) // 610709 - NV Write
|
||||
ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT ) // 6107xx, Success if 610700 or 610709 - NV Write
|
||||
|
||||
// Write ZNP Has Configured
|
||||
ZBM(ZBS_WNV_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(ZNP_HAS_CONFIGURED), Z_B1(ZNP_HAS_CONFIGURED),
|
||||
0x00 /* offset */, 0x01 /* len */, 0x55 ) // 2109000F000155 - 610900
|
||||
// Z_ZDO:startupFromApp
|
||||
ZBM(ZBS_STARTUPFROMAPP, Z_SREQ | Z_ZDO, ZDO_STARTUP_FROM_APP, 100, 0 /* delay */) // 25406400
|
||||
ZBM(ZBR_STARTUPFROMAPP, Z_SRSP | Z_ZDO, ZDO_STARTUP_FROM_APP ) // 6540 + 01 for new network, 00 for exisitng network, 02 for error
|
||||
ZBM(AREQ_STARTUPFROMAPP, Z_AREQ | Z_ZDO, ZDO_STATE_CHANGE_IND, ZDO_DEV_ZB_COORD ) // 45C009 + 08 = starting, 09 = started
|
||||
// GetDeviceInfo
|
||||
ZBM(ZBS_GETDEVICEINFO, Z_SREQ | Z_UTIL, Z_UTIL_GET_DEVICE_INFO ) // 2700
|
||||
ZBM(ZBR_GETDEVICEINFO, Z_SRSP | Z_UTIL, Z_UTIL_GET_DEVICE_INFO, Z_Success ) // Ex= 6700.00.6263151D004B1200.0000.07.09.00
|
||||
// IEEE Adr (8 bytes) = 6263151D004B1200
|
||||
// Short Addr (2 bytes) = 0000
|
||||
// Device Type (1 byte) = 07 (coord?)
|
||||
// Device State (1 byte) = 09 (coordinator started)
|
||||
// NumAssocDevices (1 byte) = 00
|
||||
|
||||
// Read Pan ID
|
||||
//ZBM(ZBS_READ_NV_PANID, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, PANID & 0xFF, PANID >> 8, 0x00 /* offset */ ) // 2108830000
|
||||
|
||||
// Z_ZDO:nodeDescReq
|
||||
ZBM(ZBS_ZDO_NODEDESCREQ, Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, 0x00, 0x00 /* dst addr */, 0x00, 0x00 /* NWKAddrOfInterest */) // 250200000000
|
||||
ZBM(ZBR_ZDO_NODEDESCREQ, Z_SRSP | Z_ZDO, ZDO_NODE_DESC_REQ, Z_Success ) // 650200
|
||||
// Async resp ex: 4582.0000.00.0000.00.40.8F.0000.50.A000.0100.A000.00
|
||||
ZBM(AREQ_ZDO_NODEDESCRSP, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP) // 4582
|
||||
// SrcAddr (2 bytes) 0000
|
||||
// Status (1 byte) 00 Success
|
||||
// NwkAddr (2 bytes) 0000
|
||||
// LogicalType (1 byte) - 00 Coordinator
|
||||
// APSFlags (1 byte) - 40 0=APSFlags 4=NodeFreqBands
|
||||
// MACCapabilityFlags (1 byte) - 8F ALL
|
||||
// ManufacturerCode (2 bytes) - 0000
|
||||
// MaxBufferSize (1 byte) - 50 NPDU
|
||||
// MaxTransferSize (2 bytes) - A000 = 160
|
||||
// ServerMask (2 bytes) - 0100 - Primary Trust Center
|
||||
// MaxOutTransferSize (2 bytes) - A000 = 160
|
||||
// DescriptorCapabilities (1 byte) - 00
|
||||
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
|
||||
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
|
||||
|
||||
// Z_ZDO:activeEpReq
|
||||
ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00) // 250500000000
|
||||
ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_Success) // 65050000
|
||||
ZBM(ZBR_ZDO_ACTIVEEPRSP_NONE, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
|
||||
0x00, 0x00 /* nwkaddr */, 0x00 /* activeepcount */) // 45050000 - no Ep running
|
||||
ZBM(ZBR_ZDO_ACTIVEEPRSP_OK, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
|
||||
0x00, 0x00 /* nwkaddr */, 0x02 /* activeepcount */, 0x0B, 0x01 /* the actual endpoints */) // 25050000 - no Ep running
|
||||
|
||||
// Z_AF:register profile:104, ep:01
|
||||
ZBM(ZBS_AF_REGISTER01, Z_SREQ | Z_AF, AF_REGISTER, 0x01 /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 24000401050000000000
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
|
||||
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
|
||||
ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_Success) // 640000
|
||||
ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 2400040B050000000000
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
|
||||
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
|
||||
// Z_ZDO:mgmtPermitJoinReq
|
||||
ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 /* AddrMode */, // 25360200000000
|
||||
0x00, 0x00 /* DstAddr */, 0x00 /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBS_PERMITJOINREQ_OPEN_60, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFC3C00
|
||||
0xFC, 0xFF /* DstAddr */, 60 /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBS_PERMITJOINREQ_OPEN_XX, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFCFF00
|
||||
0xFC, 0xFF /* DstAddr */, 0xFF /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success) // 653600
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 /* Duration */) // 45CB00
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 /* Duration */) // 45CB3C
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_FF, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF /* Duration */) // 45CBFF
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_RSP, 0x00, 0x00 /* srcAddr*/, Z_Success ) // 45B6000000
|
||||
|
||||
// Filters for ZCL frames
|
||||
ZBM(ZBR_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
|
||||
ZBM(ZBR_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
|
||||
|
||||
static const Zigbee_Instruction zb_prog[] PROGMEM = {
|
||||
ZI_LABEL(0)
|
||||
ZI_NOOP()
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
|
||||
ZI_WAIT(15000) // wait for 15 seconds for Tasmota to stabilize
|
||||
ZI_ON_ERROR_GOTO(50)
|
||||
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: rebooting device")
|
||||
ZI_SEND(ZBS_RESET) // reboot cc2530 just in case we rebooted ESP8266 but not cc2530
|
||||
ZI_WAIT_RECV(5000, ZBR_RESET) // timeout 5s
|
||||
ZI_WAIT(100)
|
||||
ZI_LOG(LOG_LEVEL_INFO, "ZIG: checking device configuration")
|
||||
ZI_SEND(ZBS_ZNPHC) // check value of ZNP Has Configured
|
||||
ZI_WAIT_RECV(2000, ZBR_ZNPHC)
|
||||
ZI_WAIT(100)
|
||||
ZI_SEND(ZBS_VERSION) // check ZNP software version
|
||||
ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check version
|
||||
ZI_SEND(ZBS_PAN) // check PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_PAN)
|
||||
ZI_SEND(ZBS_EXTPAN) // check EXT PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_EXTPAN)
|
||||
ZI_SEND(ZBS_CHANN) // check CHANNEL
|
||||
ZI_WAIT_RECV(1000, ZBR_CHANN)
|
||||
ZI_SEND(ZBS_PFGK) // check PFGK
|
||||
ZI_WAIT_RECV(1000, ZBR_PFGK)
|
||||
ZI_SEND(ZBS_PFGKEN) // check PFGKEN
|
||||
ZI_WAIT_RECV(1000, ZBR_PFGKEN)
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee configuration ok")
|
||||
// all is good, we can start
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_START) // START ZNP App
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_STARTING, "Configured, starting coordinator")
|
||||
//ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
// Z_ZDO:startupFromApp
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: starting zigbee coordinator")
|
||||
ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator
|
||||
ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command
|
||||
ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started
|
||||
ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo
|
||||
ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
|
||||
//ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // TODO memorize info
|
||||
ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ)
|
||||
ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCRSP)
|
||||
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_NONE)
|
||||
ZI_SEND(ZBS_AF_REGISTER01) // Z_AF register for endpoint 01, profile 0x0104 Home Automation
|
||||
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
|
||||
ZI_SEND(ZBS_AF_REGISTER0B) // Z_AF register for endpoint 0B, profile 0x0104 Home Automation
|
||||
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
|
||||
// Z_ZDO:nodeDescReq ?? Is is useful to redo it? TODO
|
||||
// redo Z_ZDO:activeEpReq to check that Ep are available
|
||||
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_OK)
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
|
||||
//ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX) // Opening Permit Join, normally through command
|
||||
//ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_FF)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_READY)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_OK, "Started")
|
||||
ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device ready, listening...")
|
||||
ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
|
||||
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
ZI_WAIT_FOREVER()
|
||||
ZI_GOTO(ZIGBEE_LABEL_READY)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE)
|
||||
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_CLOSE, "Disable Pairing mode")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60)
|
||||
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_60, "Enable Pairing mode for 60 seconds")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_60)
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_60)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX)
|
||||
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_XX, "Enable Pairing mode until next boot")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX)
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_FF)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(50) // reformat device
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_RESET_CONF, "Reseting configuration")
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee bad configuration of device, doing a factory reset")
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_SEND(ZBS_FACTRES) // factory reset
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_RESET) // reset device
|
||||
ZI_WAIT_RECV(5000, ZBR_RESET)
|
||||
ZI_SEND(ZBS_W_PAN) // write PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_EXTPAN) // write EXT PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_CHANN) // write CHANNEL
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_LOGTYP) // write Logical Type = coordinator
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_PFGK) // write PRECFGKEY
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_PFGKEN) // write PRECFGKEY Enable
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_WNV_SECMODE) // write Security Mode
|
||||
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
|
||||
ZI_SEND(ZBS_W_ZDODCB) // write Z_ZDO Direct CB
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
// Now mark the device as ready, writing 0x55 in memory slot 0x0F00
|
||||
ZI_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured
|
||||
ZI_WAIT_RECV_FUNC(1000, ZBR_WNV_INIT_OK, &Z_CheckNVWrite)
|
||||
ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured
|
||||
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
|
||||
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device reconfigured")
|
||||
ZI_GOTO(ZIGBEE_LABEL_START)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported")
|
||||
ZI_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_ABORT, "Abort")
|
||||
ZI_LOG(LOG_LEVEL_ERROR, "ZIG: Abort")
|
||||
ZI_STOP(ZIGBEE_LABEL_ABORT)
|
||||
};
|
||||
|
||||
uint8_t ZigbeeGetInstructionSize(uint8_t instr) { // in Zigbee_Instruction lines (words)
|
||||
if (instr >= ZGB_INSTR_12_BYTES) {
|
||||
return 3;
|
||||
} else if (instr >= ZGB_INSTR_8_BYTES) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ZigbeeGotoLabel(uint8_t label) {
|
||||
// look for the label scanning entire code
|
||||
uint16_t goto_pc = 0xFFFF; // 0xFFFF means not found
|
||||
uint8_t cur_instr = 0;
|
||||
uint8_t cur_d8 = 0;
|
||||
uint8_t cur_instr_len = 1; // size of current instruction in words
|
||||
|
||||
for (uint32_t i = 0; i < sizeof(zb_prog)/sizeof(zb_prog[0]); i += cur_instr_len) {
|
||||
const Zigbee_Instruction *cur_instr_line = &zb_prog[i];
|
||||
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
|
||||
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZGB GOTO: pc %d instr %d"), i, cur_instr);
|
||||
|
||||
if (ZGB_INSTR_LABEL == cur_instr) {
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: found label %d at pc %d"), cur_d8, i);
|
||||
if (label == cur_d8) {
|
||||
// label found, goto to this pc
|
||||
zigbee.pc = i;
|
||||
zigbee.state_machine = true;
|
||||
zigbee.state_waiting = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// get instruction length
|
||||
cur_instr_len = ZigbeeGetInstructionSize(cur_instr);
|
||||
}
|
||||
|
||||
// no label found, abort
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Goto label not found, label=%d pc=%d"), label, zigbee.pc);
|
||||
if (ZIGBEE_LABEL_ABORT != label) {
|
||||
// if not already looking for ZIGBEE_LABEL_ABORT, goto ZIGBEE_LABEL_ABORT
|
||||
ZigbeeGotoLabel(ZIGBEE_LABEL_ABORT);
|
||||
} else {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Label Abort (%d) not present, aborting Zigbee"), ZIGBEE_LABEL_ABORT);
|
||||
zigbee.state_machine = false;
|
||||
zigbee.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ZigbeeStateMachine_Run(void) {
|
||||
uint8_t cur_instr = 0;
|
||||
uint8_t cur_d8 = 0;
|
||||
uint16_t cur_d16 = 0;
|
||||
const void* cur_ptr1 = nullptr;
|
||||
const void* cur_ptr2 = nullptr;
|
||||
uint32_t now = millis();
|
||||
|
||||
if (zigbee.state_waiting) { // state machine is waiting for external event or timeout
|
||||
// checking if timeout expired
|
||||
if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) { // if next_timeout == 0 then wait forever
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout occured pc=%d"), zigbee.pc);
|
||||
if (!zigbee.state_no_timeout) {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout, goto label %d"), zigbee.on_timeout_goto);
|
||||
ZigbeeGotoLabel(zigbee.on_timeout_goto);
|
||||
} else {
|
||||
zigbee.state_waiting = false; // simply stop waiting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((zigbee.state_machine) && (!zigbee.state_waiting)) {
|
||||
// reinit receive filters and functions (they only work for a single instruction)
|
||||
zigbee.recv_filter = nullptr;
|
||||
zigbee.recv_func = nullptr;
|
||||
zigbee.recv_until = false;
|
||||
zigbee.state_no_timeout = false; // reset the no_timeout for next instruction
|
||||
|
||||
if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Invalid pc: %d, aborting"), zigbee.pc);
|
||||
zigbee.pc = -1;
|
||||
}
|
||||
if (zigbee.pc < 0) {
|
||||
zigbee.state_machine = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// load current instruction details
|
||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Executing instruction pc=%d"), zigbee.pc);
|
||||
const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc];
|
||||
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
|
||||
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
|
||||
cur_d16 = pgm_read_word(&cur_instr_line->i.d16);
|
||||
if (cur_instr >= ZGB_INSTR_8_BYTES) {
|
||||
cur_instr_line++;
|
||||
cur_ptr1 = cur_instr_line->p;
|
||||
}
|
||||
if (cur_instr >= ZGB_INSTR_12_BYTES) {
|
||||
cur_instr_line++;
|
||||
cur_ptr2 = cur_instr_line->p;
|
||||
}
|
||||
|
||||
zigbee.pc += ZigbeeGetInstructionSize(cur_instr); // move pc to next instruction, before any goto
|
||||
|
||||
switch (cur_instr) {
|
||||
case ZGB_INSTR_NOOP:
|
||||
case ZGB_INSTR_LABEL: // do nothing
|
||||
break;
|
||||
case ZGB_INSTR_GOTO:
|
||||
ZigbeeGotoLabel(cur_d8);
|
||||
break;
|
||||
case ZGB_INSTR_ON_ERROR_GOTO:
|
||||
zigbee.on_error_goto = cur_d8;
|
||||
break;
|
||||
case ZGB_INSTR_ON_TIMEOUT_GOTO:
|
||||
zigbee.on_timeout_goto = cur_d8;
|
||||
break;
|
||||
case ZGB_INSTR_WAIT:
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_FOREVER:
|
||||
zigbee.next_timeout = 0;
|
||||
zigbee.state_waiting = true;
|
||||
//zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
|
||||
break;
|
||||
case ZGB_INSTR_STOP:
|
||||
zigbee.state_machine = false;
|
||||
if (cur_d8) {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Stopping (%d)"), cur_d8);
|
||||
}
|
||||
break;
|
||||
case ZGB_INSTR_CALL:
|
||||
if (cur_ptr1) {
|
||||
uint32_t res;
|
||||
res = (*((ZB_Func)cur_ptr1))(cur_d8);
|
||||
if (res > 0) {
|
||||
ZigbeeGotoLabel(res);
|
||||
continue; // avoid incrementing PC after goto
|
||||
} else if (res == 0) {
|
||||
// do nothing
|
||||
} else if (res == -1) {
|
||||
// do nothing
|
||||
} else {
|
||||
ZigbeeGotoLabel(zigbee.on_error_goto);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ZGB_INSTR_LOG:
|
||||
AddLog_P(cur_d8, (char*) cur_ptr1);
|
||||
break;
|
||||
case ZGB_INSTR_MQTT_STATUS:
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{\"Status\":%d,\"Message\":\"%s\"}}"),
|
||||
cur_d8, (char*) cur_ptr1);
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
break;
|
||||
case ZGB_INSTR_SEND:
|
||||
ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */);
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_UNTIL:
|
||||
zigbee.recv_until = true; // and reuse ZGB_INSTR_WAIT_RECV
|
||||
case ZGB_INSTR_WAIT_RECV:
|
||||
zigbee.recv_filter = (uint8_t *) cur_ptr1;
|
||||
zigbee.recv_filter_len = cur_d8; // len
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
break;
|
||||
case ZGB_ON_RECV_UNEXPECTED:
|
||||
zigbee.recv_unexpected = (ZB_RecvMsgFunc) cur_ptr1;
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_RECV_CALL:
|
||||
zigbee.recv_filter = (uint8_t *) cur_ptr1;
|
||||
zigbee.recv_filter_len = cur_d8; // len
|
||||
zigbee.recv_func = (ZB_RecvMsgFunc) cur_ptr2;
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
|
@ -0,0 +1,372 @@
|
|||
/*
|
||||
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Theo Arends and Stephan Hadinger
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
|
||||
// Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991
|
||||
// IEEE Adr (8 bytes) = 0x00124B001D156362
|
||||
// Short Addr (2 bytes) = 0x0000
|
||||
// Device Type (1 byte) = 0x07 (coord?)
|
||||
// Device State (1 byte) = 0x09 (coordinator started)
|
||||
// NumAssocDevices (1 byte) = 0x02
|
||||
// List of devices: 0x8683, 0x9199
|
||||
Z_IEEEAddress long_adr = buf.get64(3);
|
||||
Z_ShortAddress short_adr = buf.get16(11);
|
||||
uint8_t device_type = buf.get8(13);
|
||||
uint8_t device_state = buf.get8(14);
|
||||
uint8_t device_associated = buf.get8(15);
|
||||
|
||||
char hex[20];
|
||||
Uint64toHex(long_adr, hex, 64);
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
||||
",\"DeviceType\":%d,\"DeviceState\":%d"
|
||||
",\"NumAssocDevices\":%d"),
|
||||
ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state,
|
||||
device_associated);
|
||||
|
||||
if (device_associated > 0) {
|
||||
uint idx = 16;
|
||||
ResponseAppend_P(PSTR(",\"AssocDevicesList\":["));
|
||||
for (uint32_t i = 0; i < device_associated; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx));
|
||||
idx += 2;
|
||||
}
|
||||
ResponseAppend_P(PSTR("]"));
|
||||
}
|
||||
|
||||
ResponseJsonEnd(); // append '}'
|
||||
ResponseJsonEnd(); // append '}'
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) {
|
||||
// Check the status after NV Init "ZNP Has Configured"
|
||||
// Good response should be 610700 or 610709 (Success or Created)
|
||||
// We only filter the response on 6107 and check the code in this function
|
||||
uint8_t status = buf.get8(2);
|
||||
if ((0x00 == status) || (0x09 == status)) {
|
||||
return 0; // Ok, continue
|
||||
} else {
|
||||
return -2; // Error
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
|
||||
// check that the version is supported
|
||||
// typical version for ZNP 1.2
|
||||
// 61020200-02.06.03.D9143401.0200000000
|
||||
// TranportRev = 02
|
||||
// Product = 00
|
||||
// MajorRel = 2
|
||||
// MinorRel = 6
|
||||
// MaintRel = 3
|
||||
// Revision = 20190425 d (0x013414D9)
|
||||
uint8_t major_rel = buf.get8(4);
|
||||
uint8_t minor_rel = buf.get8(5);
|
||||
uint8_t maint_rel = buf.get8(6);
|
||||
uint32_t revision = buf.get32(7);
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"MajorRel\":%d,\"MinorRel\":%d"
|
||||
",\"MaintRel\":%d,\"Revision\":%d}}"),
|
||||
ZIGBEE_STATUS_CC_VERSION, major_rel, minor_rel,
|
||||
maint_rel, revision);
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
|
||||
if ((0x02 == major_rel) && (0x06 == minor_rel)) {
|
||||
return 0; // version 2.6.x is ok
|
||||
} else {
|
||||
return ZIGBEE_LABEL_UNSUPPORTED_VERSION; // abort
|
||||
}
|
||||
}
|
||||
|
||||
bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) {
|
||||
if ( (pgm_read_byte(&match[0]) == buf.get8(0)) &&
|
||||
(pgm_read_byte(&match[1]) == buf.get8(1)) ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
|
||||
// we received a PermitJoin status change
|
||||
uint8_t duration = buf.get8(2);
|
||||
uint8_t status_code;
|
||||
const char* message;
|
||||
|
||||
if (0xFF == duration) {
|
||||
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_XX;
|
||||
message = PSTR("Enable Pairing mode until next boot");
|
||||
} else if (duration > 0) {
|
||||
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_60;
|
||||
message = PSTR("Enable Pairing mode for %d seconds");
|
||||
} else {
|
||||
status_code = ZIGBEE_STATUS_PERMITJOIN_CLOSE;
|
||||
message = PSTR("Disable Pairing mode");
|
||||
}
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"Message\":\""),
|
||||
status_code);
|
||||
ResponseAppend_P(message, duration);
|
||||
ResponseAppend_P(PSTR("\"}}"));
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send ACTIVE_EP_REQ to collect active endpoints for this address
|
||||
void Z_SendActiveEpReq(uint16_t shortaddr) {
|
||||
uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ,
|
||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
||||
|
||||
uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
|
||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
||||
|
||||
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
|
||||
//ZigbeeZNPSend(NodeDescReq, sizeof(NodeDescReq)); Not sure this is useful
|
||||
}
|
||||
|
||||
// Send ZDO_SIMPLE_DESC_REQ to get full list of supported Clusters for a specific endpoint
|
||||
void Z_SendSimpleDescReq(uint16_t shortaddr, uint8_t endpoint) {
|
||||
uint8_t SimpleDescReq[] = { Z_SREQ | Z_ZDO, ZDO_SIMPLE_DESC_REQ, // 2504
|
||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr),
|
||||
endpoint };
|
||||
|
||||
ZigbeeZNPSend(SimpleDescReq, sizeof(SimpleDescReq));
|
||||
}
|
||||
|
||||
const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" };
|
||||
int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
|
||||
// Received ZDO_NODE_DESC_RSP
|
||||
Z_ShortAddress srcAddr = buf.get16(2);
|
||||
uint8_t status = buf.get8(4);
|
||||
Z_ShortAddress nwkAddr = buf.get16(5);
|
||||
uint8_t logicalType = buf.get8(7);
|
||||
uint8_t apsFlags = buf.get8(8);
|
||||
uint8_t MACCapabilityFlags = buf.get8(9);
|
||||
uint16_t manufacturerCapabilities = buf.get16(10);
|
||||
uint8_t maxBufferSize = buf.get8(12);
|
||||
uint16_t maxInTransferSize = buf.get16(13);
|
||||
uint16_t serverMask = buf.get16(15);
|
||||
uint16_t maxOutTransferSize = buf.get16(17);
|
||||
uint8_t descriptorCapabilities = buf.get8(19);
|
||||
|
||||
if (0 == status) {
|
||||
uint8_t deviceType = logicalType & 0x7; // 0=coordinator, 1=router, 2=end device
|
||||
if (deviceType > 3) { deviceType = 3; }
|
||||
bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0;
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"NodeType\":\"%s\",\"ComplexDesc\":%s}}"),
|
||||
ZIGBEE_STATUS_NODE_DESC, Z_DeviceType[deviceType],
|
||||
complexDescriptorAvailable ? "true" : "false"
|
||||
);
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
|
||||
// Received ZDO_ACTIVE_EP_RSP
|
||||
Z_ShortAddress srcAddr = buf.get16(2);
|
||||
uint8_t status = buf.get8(4);
|
||||
Z_ShortAddress nwkAddr = buf.get16(5);
|
||||
uint8_t activeEpCount = buf.get8(7);
|
||||
uint8_t* activeEpList = (uint8_t*) buf.charptr(8);
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < activeEpCount; i++) {
|
||||
Z_AddDeviceEndpoint(nwkAddr, activeEpList[i]);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < activeEpCount; i++) {
|
||||
Z_SendSimpleDescReq(nwkAddr, activeEpList[i]);
|
||||
}
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"ActiveEndpoints\":["),
|
||||
ZIGBEE_STATUS_ACTIVE_EP);
|
||||
for (uint32_t i = 0; i < activeEpCount; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%02X\""), activeEpList[i]);
|
||||
}
|
||||
ResponseAppend_P(PSTR("]}}"));
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
||||
// Received ZDO_SIMPLE_DESC_RSP
|
||||
Z_ShortAddress srcAddr = buf.get16(2);
|
||||
uint8_t status = buf.get8(4);
|
||||
Z_ShortAddress nwkAddr = buf.get16(5);
|
||||
uint8_t lenDescriptor = buf.get8(7);
|
||||
uint8_t endpoint = buf.get8(8);
|
||||
uint16_t profileId = buf.get16(9); // The profile Id for this endpoint.
|
||||
uint16_t deviceId = buf.get16(11); // The Device Description Id for this endpoint.
|
||||
uint8_t deviceVersion = buf.get8(13); // 0 – Version 1.00
|
||||
uint8_t numInCluster = buf.get8(14);
|
||||
uint8_t numOutCluster = buf.get8(15 + numInCluster*2);
|
||||
|
||||
if (0 == status) {
|
||||
for (uint32_t i = 0; i < numInCluster; i++) {
|
||||
Z_AddDeviceCluster(nwkAddr, endpoint, buf.get16(15 + i*2), false);
|
||||
}
|
||||
for (uint32_t i = 0; i < numOutCluster; i++) {
|
||||
Z_AddDeviceCluster(nwkAddr, endpoint, buf.get16(16 + numInCluster*2 + i*2), true);
|
||||
}
|
||||
// String dump = Z_DumpDevices();
|
||||
// Serial.printf(">>> Devices dump = %s\n", dump.c_str());
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"Endpoint\":\"0x%02X\""
|
||||
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVerion\":%d"
|
||||
"\"InClusters\":["),
|
||||
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
|
||||
profileId, deviceId, deviceVersion);
|
||||
for (uint32_t i = 0; i < numInCluster; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(15 + i*2));
|
||||
}
|
||||
ResponseAppend_P(PSTR("],\"OutClusters\":["));
|
||||
for (uint32_t i = 0; i < numOutCluster; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(16 + numInCluster*2 + i*2));
|
||||
}
|
||||
ResponseAppend_P(PSTR("]}}"));
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
|
||||
Z_ShortAddress srcAddr = buf.get16(2);
|
||||
Z_ShortAddress nwkAddr = buf.get16(4);
|
||||
Z_IEEEAddress ieeeAddr = buf.get64(6);
|
||||
uint8_t capabilities = buf.get8(14);
|
||||
|
||||
Z_AddDeviceLongAddr(nwkAddr, ieeeAddr);
|
||||
// String dump = Z_DumpDevices();
|
||||
// Serial.printf(">>> Devices dump = %s\n", dump.c_str());
|
||||
|
||||
char hex[20];
|
||||
Uint64toHex(ieeeAddr, hex, 64);
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
||||
",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"),
|
||||
ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr,
|
||||
(capabilities & 0x04) ? "true" : "false",
|
||||
(capabilities & 0x08) ? "true" : "false",
|
||||
(capabilities & 0x40) ? "true" : "false"
|
||||
);
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
Z_SendActiveEpReq(nwkAddr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
||||
uint16_t groupid = buf.get16(2);
|
||||
uint16_t clusterid = buf.get16(4);
|
||||
Z_ShortAddress srcaddr = buf.get16(6);
|
||||
uint8_t srcendpoint = buf.get8(8);
|
||||
uint8_t dstendpoint = buf.get8(9);
|
||||
uint8_t wasbroadcast = buf.get8(10);
|
||||
uint8_t linkquality = buf.get8(11);
|
||||
uint8_t securityuse = buf.get8(12);
|
||||
uint32_t timestamp = buf.get32(13);
|
||||
uint8_t seqnumber = buf.get8(17);
|
||||
|
||||
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
|
||||
|
||||
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
|
||||
srcendpoint, dstendpoint, wasbroadcast,
|
||||
linkquality, securityuse, seqnumber,
|
||||
timestamp);
|
||||
|
||||
char shortaddr[8];
|
||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
||||
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json_root = jsonBuffer.createObject();
|
||||
JsonObject& json = json_root.createNestedObject(shortaddr);
|
||||
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
|
||||
zcl_received.parseRawAttributes(json);
|
||||
} else if (zcl_received.isClusterSpecificCommand()) {
|
||||
zcl_received.parseClusterSpecificCommand(json);
|
||||
}
|
||||
zcl_received.postProcessAttributes(json);
|
||||
|
||||
String msg("");
|
||||
msg.reserve(100);
|
||||
json_root.printTo(msg);
|
||||
|
||||
Response_P(PSTR("%s"), msg.c_str());
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
|
||||
// Default message handler for new messages
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Z_Recv_Default"));
|
||||
if (zigbee.init_phase) {
|
||||
// if still during initialization phase, ignore any unexpected message
|
||||
return -1; // ignore message
|
||||
} else {
|
||||
if (Z_ReceiveMatchPrefix(buf, ZBR_AF_INCOMING_MESSAGE)) {
|
||||
return Z_ReceiveAfIncomingMessage(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, ZBR_END_DEVICE_ANNCE_IND)) {
|
||||
return Z_ReceiveEndDeviceAnnonce(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, ZBR_PERMITJOIN_AREQ_OPEN_XX)) {
|
||||
return Z_ReceivePermitJoinStatus(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_NODEDESCRSP)) {
|
||||
return Z_ReceiveNodeDesc(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_ACTIVEEPRSP)) {
|
||||
return Z_ReceiveActiveEp(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_SIMPLEDESCRSP)) {
|
||||
return Z_ReceiveSimpleDesc(res, buf);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Z_State_Ready(uint8_t value) {
|
||||
zigbee.init_phase = false; // initialization phase complete
|
||||
return 0; // continue
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
|
@ -24,20 +24,6 @@
|
|||
const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255
|
||||
const uint8_t ZIGBEE_SOF = 0xFE;
|
||||
|
||||
// Status code used for ZigbeeStatus MQTT message
|
||||
// Ex: {"ZigbeeStatus":{"code": 3,"message":"Configured, starting coordinator"}}
|
||||
const uint8_t ZIGBEE_STATUS_OK = 0; // Zigbee started and working
|
||||
const uint8_t ZIGBEE_STATUS_BOOT = 1; // CC2530 booting
|
||||
const uint8_t ZIGBEE_STATUS_RESET_CONF = 2; // Resetting CC2530 configuration
|
||||
const uint8_t ZIGBEE_STATUS_STARTING = 3; // Starting CC2530 as coordinator
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_CLOSE = 20; // Disable PermitJoin
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_60 = 21; // Enable PermitJoin for 60 seconds
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_XX = 22; // Enable PermitJoin until next boot
|
||||
const uint8_t ZIGBEE_STATUS_DEVICE_VERSION = 50; // Status: CC2530 ZNP Version
|
||||
const uint8_t ZIGBEE_STATUS_DEVICE_INFO = 51; // Status: CC2530 Device Configuration
|
||||
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||
const uint8_t ZIGBEE_STATUS_ABORT = 99; // Fatal error, Zigbee not working
|
||||
|
||||
//#define Z_USE_SOFTWARE_SERIAL
|
||||
|
||||
#ifdef Z_USE_SOFTWARE_SERIAL
|
||||
|
@ -49,739 +35,11 @@ TasmotaSerial *ZigbeeSerial = nullptr;
|
|||
#endif
|
||||
|
||||
|
||||
const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN;
|
||||
const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN
|
||||
"|" D_CMND_ZIGBEE_DUMP;
|
||||
|
||||
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin };
|
||||
|
||||
typedef int32_t (*ZB_Func)(uint8_t value);
|
||||
typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, class SBuffer &buf);
|
||||
|
||||
typedef union Zigbee_Instruction {
|
||||
struct {
|
||||
uint8_t i; // instruction
|
||||
uint8_t d8; // 8 bits data
|
||||
uint16_t d16; // 16 bits data
|
||||
} i;
|
||||
const void *p; // pointer
|
||||
// const void *m; // for type checking only, message
|
||||
// const ZB_Func f;
|
||||
// const ZB_RecvMsgFunc fr;
|
||||
} Zigbee_Instruction;
|
||||
//
|
||||
// Zigbee_Instruction z1 = { .i = {1,2,3}};
|
||||
// Zigbee_Instruction z3 = { .p = nullptr };
|
||||
|
||||
typedef struct Zigbee_Instruction_Type {
|
||||
uint8_t instr;
|
||||
uint8_t data;
|
||||
} Zigbee_Instruction_Type;
|
||||
|
||||
enum Zigbee_StateMachine_Instruction_Set {
|
||||
// 2 bytes instructions
|
||||
ZGB_INSTR_4_BYTES = 0,
|
||||
ZGB_INSTR_NOOP = 0, // do nothing
|
||||
ZGB_INSTR_LABEL, // define a label
|
||||
ZGB_INSTR_GOTO, // goto label
|
||||
ZGB_INSTR_ON_ERROR_GOTO, // goto label if error
|
||||
ZGB_INSTR_ON_TIMEOUT_GOTO, // goto label if timeout
|
||||
ZGB_INSTR_WAIT, // wait for x ms (in chunks of 100ms)
|
||||
ZGB_INSTR_WAIT_FOREVER, // wait forever but state machine still active
|
||||
ZGB_INSTR_STOP, // stop state machine with optional error code
|
||||
|
||||
// 6 bytes instructions
|
||||
ZGB_INSTR_8_BYTES = 0x80,
|
||||
ZGB_INSTR_CALL = 0x80, // call a function
|
||||
ZGB_INSTR_LOG, // log a message, if more detailed logging required, call a function
|
||||
ZGB_INSTR_MQTT_STATUS, // send MQTT status string with code
|
||||
ZGB_INSTR_SEND, // send a ZNP message
|
||||
ZGB_INSTR_WAIT_UNTIL, // wait until the specified message is received, ignore all others
|
||||
ZGB_INSTR_WAIT_RECV, // wait for a message according to the filter
|
||||
ZGB_ON_RECV_UNEXPECTED, // function to handle unexpected messages, or nullptr
|
||||
|
||||
// 10 bytes instructions
|
||||
ZGB_INSTR_12_BYTES = 0xF0,
|
||||
ZGB_INSTR_WAIT_RECV_CALL, // wait for a filtered message and call function upon receive
|
||||
};
|
||||
|
||||
#define ZI_NOOP() { .i = { ZGB_INSTR_NOOP, 0x00, 0x0000} },
|
||||
#define ZI_LABEL(x) { .i = { ZGB_INSTR_LABEL, (x), 0x0000} },
|
||||
#define ZI_GOTO(x) { .i = { ZGB_INSTR_GOTO, (x), 0x0000} },
|
||||
#define ZI_ON_ERROR_GOTO(x) { .i = { ZGB_INSTR_ON_ERROR_GOTO, (x), 0x0000} },
|
||||
#define ZI_ON_TIMEOUT_GOTO(x) { .i = { ZGB_INSTR_ON_TIMEOUT_GOTO, (x), 0x0000} },
|
||||
#define ZI_WAIT(x) { .i = { ZGB_INSTR_WAIT, 0x00, (x)} },
|
||||
#define ZI_WAIT_FOREVER() { .i = { ZGB_INSTR_WAIT_FOREVER, 0x00, 0x0000} },
|
||||
#define ZI_STOP(x) { .i = { ZGB_INSTR_STOP, (x), 0x0000} },
|
||||
|
||||
#define ZI_CALL(f, x) { .i = { ZGB_INSTR_CALL, (x), 0x0000} }, { .p = (const void*)(f) },
|
||||
#define ZI_LOG(x, m) { .i = { ZGB_INSTR_LOG, (x), 0x0000 } }, { .p = ((const void*)(m)) },
|
||||
#define ZI_MQTT_STATUS(x, m) { .i = { ZGB_INSTR_MQTT_STATUS, (x), 0x0000 } }, { .p = ((const void*)(m)) },
|
||||
#define ZI_ON_RECV_UNEXPECTED(f) { .i = { ZGB_ON_RECV_UNEXPECTED, 0x00, 0x0000} }, { .p = (const void*)(f) },
|
||||
#define ZI_SEND(m) { .i = { ZGB_INSTR_SEND, sizeof(m), 0x0000} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_RECV(x, m) { .i = { ZGB_INSTR_WAIT_RECV, sizeof(m), (x)} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_UNTIL(x, m) { .i = { ZGB_INSTR_WAIT_UNTIL, sizeof(m), (x)} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_RECV_FUNC(x, m, f) { .i = { ZGB_INSTR_WAIT_RECV_CALL, sizeof(m), (x)} }, { .p = (const void*)(m) }, { .p = (const void*)(f) },
|
||||
|
||||
// Labels used in the State Machine -- internal only
|
||||
const uint8_t ZIGBEE_LABEL_START = 10; // Start ZNP
|
||||
const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop
|
||||
const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_CLOSE = 30; // disable permit join
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60 = 31; // enable permit join for 60 seconds
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX = 32; // enable permit join for 60 seconds
|
||||
// errors
|
||||
const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error
|
||||
const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||
|
||||
struct ZigbeeStatus {
|
||||
bool active = true; // is Zigbee active for this device, i.e. GPIOs configured
|
||||
bool state_machine = false; // the state machine is running
|
||||
bool state_waiting = false; // the state machine is waiting for external event or timeout
|
||||
bool state_no_timeout = false; // the current wait loop does not generate a timeout but only continues running
|
||||
bool ready = false; // cc2530 initialization is complet, ready to operate
|
||||
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort
|
||||
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort
|
||||
int16_t pc = 0; // program counter, -1 means abort
|
||||
uint32_t next_timeout = 0; // millis for the next timeout
|
||||
|
||||
uint8_t *recv_filter = nullptr; // receive filter message
|
||||
bool recv_until = false; // ignore all messages until the received frame fully matches
|
||||
size_t recv_filter_len = 0;
|
||||
ZB_RecvMsgFunc recv_func = nullptr; // function to call when message is expected
|
||||
ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received
|
||||
|
||||
bool init_phase = true; // initialization phase, before accepting zigbee traffic
|
||||
};
|
||||
struct ZigbeeStatus zigbee;
|
||||
|
||||
SBuffer *zigbee_buffer = nullptr;
|
||||
|
||||
/*********************************************************************************************\
|
||||
* State Machine
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define Z_B0(a) (uint8_t)( ((a) ) & 0xFF )
|
||||
#define Z_B1(a) (uint8_t)( ((a) >> 8) & 0xFF )
|
||||
#define Z_B2(a) (uint8_t)( ((a) >> 16) & 0xFF )
|
||||
#define Z_B3(a) (uint8_t)( ((a) >> 24) & 0xFF )
|
||||
#define Z_B4(a) (uint8_t)( ((a) >> 32) & 0xFF )
|
||||
#define Z_B5(a) (uint8_t)( ((a) >> 40) & 0xFF )
|
||||
#define Z_B6(a) (uint8_t)( ((a) >> 48) & 0xFF )
|
||||
#define Z_B7(a) (uint8_t)( ((a) >> 56) & 0xFF )
|
||||
// Macro to define message to send and receive
|
||||
#define ZBM(n, x...) const uint8_t n[] PROGMEM = { x };
|
||||
|
||||
// ZBS_* Zigbee Send
|
||||
// ZBR_* Zigbee Recv
|
||||
ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 ) // 410001 SYS_RESET_REQ Hardware reset
|
||||
ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND ) // 4180 SYS_RESET_REQ Hardware reset response
|
||||
|
||||
ZBM(ZBS_VERSION, Z_SREQ | Z_SYS, SYS_VERSION ) // 2102 Z_SYS:version
|
||||
ZBM(ZBR_VERSION, Z_SRSP | Z_SYS, SYS_VERSION ) // 6102 Z_SYS:version
|
||||
|
||||
// Check if ZNP_HAS_CONFIGURED is set
|
||||
ZBM(ZBS_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, 0x00 /* offset */ ) // 2108000F00 - 6108000155
|
||||
ZBM(ZBR_ZNPHC, Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_Success, 0x01 /* len */, 0x55) // 6108000155
|
||||
// If not set, the response is 61-08-02-00 = Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_InvalidParameter, 0x00 /* len */
|
||||
|
||||
ZBM(ZBS_PAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PANID ) // 260483
|
||||
ZBM(ZBR_PAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PANID, 0x02 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 6604008302xxxx
|
||||
|
||||
ZBM(ZBS_EXTPAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_EXTENDED_PAN_ID ) // 26042D
|
||||
ZBM(ZBR_EXTPAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_EXTENDED_PAN_ID,
|
||||
0x08 /* len */,
|
||||
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
|
||||
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID),
|
||||
) // 6604002D08xxxxxxxxxxxxxxxx
|
||||
|
||||
ZBM(ZBS_CHANN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_CHANLIST ) // 260484
|
||||
ZBM(ZBR_CHANN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_CHANLIST,
|
||||
0x04 /* len */,
|
||||
Z_B0(USE_ZIGBEE_CHANNEL), Z_B1(USE_ZIGBEE_CHANNEL), Z_B2(USE_ZIGBEE_CHANNEL), Z_B3(USE_ZIGBEE_CHANNEL),
|
||||
) // 6604008404xxxxxxxx
|
||||
|
||||
ZBM(ZBS_PFGK, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEY ) // 260462
|
||||
ZBM(ZBR_PFGK, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEY,
|
||||
0x10 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
|
||||
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 660400621001030507090B0D0F00020406080A0C0D
|
||||
|
||||
ZBM(ZBS_PFGKEN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEYS_ENABLE ) // 260463
|
||||
ZBM(ZBR_PFGKEN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEYS_ENABLE,
|
||||
0x01 /* len */, 0x00 ) // 660400630100
|
||||
|
||||
// commands to "format" the device
|
||||
// Write configuration - write success
|
||||
ZBM(ZBR_W_OK, Z_SRSP | Z_SAPI, SAPI_WRITE_CONFIGURATION, Z_Success ) // 660500 - Write Configuration
|
||||
ZBM(ZBR_WNV_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Success ) // 610900 - NV Write
|
||||
|
||||
// Factory reset
|
||||
ZBM(ZBS_FACTRES, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x02 ) // 2605030102
|
||||
// Write PAN ID
|
||||
ZBM(ZBS_W_PAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PANID, 0x02 /* len */, Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 26058302xxxx
|
||||
// Write EXT PAN ID
|
||||
ZBM(ZBS_W_EXTPAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_EXTENDED_PAN_ID, 0x08 /* len */,
|
||||
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
|
||||
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID)
|
||||
) // 26052D086263151D004B1200
|
||||
// Write Channel ID
|
||||
ZBM(ZBS_W_CHANN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_CHANLIST, 0x04 /* len */,
|
||||
Z_B0(USE_ZIGBEE_CHANNEL), Z_B1(USE_ZIGBEE_CHANNEL), Z_B2(USE_ZIGBEE_CHANNEL), Z_B3(USE_ZIGBEE_CHANNEL),
|
||||
/*0x00, 0x08, 0x00, 0x00*/ ) // 26058404xxxxxxxx
|
||||
// Write Logical Type = 00 = coordinator
|
||||
ZBM(ZBS_W_LOGTYP, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_LOGICAL_TYPE, 0x01 /* len */, 0x00 ) // 2605870100
|
||||
// Write precfgkey
|
||||
ZBM(ZBS_W_PFGK, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEY,
|
||||
0x10 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
|
||||
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 2605621001030507090B0D0F00020406080A0C0D
|
||||
// Write precfgkey enable
|
||||
ZBM(ZBS_W_PFGKEN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEYS_ENABLE, 0x01 /* len */, 0x00 ) // 2605630100
|
||||
// Write Security Mode
|
||||
ZBM(ZBS_WNV_SECMODE, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(CONF_TCLK_TABLE_START), Z_B1(CONF_TCLK_TABLE_START),
|
||||
0x00 /* offset */, 0x20 /* len */,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c,
|
||||
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // 2109010100200FFFFFFFFFFFFFFFF5A6967426565416C6C69616E636530390000000000000000
|
||||
// Write Z_ZDO Direct CB
|
||||
ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 0x01 /* len */, 0x01 ) // 26058F0101
|
||||
// NV Init ZNP Has Configured
|
||||
ZBM(ZBS_WNV_INITZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_ITEM_INIT, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8,
|
||||
0x01, 0x00 /* InitLen 16 bits */, 0x01 /* len */, 0x00 ) // 2107000F01000100 - 610709
|
||||
// Init succeeded
|
||||
ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT, Z_Created ) // 610709 - NV Write
|
||||
// Write ZNP Has Configured
|
||||
ZBM(ZBS_WNV_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(ZNP_HAS_CONFIGURED), Z_B1(ZNP_HAS_CONFIGURED),
|
||||
0x00 /* offset */, 0x01 /* len */, 0x55 ) // 2109000F000155 - 610900
|
||||
// Z_ZDO:startupFromApp
|
||||
ZBM(ZBS_STARTUPFROMAPP, Z_SREQ | Z_ZDO, ZDO_STARTUP_FROM_APP, 100, 0 /* delay */) // 25406400
|
||||
ZBM(ZBR_STARTUPFROMAPP, Z_SRSP | Z_ZDO, ZDO_STARTUP_FROM_APP ) // 6540 + 01 for new network, 00 for exisitng network, 02 for error
|
||||
ZBM(AREQ_STARTUPFROMAPP, Z_AREQ | Z_ZDO, ZDO_STATE_CHANGE_IND, ZDO_DEV_ZB_COORD ) // 45C009 + 08 = starting, 09 = started
|
||||
// GetDeviceInfo
|
||||
ZBM(ZBS_GETDEVICEINFO, Z_SREQ | Z_UTIL, Z_UTIL_GET_DEVICE_INFO ) // 2700
|
||||
ZBM(ZBR_GETDEVICEINFO, Z_SRSP | Z_UTIL, Z_UTIL_GET_DEVICE_INFO, Z_Success ) // Ex= 6700.00.6263151D004B1200.0000.07.09.00
|
||||
// IEEE Adr (8 bytes) = 6263151D004B1200
|
||||
// Short Addr (2 bytes) = 0000
|
||||
// Device Type (1 byte) = 07 (coord?)
|
||||
// Device State (1 byte) = 09 (coordinator started)
|
||||
// NumAssocDevices (1 byte) = 00
|
||||
|
||||
// Read Pan ID
|
||||
//ZBM(ZBS_READ_NV_PANID, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, PANID & 0xFF, PANID >> 8, 0x00 /* offset */ ) // 2108830000
|
||||
|
||||
// Z_ZDO:nodeDescReq
|
||||
ZBM(ZBS_ZDO_NODEDESCREQ, Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, 0x00, 0x00 /* dst addr */, 0x00, 0x00 /* NWKAddrOfInterest */) // 250200000000
|
||||
ZBM(ZBR_ZDO_NODEDESCREQ, Z_SRSP | Z_ZDO, ZDO_NODE_DESC_REQ, Z_Success ) // 650200
|
||||
// Async resp ex: 4582.0000.00.0000.00.40.8F.0000.50.A000.0100.A000.00
|
||||
ZBM(AREQ_ZDO_NODEDESCREQ, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP) // 4582
|
||||
// SrcAddr (2 bytes) 0000
|
||||
// Status (1 byte) 00 Success
|
||||
// NwkAddr (2 bytes) 0000
|
||||
// LogicalType (1 byte) - 00 Coordinator
|
||||
// APSFlags (1 byte) - 40 0=APSFlags 4=NodeFreqBands
|
||||
// MACCapabilityFlags (1 byte) - 8F ALL
|
||||
// ManufacturerCode (2 bytes) - 0000
|
||||
// MaxBufferSize (1 byte) - 50 NPDU
|
||||
// MaxTransferSize (2 bytes) - A000 = 160
|
||||
// ServerMask (2 bytes) - 0100 - Primary Trust Center
|
||||
// MaxOutTransferSize (2 bytes) - A000 = 160
|
||||
// DescriptorCapabilities (1 byte) - 00
|
||||
|
||||
// Z_ZDO:activeEpReq
|
||||
ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00) // 250500000000
|
||||
ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_Success) // 65050000
|
||||
ZBM(ZBR_ZDO_ACTIVEEPRSP_NONE, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
|
||||
0x00, 0x00 /* nwkaddr */, 0x00 /* activeepcount */) // 45050000 - no Ep running
|
||||
ZBM(ZBR_ZDO_ACTIVEEPRSP_OK, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
|
||||
0x00, 0x00 /* nwkaddr */, 0x02 /* activeepcount */, 0x0B, 0x01 /* the actual endpoints */) // 25050000 - no Ep running
|
||||
|
||||
// Z_AF:register profile:104, ep:01
|
||||
ZBM(ZBS_AF_REGISTER01, Z_SREQ | Z_AF, AF_REGISTER, 0x01 /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 24000401050000000000
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
|
||||
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
|
||||
ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_Success) // 640000
|
||||
ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 2400040B050000000000
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
|
||||
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
|
||||
// Z_ZDO:mgmtPermitJoinReq
|
||||
ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 /* AddrMode */, // 25360200000000
|
||||
0x00, 0x00 /* DstAddr */, 0x00 /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBS_PERMITJOINREQ_OPEN_60, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFC3C00
|
||||
0xFC, 0xFF /* DstAddr */, 60 /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBS_PERMITJOINREQ_OPEN_XX, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFCFF00
|
||||
0xFC, 0xFF /* DstAddr */, 0xFF /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success) // 653600
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 /* Duration */) // 45CB00
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 /* Duration */) // 45CB3C
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF /* Duration */) // 45CBFF
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_RSP, 0x00, 0x00 /* srcAddr*/, Z_Success ) // 45B6000000
|
||||
|
||||
// Filters for ZCL frames
|
||||
ZBM(ZBR_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
|
||||
|
||||
static const Zigbee_Instruction zb_prog[] PROGMEM = {
|
||||
ZI_LABEL(0)
|
||||
ZI_NOOP()
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
|
||||
ZI_WAIT(15000) // wait for 15 seconds for Tasmota to stabilize
|
||||
ZI_ON_ERROR_GOTO(50)
|
||||
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: rebooting device")
|
||||
ZI_SEND(ZBS_RESET) // reboot cc2530 just in case we rebooted ESP8266 but not cc2530
|
||||
ZI_WAIT_RECV(5000, ZBR_RESET) // timeout 5s
|
||||
ZI_LOG(LOG_LEVEL_INFO, "ZIG: checking device configuration")
|
||||
ZI_SEND(ZBS_ZNPHC) // check value of ZNP Has Configured
|
||||
ZI_WAIT_RECV(2000, ZBR_ZNPHC)
|
||||
ZI_SEND(ZBS_VERSION) // check ZNP software version
|
||||
ZI_WAIT_RECV_FUNC(1000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check version
|
||||
ZI_SEND(ZBS_PAN) // check PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_PAN)
|
||||
ZI_SEND(ZBS_EXTPAN) // check EXT PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_EXTPAN)
|
||||
ZI_SEND(ZBS_CHANN) // check CHANNEL
|
||||
ZI_WAIT_RECV(1000, ZBR_CHANN)
|
||||
ZI_SEND(ZBS_PFGK) // check PFGK
|
||||
ZI_WAIT_RECV(1000, ZBR_PFGK)
|
||||
ZI_SEND(ZBS_PFGKEN) // check PFGKEN
|
||||
ZI_WAIT_RECV(1000, ZBR_PFGKEN)
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee configuration ok")
|
||||
// all is good, we can start
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_START) // START ZNP App
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_STARTING, "Configured, starting coordinator")
|
||||
//ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
// Z_ZDO:startupFromApp
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: starting zigbee coordinator")
|
||||
ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator
|
||||
ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command
|
||||
ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started
|
||||
ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo
|
||||
ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
|
||||
//ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // TODO memorize info
|
||||
ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ)
|
||||
ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCREQ)
|
||||
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_NONE)
|
||||
ZI_SEND(ZBS_AF_REGISTER01) // Z_AF register for endpoint 01, profile 0x0104 Home Automation
|
||||
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
|
||||
ZI_SEND(ZBS_AF_REGISTER0B) // Z_AF register for endpoint 0B, profile 0x0104 Home Automation
|
||||
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
|
||||
// Z_ZDO:nodeDescReq ?? Is is useful to redo it? TODO
|
||||
// redo Z_ZDO:activeEpReq to check that Ep are available
|
||||
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_OK)
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
|
||||
//ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX) // Opening Permit Join, normally through command
|
||||
//ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_XX)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_READY)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_OK, "Started")
|
||||
ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device ready, listening...")
|
||||
ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
|
||||
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
ZI_WAIT_FOREVER()
|
||||
ZI_GOTO(ZIGBEE_LABEL_READY)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_CLOSE, "Disable Pairing mode")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_60, "Enable Pairing mode for 60 seconds")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_60)
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_60)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_XX, "Enable Pairing mode until next boot")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX)
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_XX)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(50) // reformat device
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_RESET_CONF, "Reseting configuration")
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee bad configuration of device, doing a factory reset")
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_SEND(ZBS_FACTRES) // factory reset
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_RESET) // reset device
|
||||
ZI_WAIT_RECV(5000, ZBR_RESET)
|
||||
ZI_SEND(ZBS_W_PAN) // write PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_EXTPAN) // write EXT PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_CHANN) // write CHANNEL
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_LOGTYP) // write Logical Type = coordinator
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_PFGK) // write PRECFGKEY
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_PFGKEN) // write PRECFGKEY Enable
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_WNV_SECMODE) // write Security Mode
|
||||
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
|
||||
ZI_SEND(ZBS_W_ZDODCB) // write Z_ZDO Direct CB
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
// Now mark the device as ready, writing 0x55 in memory slot 0x0F00
|
||||
ZI_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured
|
||||
ZI_WAIT_RECV(1000, ZBR_WNV_INIT_OK)
|
||||
ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured
|
||||
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
|
||||
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device reconfigured")
|
||||
ZI_GOTO(ZIGBEE_LABEL_START)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported")
|
||||
ZI_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_ABORT, "Abort")
|
||||
ZI_LOG(LOG_LEVEL_ERROR, "ZIG: Abort")
|
||||
ZI_STOP(ZIGBEE_LABEL_ABORT)
|
||||
};
|
||||
|
||||
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
|
||||
// Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991
|
||||
// IEEE Adr (8 bytes) = 0x00124B001D156362
|
||||
// Short Addr (2 bytes) = 0x0000
|
||||
// Device Type (1 byte) = 0x07 (coord?)
|
||||
// Device State (1 byte) = 0x09 (coordinator started)
|
||||
// NumAssocDevices (1 byte) = 0x02
|
||||
// List of devices: 0x8683, 0x9199
|
||||
Z_IEEEAddress long_adr = buf.get64(3);
|
||||
Z_ShortAddress short_adr = buf.get16(11);
|
||||
uint8_t device_type = buf.get8(13);
|
||||
uint8_t device_state = buf.get8(14);
|
||||
uint8_t device_associated = buf.get8(15);
|
||||
|
||||
char hex[20];
|
||||
Uint64toHex(long_adr, hex, 64);
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"code\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
||||
",\"DeviceType\":%d,\"DeviceState\":%d"
|
||||
",\"NumAssocDevices\":%d"),
|
||||
ZIGBEE_STATUS_DEVICE_INFO, hex, short_adr, device_type, device_state,
|
||||
device_associated);
|
||||
|
||||
if (device_associated > 0) {
|
||||
uint idx = 16;
|
||||
ResponseAppend_P(PSTR(",\"AssocDevicesList\":["));
|
||||
for (uint32_t i = 0; i < device_associated; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx));
|
||||
idx += 2;
|
||||
}
|
||||
ResponseAppend_P(PSTR("]"));
|
||||
}
|
||||
|
||||
ResponseJsonEnd(); // append '}'
|
||||
ResponseJsonEnd(); // append '}'
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
|
||||
// check that the version is supported
|
||||
// typical version for ZNP 1.2
|
||||
// 61020200-02.06.03.D9143401.0200000000
|
||||
// TranportRev = 02
|
||||
// Product = 00
|
||||
// MajorRel = 2
|
||||
// MinorRel = 6
|
||||
// MaintRel = 3
|
||||
// Revision = 20190425 d (0x013414D9)
|
||||
uint8_t major_rel = buf.get8(4);
|
||||
uint8_t minor_rel = buf.get8(5);
|
||||
uint8_t maint_rel = buf.get8(6);
|
||||
uint32_t revision = buf.get32(7);
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"code\":%d,\"MajorRel\":%d,\"MinorRel\":%d"
|
||||
",\"MaintRel\":%d,\"Revision\":%d}}"),
|
||||
ZIGBEE_STATUS_DEVICE_VERSION, major_rel, minor_rel,
|
||||
maint_rel, revision);
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
|
||||
if ((0x02 == major_rel) && (0x06 == minor_rel)) {
|
||||
return 0; // version 2.6.x is ok
|
||||
} else {
|
||||
return ZIGBEE_LABEL_UNSUPPORTED_VERSION; // abort
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Z_Recv_Default(int32_t res, class SBuffer &buf) {
|
||||
// Default message handler for new messages
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Z_Recv_Default"));
|
||||
if (zigbee.init_phase) {
|
||||
// if still during initialization phase, ignore any unexpected message
|
||||
return -1; // ignore message
|
||||
} else {
|
||||
if ( (pgm_read_byte(&ZBR_AF_INCOMING_MESSAGE[0]) == buf.get8(0)) &&
|
||||
(pgm_read_byte(&ZBR_AF_INCOMING_MESSAGE[1]) == buf.get8(1)) ) {
|
||||
uint16_t groupid = buf.get16(2);
|
||||
uint16_t clusterid = buf.get16(4);
|
||||
Z_ShortAddress srcaddr = buf.get16(6);
|
||||
uint8_t srcendpoint = buf.get8(8);
|
||||
uint8_t dstendpoint = buf.get8(9);
|
||||
uint8_t wasbroadcast = buf.get8(10);
|
||||
uint8_t linkquality = buf.get8(11);
|
||||
uint8_t securityuse = buf.get8(12);
|
||||
uint32_t timestamp = buf.get32(13);
|
||||
uint8_t seqnumber = buf.get8(17);
|
||||
|
||||
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
|
||||
|
||||
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
|
||||
srcendpoint, dstendpoint, wasbroadcast,
|
||||
linkquality, securityuse, seqnumber,
|
||||
timestamp);
|
||||
|
||||
char shortaddr[8];
|
||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
||||
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json_root = jsonBuffer.createObject();
|
||||
JsonObject& json = json_root.createNestedObject(shortaddr);
|
||||
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
|
||||
zcl_received.parseRawAttributes(json);
|
||||
} else if (zcl_received.isClusterSpecificCommand()) {
|
||||
zcl_received.parseClusterSpecificCommand(json);
|
||||
}
|
||||
zcl_received.postProcessAttributes(json);
|
||||
|
||||
String msg("");
|
||||
msg.reserve(100);
|
||||
json_root.printTo(msg);
|
||||
|
||||
Response_P(PSTR("%s"), msg.c_str());
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Z_State_Ready(uint8_t value) {
|
||||
zigbee.init_phase = false; // initialization phase complete
|
||||
return 0; // continue
|
||||
}
|
||||
|
||||
uint8_t ZigbeeGetInstructionSize(uint8_t instr) { // in Zigbee_Instruction lines (words)
|
||||
if (instr >= ZGB_INSTR_12_BYTES) {
|
||||
return 3;
|
||||
} else if (instr >= ZGB_INSTR_8_BYTES) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ZigbeeGotoLabel(uint8_t label) {
|
||||
// look for the label scanning entire code
|
||||
uint16_t goto_pc = 0xFFFF; // 0xFFFF means not found
|
||||
uint8_t cur_instr = 0;
|
||||
uint8_t cur_d8 = 0;
|
||||
uint8_t cur_instr_len = 1; // size of current instruction in words
|
||||
|
||||
for (uint32_t i = 0; i < sizeof(zb_prog)/sizeof(zb_prog[0]); i += cur_instr_len) {
|
||||
const Zigbee_Instruction *cur_instr_line = &zb_prog[i];
|
||||
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
|
||||
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZGB GOTO: pc %d instr %d"), i, cur_instr);
|
||||
|
||||
if (ZGB_INSTR_LABEL == cur_instr) {
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: found label %d at pc %d"), cur_d8, i);
|
||||
if (label == cur_d8) {
|
||||
// label found, goto to this pc
|
||||
zigbee.pc = i;
|
||||
zigbee.state_machine = true;
|
||||
zigbee.state_waiting = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// get instruction length
|
||||
cur_instr_len = ZigbeeGetInstructionSize(cur_instr);
|
||||
}
|
||||
|
||||
// no label found, abort
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Goto label not found, label=%d pc=%d"), label, zigbee.pc);
|
||||
if (ZIGBEE_LABEL_ABORT != label) {
|
||||
// if not already looking for ZIGBEE_LABEL_ABORT, goto ZIGBEE_LABEL_ABORT
|
||||
ZigbeeGotoLabel(ZIGBEE_LABEL_ABORT);
|
||||
} else {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Label Abort (%d) not present, aborting Zigbee"), ZIGBEE_LABEL_ABORT);
|
||||
zigbee.state_machine = false;
|
||||
zigbee.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ZigbeeStateMachine_Run(void) {
|
||||
uint8_t cur_instr = 0;
|
||||
uint8_t cur_d8 = 0;
|
||||
uint16_t cur_d16 = 0;
|
||||
const void* cur_ptr1 = nullptr;
|
||||
const void* cur_ptr2 = nullptr;
|
||||
uint32_t now = millis();
|
||||
|
||||
if (zigbee.state_waiting) { // state machine is waiting for external event or timeout
|
||||
// checking if timeout expired
|
||||
if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) { // if next_timeout == 0 then wait forever
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout occured pc=%d"), zigbee.pc);
|
||||
if (!zigbee.state_no_timeout) {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout, goto label %d"), zigbee.on_timeout_goto);
|
||||
ZigbeeGotoLabel(zigbee.on_timeout_goto);
|
||||
} else {
|
||||
zigbee.state_waiting = false; // simply stop waiting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((zigbee.state_machine) && (!zigbee.state_waiting)) {
|
||||
// reinit receive filters and functions (they only work for a single instruction)
|
||||
zigbee.recv_filter = nullptr;
|
||||
zigbee.recv_func = nullptr;
|
||||
zigbee.recv_until = false;
|
||||
zigbee.state_no_timeout = false; // reset the no_timeout for next instruction
|
||||
|
||||
if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Invalid pc: %d, aborting"), zigbee.pc);
|
||||
zigbee.pc = -1;
|
||||
}
|
||||
if (zigbee.pc < 0) {
|
||||
zigbee.state_machine = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// load current instruction details
|
||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Executing instruction pc=%d"), zigbee.pc);
|
||||
const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc];
|
||||
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
|
||||
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
|
||||
cur_d16 = pgm_read_word(&cur_instr_line->i.d16);
|
||||
if (cur_instr >= ZGB_INSTR_8_BYTES) {
|
||||
cur_instr_line++;
|
||||
cur_ptr1 = cur_instr_line->p;
|
||||
}
|
||||
if (cur_instr >= ZGB_INSTR_12_BYTES) {
|
||||
cur_instr_line++;
|
||||
cur_ptr2 = cur_instr_line->p;
|
||||
}
|
||||
|
||||
zigbee.pc += ZigbeeGetInstructionSize(cur_instr); // move pc to next instruction, before any goto
|
||||
|
||||
switch (cur_instr) {
|
||||
case ZGB_INSTR_NOOP:
|
||||
case ZGB_INSTR_LABEL: // do nothing
|
||||
break;
|
||||
case ZGB_INSTR_GOTO:
|
||||
ZigbeeGotoLabel(cur_d8);
|
||||
break;
|
||||
case ZGB_INSTR_ON_ERROR_GOTO:
|
||||
zigbee.on_error_goto = cur_d8;
|
||||
break;
|
||||
case ZGB_INSTR_ON_TIMEOUT_GOTO:
|
||||
zigbee.on_timeout_goto = cur_d8;
|
||||
break;
|
||||
case ZGB_INSTR_WAIT:
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_FOREVER:
|
||||
zigbee.next_timeout = 0;
|
||||
zigbee.state_waiting = true;
|
||||
//zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
|
||||
break;
|
||||
case ZGB_INSTR_STOP:
|
||||
zigbee.state_machine = false;
|
||||
if (cur_d8) {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Stopping (%d)"), cur_d8);
|
||||
}
|
||||
break;
|
||||
case ZGB_INSTR_CALL:
|
||||
if (cur_ptr1) {
|
||||
uint32_t res;
|
||||
res = (*((ZB_Func)cur_ptr1))(cur_d8);
|
||||
if (res > 0) {
|
||||
ZigbeeGotoLabel(res);
|
||||
continue; // avoid incrementing PC after goto
|
||||
} else if (res == 0) {
|
||||
// do nothing
|
||||
} else if (res == -1) {
|
||||
// do nothing
|
||||
} else {
|
||||
ZigbeeGotoLabel(zigbee.on_error_goto);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ZGB_INSTR_LOG:
|
||||
AddLog_P(cur_d8, (char*) cur_ptr1);
|
||||
break;
|
||||
case ZGB_INSTR_MQTT_STATUS:
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{\"code\":%d,\"message\":\"%s\"}}"),
|
||||
cur_d8, (char*) cur_ptr1);
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
break;
|
||||
case ZGB_INSTR_SEND:
|
||||
ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */);
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_UNTIL:
|
||||
zigbee.recv_until = true; // and reuse ZGB_INSTR_WAIT_RECV
|
||||
case ZGB_INSTR_WAIT_RECV:
|
||||
zigbee.recv_filter = (uint8_t *) cur_ptr1;
|
||||
zigbee.recv_filter_len = cur_d8; // len
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
break;
|
||||
case ZGB_ON_RECV_UNEXPECTED:
|
||||
zigbee.recv_unexpected = (ZB_RecvMsgFunc) cur_ptr1;
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_RECV_CALL:
|
||||
zigbee.recv_filter = (uint8_t *) cur_ptr1;
|
||||
zigbee.recv_filter_len = cur_d8; // len
|
||||
zigbee.recv_func = (ZB_RecvMsgFunc) cur_ptr2;
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
|
||||
&CmndZigbeeDump };
|
||||
|
||||
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
||||
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
|
||||
|
@ -932,7 +190,7 @@ void ZigbeeInput(void)
|
|||
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
|
||||
|
||||
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
|
||||
ResponseTime_P(PSTR(",\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
|
||||
|
@ -976,6 +234,13 @@ void ZigbeeInit(void)
|
|||
* Commands
|
||||
\*********************************************************************************************/
|
||||
|
||||
void CmndZigbeeDump(void) {
|
||||
if (ZigbeeSerial) {
|
||||
String dump = Z_DumpDevices();
|
||||
Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, dump.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void CmndZigbeeZNPSend(void)
|
||||
{
|
||||
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
|
||||
|
@ -1031,7 +296,7 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
|
|||
XdrvRulesProcess();
|
||||
}
|
||||
|
||||
|
||||
// Allow or Deny pairing of new Zigbee devices
|
||||
void CmndZigbeePermitJoin(void)
|
||||
{
|
||||
uint32_t payload = XdrvMailbox.payload;
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
xdrv_26_sm2135.ino - sm2135 I2C five channel led support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Theo Arends
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
#ifdef USE_SM2135
|
||||
/*********************************************************************************************\
|
||||
* SM2135 I2C RGBCW Led bulbs like Action LSC SmartLed
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define XDRV_26 26
|
||||
|
||||
#define SM2135_ADDR 0x40 // 0x40 .. 0x46
|
||||
|
||||
//#define SM2135_CURRENT 0x24 // Defaults: 20mA for RGB, 30mA for CW
|
||||
#define SM2135_CURRENT 0x16 // 3 x 15mA for RGB, 2 x 40mA/2 for CW
|
||||
|
||||
#define SM2135_RGB 0x00
|
||||
#define SM2135_CW 0x80
|
||||
|
||||
struct SM2135 {
|
||||
bool found = true;
|
||||
} Sm2135;
|
||||
|
||||
bool Sm2135SetChannels(void)
|
||||
{
|
||||
char *buffer = XdrvMailbox.data;
|
||||
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SM1: R %d G %d B %d, C %d W %d"), buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]);
|
||||
|
||||
if (('\0' == buffer[0]) && ('\0' == buffer[1]) && ('\0' == buffer[2])) {
|
||||
// No color so must be Cold/Warm
|
||||
if ((buffer[3] + buffer[4]) >= (1 * 256)) {
|
||||
// Scale down to 255 total to fix max power usage of 9W (=40mA)
|
||||
// Currently not needed with setting 2 x 40mA/2 = 40mA = 9W = 255 (handled by lights.ino)
|
||||
|
||||
buffer[3] >>= 1; // Divide by 2
|
||||
buffer[4] >>= 1; // Divide by 2
|
||||
}
|
||||
Wire.beginTransmission(SM2135_ADDR);
|
||||
Wire.write(SM2135_CURRENT); // Set current to 40mA
|
||||
Wire.write(SM2135_CW); // Select CW - Shutdown RGB?
|
||||
Wire.endTransmission();
|
||||
delay(1);
|
||||
Wire.beginTransmission(SM2135_ADDR +5);
|
||||
Wire.write(buffer[3]); // Cold
|
||||
Wire.write(buffer[4]); // Warm
|
||||
Wire.endTransmission();
|
||||
} else {
|
||||
// Color
|
||||
if ((buffer[0] + buffer[1] + buffer[2]) >= (3 * 256)) {
|
||||
// Scale down to 765 total to fix max power usage of 9W
|
||||
// Currently not needed with setting 3 x 15mA = 45mA = 11W = 765
|
||||
}
|
||||
Wire.beginTransmission(SM2135_ADDR);
|
||||
Wire.write(SM2135_CURRENT); // Set current to 15mA
|
||||
Wire.write(SM2135_RGB); // Select RGB - Shutdown CW?
|
||||
Wire.write(buffer[0]); // Red
|
||||
Wire.write(buffer[1]); // Green
|
||||
Wire.write(buffer[2]); // Blue
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sm2135ModuleSelected(void)
|
||||
{
|
||||
if (I2cDevice(SM2135_ADDR)) {
|
||||
|
||||
// Make sure it is the SM2135 chip as it's address is also used by HTU21, INA219, INA226
|
||||
// EXPERIMENTAL: Need further testing
|
||||
|
||||
light_type = LT_RGBWC;
|
||||
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "SM2135", SM2135_ADDR);
|
||||
} else {
|
||||
Sm2135.found = false;
|
||||
}
|
||||
return Sm2135.found;
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Interface
|
||||
\*********************************************************************************************/
|
||||
|
||||
bool Xdrv26(uint8_t function)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (i2c_flg && Sm2135.found) {
|
||||
switch (function) {
|
||||
case FUNC_SET_CHANNELS:
|
||||
result = Sm2135SetChannels();
|
||||
break;
|
||||
case FUNC_MODULE_INIT:
|
||||
result = Sm2135ModuleSelected();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // USE_SM2135
|
||||
#endif // USE_LIGHT
|
|
@ -0,0 +1,594 @@
|
|||
/*
|
||||
xdrv_27_shutter.ino - Shutter/Blind support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Stefan Bode
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_SHUTTER
|
||||
/*********************************************************************************************\
|
||||
* Shutter or Blind support using two consecutive relays
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define XDRV_27 27
|
||||
|
||||
#define D_PRFX_SHUTTER "Shutter"
|
||||
#define D_CMND_SHUTTER_OPEN "Open"
|
||||
#define D_CMND_SHUTTER_CLOSE "Close"
|
||||
#define D_CMND_SHUTTER_STOP "Stop"
|
||||
#define D_CMND_SHUTTER_POSITION "Position"
|
||||
#define D_CMND_SHUTTER_OPENTIME "OpenDuration"
|
||||
#define D_CMND_SHUTTER_CLOSETIME "CloseDuration"
|
||||
#define D_CMND_SHUTTER_RELAY "Relay"
|
||||
#define D_CMND_SHUTTER_SETHALFWAY "SetHalfway"
|
||||
#define D_CMND_SHUTTER_SETCLOSE "SetClose"
|
||||
#define D_CMND_SHUTTER_INVERT "Invert"
|
||||
#define D_CMND_SHUTTER_CLIBRATION "Calibration"
|
||||
|
||||
#define D_SHUTTER "SHUTTER"
|
||||
|
||||
const uint16_t MOTOR_STOP_TIME = 500; // in mS
|
||||
|
||||
enum ShutterModes { SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE };
|
||||
|
||||
const char kShutterCommands[] PROGMEM = D_PRFX_SHUTTER "|"
|
||||
D_CMND_SHUTTER_OPEN "|" D_CMND_SHUTTER_CLOSE "|" D_CMND_SHUTTER_STOP "|" D_CMND_SHUTTER_POSITION "|"
|
||||
D_CMND_SHUTTER_OPENTIME "|" D_CMND_SHUTTER_CLOSETIME "|" D_CMND_SHUTTER_RELAY "|"
|
||||
D_CMND_SHUTTER_SETHALFWAY "|" D_CMND_SHUTTER_SETCLOSE "|" D_CMND_SHUTTER_INVERT "|" D_CMND_SHUTTER_CLIBRATION;
|
||||
|
||||
void (* const ShutterCommand[])(void) PROGMEM = {
|
||||
&CmndShutterOpen, &CmndShutterClose, &CmndShutterStop, &CmndShutterPosition,
|
||||
&CmndShutterOpenTime, &CmndShutterCloseTime, &CmndShutterRelay,
|
||||
&CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration };
|
||||
|
||||
const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"direction\":%d}";
|
||||
|
||||
#include <Ticker.h>
|
||||
|
||||
Ticker TickerShutter;
|
||||
|
||||
struct SHUTTER {
|
||||
power_t mask = 0; // bit mask with 11 at the position of relays that belong to at least ONE shutter
|
||||
power_t old_power = 0; // preserve old bitmask for power to extract the relay that changes.
|
||||
power_t switched_relay = 0; // bitmatrix that contain the relays that was lastly changed.
|
||||
uint32_t time[MAX_SHUTTERS];
|
||||
int32_t open_max[MAX_SHUTTERS]; // max value on maximum open calculated
|
||||
int32_t target_position[MAX_SHUTTERS]; // position to go to
|
||||
int32_t start_position[MAX_SHUTTERS];
|
||||
int32_t real_position[MAX_SHUTTERS]; // value between 0 and Shutter.open_max
|
||||
uint16_t open_time[MAX_SHUTTERS]; // duration to open the shutter
|
||||
uint16_t close_time[MAX_SHUTTERS]; // duration to close the shutter
|
||||
uint16_t close_velocity[MAX_SHUTTERS]; // in relation to open velocity. higher value = faster
|
||||
uint16_t operations[MAX_SHUTTERS];
|
||||
int8_t direction[MAX_SHUTTERS]; // 1 == UP , 0 == stop; -1 == down
|
||||
uint8_t mode = 0; // operation mode definition. see enum type above SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE
|
||||
} Shutter;
|
||||
|
||||
void ShutterRtc50mS(void)
|
||||
{
|
||||
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
|
||||
Shutter.time[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t ShutterPercentToRealPosition(uint8_t percent,uint8_t index)
|
||||
{
|
||||
if (Settings.shutter_set50percent[index] != 50) {
|
||||
return percent <= 5 ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index];
|
||||
} else {
|
||||
return percent <= 5 ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ShutterRealToPercentPosition(int32_t realpos, uint8_t index)
|
||||
{
|
||||
if (Settings.shutter_set50percent[index] != 50) {
|
||||
return Settings.shuttercoeff[2][index] * 5 > realpos ? realpos / Settings.shuttercoeff[2][index] : (realpos-Settings.shuttercoeff[0][index]) / Settings.shuttercoeff[1][index];
|
||||
} else {
|
||||
return Settings.shuttercoeff[2][index] * 5 > realpos ? realpos / Settings.shuttercoeff[2][index] : (realpos-Settings.shuttercoeff[0][index]) / Settings.shuttercoeff[1][index];
|
||||
}
|
||||
}
|
||||
|
||||
void ShutterInit(void)
|
||||
{
|
||||
shutters_present = 0;
|
||||
Shutter.mask = 0;
|
||||
//Initialize to get relay that changed
|
||||
Shutter.old_power = power;
|
||||
char shutter_open_chr[10];
|
||||
char shutter_close_chr[10];
|
||||
bool relay_in_interlock = false;
|
||||
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Accuracy digits: %d"), Settings.shutter_accuracy);
|
||||
|
||||
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
|
||||
// upgrade to 0.1sec calculation base.
|
||||
if ( Settings.shutter_accuracy == 0) {
|
||||
Settings.shutter_closetime[i] = Settings.shutter_closetime[i] * 10;
|
||||
Settings.shutter_opentime[i] = Settings.shutter_opentime[i] * 10;
|
||||
}
|
||||
// set startrelay to 1 on first init, but only to shutter 1. 90% usecase
|
||||
Settings.shutter_startrelay[i] = (Settings.shutter_startrelay[i] == 0 && i == 0? 1 : Settings.shutter_startrelay[i]);
|
||||
if (Settings.shutter_startrelay[i] && Settings.shutter_startrelay[i] <9) {
|
||||
shutters_present++;
|
||||
|
||||
// Determine shutter types
|
||||
Shutter.mask |= 3 << (Settings.shutter_startrelay[i] -1) ;
|
||||
|
||||
for (uint32_t j = 0; j < MAX_INTERLOCKS * Settings.flag.interlock; j++) {
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Interlock state i=%d %d, flag %d, , shuttermask %d, maskedIL %d"),i, Settings.interlock[i], Settings.flag.interlock,Shutter.mask, Settings.interlock[i]&Shutter.mask);
|
||||
if (Settings.interlock[j] && Settings.interlock[j] & Shutter.mask) {
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Relay in Interlock group"));
|
||||
relay_in_interlock = true;
|
||||
}
|
||||
}
|
||||
if (relay_in_interlock) {
|
||||
if (Settings.pulse_timer[i] > 0) {
|
||||
Shutter.mode = SHT_PULSE_OPEN__PULSE_CLOSE;
|
||||
} else {
|
||||
Shutter.mode = SHT_OFF_OPEN__OFF_CLOSE;
|
||||
}
|
||||
} else {
|
||||
Shutter.mode = SHT_OFF_ON__OPEN_CLOSE;
|
||||
}
|
||||
|
||||
TickerShutter.attach_ms(50, ShutterRtc50mS );
|
||||
// default the 50 percent should not have any impact without changing it. set to 60
|
||||
Settings.shutter_set50percent[i] = (Settings.shutter_set50percent[i] == 0 ? 50 : Settings.shutter_set50percent[i]);
|
||||
// use 10 sec. as default to allow everybody to play without deep initialize
|
||||
Shutter.open_time[i] = Settings.shutter_opentime[i] > 0 ? Settings.shutter_opentime[i] : 100;
|
||||
Shutter.close_time[i] = Settings.shutter_closetime[i] > 0 ? Settings.shutter_closetime[i] : 100;
|
||||
|
||||
// Update Calculation 20 because time interval is 0.05 sec
|
||||
Shutter.open_max[i] = 200 * Shutter.open_time[i];
|
||||
Shutter.close_velocity[i] = Shutter.open_max[i] / Shutter.close_time[i] / 2 ;
|
||||
|
||||
// calculate a ramp slope at the first 5 percent to compensate that shutters move with down part later than the upper part
|
||||
Settings.shuttercoeff[1][i] = Shutter.open_max[i] * (100 - Settings.shutter_set50percent[i] ) / 5000;
|
||||
Settings.shuttercoeff[0][i] = Shutter.open_max[i] - (Settings.shuttercoeff[1][i] * 100);
|
||||
Settings.shuttercoeff[2][i] = (Settings.shuttercoeff[0][i] + 5 * Settings.shuttercoeff[1][i]) / 5;
|
||||
Shutter.mask |= 3 << (Settings.shutter_startrelay[i] -1) ;
|
||||
|
||||
Shutter.real_position[i] = ShutterPercentToRealPosition(Settings.shutter_position[i], i);
|
||||
//Shutter.real_position[i] = Settings.shutter_position[i] <= 5 ? Settings.shuttercoeff[2][i] * Settings.shutter_position[i] : Settings.shuttercoeff[1][i] * Settings.shutter_position[i] + Settings.shuttercoeff[0,i];
|
||||
Shutter.start_position[i] = Shutter.real_position[i];
|
||||
dtostrfd((float)Shutter.open_time[i] / 10 , 1, shutter_open_chr);
|
||||
dtostrfd((float)Shutter.close_time[i] / 10, 1, shutter_close_chr);
|
||||
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100 Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoedffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, shuttermode %d"),
|
||||
i, Settings.shutter_startrelay[i], Shutter.real_position[i], Settings.shutter_position[i], Shutter.close_velocity[i], Shutter.open_max[i], shutter_open_chr, shutter_close_chr,
|
||||
Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i],
|
||||
Shutter.mask, Settings.shutter_invert[i], Shutter.mode);
|
||||
|
||||
} else {
|
||||
// terminate loop at first INVALID shutter.
|
||||
break;
|
||||
}
|
||||
Settings.shutter_accuracy = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ShutterUpdatePosition(void)
|
||||
{
|
||||
char scommand[CMDSZ];
|
||||
char stopic[TOPSZ];
|
||||
|
||||
for (uint32_t i = 0; i < shutters_present; i++) {
|
||||
if (Shutter.direction[i] != 0) {
|
||||
//char stemp1[20];
|
||||
Shutter.real_position[i] = Shutter.start_position[i] + ( Shutter.time[i] * (Shutter.direction[i] > 0 ? 100 : -Shutter.close_velocity[i]));
|
||||
// avoid real position leaving the boundaries.
|
||||
Shutter.real_position[i] = Shutter.real_position[i] < 0 ? 0 : (Shutter.real_position[i] > Shutter.open_max[i] ? Shutter.open_max[i] : Shutter.real_position[i]) ;
|
||||
|
||||
// Add additional runtime, if shutter did not reach the endstop for some time.
|
||||
if (Shutter.target_position[i] == Shutter.real_position[i] && Shutter.target_position[i] == 0) {
|
||||
// for every operation add 5x50ms = 250ms to stop position
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Adding additional runtime"));
|
||||
Shutter.real_position[i] += 500 * Shutter.operations[i] ;
|
||||
Shutter.operations[i] = 0;
|
||||
}
|
||||
if (Shutter.real_position[i] * Shutter.direction[i] >= Shutter.target_position[i] * Shutter.direction[i] ) {
|
||||
// calculate relay number responsible for current movement.
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Stop Condition detected: real: %d, Target: %d, direction: %d"),Shutter.real_position[i], Shutter.target_position[i],Shutter.direction[i]);
|
||||
uint8_t cur_relay = Settings.shutter_startrelay[i] + (Shutter.direction[i] == 1 ? 0 : 1) ;
|
||||
char stemp2[10];
|
||||
|
||||
Settings.shutter_position[i] = ShutterRealToPercentPosition(Shutter.real_position[i], i);
|
||||
//Settings.shutter_position[i] = Settings.shuttercoeff[2][i] * 5 > Shutter.real_position[i] ? (Shutter.real_position[i] * 10 / Settings.shuttercoeff[2][i] + 4)/10 : ((Shutter.real_position[i]-Settings.shuttercoeff[0,i]) *10 / Settings.shuttercoeff[1][i] +4) / 10;
|
||||
|
||||
if (0 < Settings.shutter_position[i] && Settings.shutter_position[i] < 100) {
|
||||
Shutter.operations[i]++;
|
||||
} else {
|
||||
Shutter.operations[i] = 0;
|
||||
}
|
||||
|
||||
dtostrfd((float)Shutter.time[i] / 20, 1, stemp2);
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Real Pos. %d, Stoppos: %ld, relay: %d, direction %d, pulsetimer: %d, rtcshutter: %s [s], operationtime %d"), i, Shutter.real_position[i], Settings.shutter_position[i], cur_relay -1, Shutter.direction[i], Settings.pulse_timer[cur_relay -1], stemp2, Shutter.operations[i]);
|
||||
Shutter.start_position[i] = Shutter.real_position[i];
|
||||
|
||||
// sending MQTT result to broker
|
||||
snprintf_P(scommand, sizeof(scommand),PSTR(D_SHUTTER "%d"), i+1);
|
||||
GetTopic_P(stopic, STAT, mqtt_topic, scommand);
|
||||
Response_P("%d", Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]);
|
||||
MqttPublish(stopic, Settings.flag.mqtt_power_retain);
|
||||
|
||||
switch (Shutter.mode) {
|
||||
case SHT_PULSE_OPEN__PULSE_CLOSE:
|
||||
// we have a momentary switch here. Needs additional pulse on same relay after the end
|
||||
if (SRC_PULSETIMER == last_source || SRC_SHUTTER == last_source || SRC_WEBGUI == last_source) {
|
||||
ExecuteCommandPower(cur_relay, 1, SRC_SHUTTER);
|
||||
} else {
|
||||
last_source = SRC_SHUTTER;
|
||||
}
|
||||
break;
|
||||
case SHT_OFF_ON__OPEN_CLOSE:
|
||||
// This is a failsafe configuration. Relay1 ON/OFF Relay2 -1/1 direction
|
||||
if ((1 << (Settings.shutter_startrelay[i]-1)) & power) {
|
||||
ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER);
|
||||
}
|
||||
break;
|
||||
case SHT_OFF_OPEN__OFF_CLOSE:
|
||||
// avoid switching OFF a relay already OFF
|
||||
if ((1 << (cur_relay-1)) & power) {
|
||||
// Relay is on and need to be switched off.
|
||||
ExecuteCommandPower(cur_relay, 0, SRC_SHUTTER);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Shutter.direction[i] = 0;
|
||||
uint8_t position = Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i];
|
||||
Response_P(PSTR("{"));
|
||||
ResponseAppend_P(JSON_SHUTTER_POS, i+1, position, 0 /*Shutter.direction[i]*/);
|
||||
ResponseJsonEnd();
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data);
|
||||
XdrvRulesProcess();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ShutterState(uint8_t device)
|
||||
{
|
||||
device--;
|
||||
device &= 3;
|
||||
return (Settings.flag3.shutter_mode && (Shutter.mask & (1 << (Settings.shutter_startrelay[device]-1))) );
|
||||
}
|
||||
|
||||
void ShutterStartInit(uint8_t index, uint8_t direction, int32_t target_pos)
|
||||
{
|
||||
Shutter.direction[index] = direction;
|
||||
Shutter.target_position[index] = target_pos;
|
||||
Shutter.start_position[index] = Shutter.real_position[index];
|
||||
Shutter.time[index] = 0;
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start shutter: %d from %d to %d in directin %d"), index, Shutter.start_position[index], Shutter.target_position[index], Shutter.direction[index]);
|
||||
}
|
||||
|
||||
void ShutterDelayForMotorStop(void)
|
||||
{
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Wait for Motorstop %d"), MOTOR_STOP_TIME);
|
||||
delay(MOTOR_STOP_TIME);
|
||||
}
|
||||
|
||||
void ShutterReportPosition(void)
|
||||
{
|
||||
uint16_t shutter_moving = 0;
|
||||
for (uint32_t i = 0; i < shutters_present; i++) {
|
||||
if (Shutter.direction[i] != 0) {
|
||||
char stemp1[20];
|
||||
char stemp2[10];
|
||||
dtostrfd((float)Shutter.time[i] / 20, 1, stemp2);
|
||||
shutter_moving = 1;
|
||||
//Settings.shutter_position[i] = Settings.shuttercoeff[2][i] * 5 > Shutter.real_position[i] ? Shutter.real_position[i] / Settings.shuttercoeff[2][i] : (Shutter.real_position[i]-Settings.shuttercoeff[0,i]) / Settings.shuttercoeff[1][i];
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d: Real Pos: %d, Target %d, source: %s, start-pos: %d %%, direction: %d, rtcshutter: %s [s]"), i,Shutter.real_position[i], Shutter.target_position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), Settings.shutter_position[i], Shutter.direction[i], stemp2 );
|
||||
}
|
||||
}
|
||||
if (rules_flag.shutter_moving > shutter_moving) {
|
||||
rules_flag.shutter_moved = 1;
|
||||
} else {
|
||||
rules_flag.shutter_moved = 0;
|
||||
}
|
||||
rules_flag.shutter_moving = shutter_moving;
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved);
|
||||
}
|
||||
|
||||
void ShutterRelayChanged(void)
|
||||
{
|
||||
|
||||
// Shutter.switched_relay = binary relay that was recently changed and cause an Action
|
||||
// powerstate_local = binary powermatrix and relays from shutter: 0..3
|
||||
// relays_changed = bool if one of the relays that belong to the shutter changed not by shutter or pulsetimer
|
||||
char stemp1[10];
|
||||
|
||||
for (uint32_t i = 0; i < shutters_present; i++) {
|
||||
power_t powerstate_local = (power >> (Settings.shutter_startrelay[i] -1)) & 3;
|
||||
//uint8 manual_relays_changed = ((Shutter.switched_relay >> (Settings.shutter_startrelay[i] -1)) & 3) && SRC_IGNORE != last_source && SRC_SHUTTER != last_source && SRC_PULSETIMER != last_source ;
|
||||
uint8 manual_relays_changed = ((Shutter.switched_relay >> (Settings.shutter_startrelay[i] -1)) & 3) && SRC_SHUTTER != last_source && SRC_PULSETIMER != last_source ;
|
||||
if (manual_relays_changed) {
|
||||
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE) {
|
||||
switch (powerstate_local) {
|
||||
case 1:
|
||||
ShutterDelayForMotorStop();
|
||||
ShutterStartInit(i, 1, Shutter.open_max[i]);
|
||||
break;
|
||||
case 3:
|
||||
ShutterDelayForMotorStop();
|
||||
ShutterStartInit(i, -1, 0);
|
||||
break;
|
||||
default:
|
||||
Shutter.direction[i] = 0;
|
||||
Shutter.target_position[i] = Shutter.real_position[i];
|
||||
}
|
||||
} else {
|
||||
if (Shutter.direction[i] != 0 && (!powerstate_local || (powerstate_local && Shutter.mode == SHT_PULSE_OPEN__PULSE_CLOSE))) {
|
||||
Shutter.target_position[i] = Shutter.real_position[i];
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Switch OFF motor. Target: %ld, source: %s, powerstate_local %ld, Shutter.switched_relay %d, manual change %d"), i, Shutter.target_position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), powerstate_local,Shutter.switched_relay,manual_relays_changed);
|
||||
} else {
|
||||
last_source = SRC_SHUTTER; // avoid switch off in the next loop
|
||||
if (powerstate_local == 2) { // testing on CLOSE relay, if ON
|
||||
// close with relay two
|
||||
ShutterDelayForMotorStop();
|
||||
ShutterStartInit(i, -1, 0);
|
||||
} else {
|
||||
// opens with relay one
|
||||
ShutterDelayForMotorStop();
|
||||
ShutterStartInit(i, 1, Shutter.open_max[i]);
|
||||
}
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Target: %ld, powerstatelocal %d"), i, Shutter.target_position[i], powerstate_local);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Shutter specific functions
|
||||
// TODO: move to shutter driver and make them accessible in a generic way
|
||||
|
||||
// device: 1..<numberOfShutters>
|
||||
// position: 0-100
|
||||
void ShutterSetPosition(uint8_t device, uint8_t position)
|
||||
{
|
||||
char svalue[32]; // Command and number parameter
|
||||
snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION "%d %d"), device, position);
|
||||
ExecuteCommand(svalue, SRC_IGNORE);
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Commands
|
||||
\*********************************************************************************************/
|
||||
|
||||
void CmndShutterOpen(void)
|
||||
{
|
||||
XdrvMailbox.payload = 100;
|
||||
last_source = SRC_WEBGUI;
|
||||
CmndShutterPosition();
|
||||
}
|
||||
|
||||
void CmndShutterClose(void)
|
||||
{
|
||||
XdrvMailbox.payload = 0;
|
||||
last_source = SRC_WEBGUI;
|
||||
CmndShutterPosition();
|
||||
}
|
||||
|
||||
void CmndShutterStop(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
||||
uint32_t index = XdrvMailbox.index -1;
|
||||
if (Shutter.direction[index] != 0) {
|
||||
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Stop moving shutter %d: direction: %d"), XdrvMailbox.index, Shutter.direction[index]);
|
||||
|
||||
int32_t temp_realpos = Shutter.start_position[index] + ( (Shutter.time[index]+10) * (Shutter.direction[index] > 0 ? 100 : -Shutter.close_velocity[index]));
|
||||
XdrvMailbox.payload = ShutterRealToPercentPosition(temp_realpos, index);
|
||||
//XdrvMailbox.payload = Settings.shuttercoeff[2][index] * 5 > temp_realpos ? temp_realpos / Settings.shuttercoeff[2][index] : (temp_realpos-Settings.shuttercoeff[0,index]) / Settings.shuttercoeff[1][index];
|
||||
last_source = SRC_WEBGUI;
|
||||
CmndShutterPosition();
|
||||
} else {
|
||||
ResponseCmndDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CmndShutterPosition(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
||||
uint32_t index = XdrvMailbox.index -1;
|
||||
//limit the payload
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Position in: payload %d, index %d, source %d"), XdrvMailbox.payload , XdrvMailbox.index, last_source );
|
||||
|
||||
int8_t target_pos_percent = XdrvMailbox.payload < 0 ? 0 : (XdrvMailbox.payload > 100 ? 100 : XdrvMailbox.payload);
|
||||
// webgui still send also on inverted shutter the native position.
|
||||
target_pos_percent = Settings.shutter_invert[index] && SRC_WEBGUI != last_source ? 100 - target_pos_percent : target_pos_percent;
|
||||
if (target_pos_percent != -99) {
|
||||
//target_pos_percent = Settings.shutter_invert[index] ? 100 - target_pos_percent : target_pos_percent;
|
||||
Shutter.target_position[index] = ShutterPercentToRealPosition(target_pos_percent, index);
|
||||
//Shutter.target_position[index] = XdrvMailbox.payload < 5 ? Settings.shuttercoeff[2][index] * XdrvMailbox.payload : Settings.shuttercoeff[1][index] * XdrvMailbox.payload + Settings.shuttercoeff[0,index];
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: lastsource %d:, realpos %d, target %d, payload %d"), last_source, Shutter.real_position[index] ,Shutter.target_position[index],target_pos_percent);
|
||||
}
|
||||
if ( (target_pos_percent >= 0) && (target_pos_percent <= 100) && abs(Shutter.target_position[index] - Shutter.real_position[index] ) / Shutter.close_velocity[index] > 2) {
|
||||
int8_t new_shutterdirection = Shutter.real_position[index] < Shutter.target_position[index] ? 1 : -1;
|
||||
if (Shutter.direction[index] == -new_shutterdirection ) {
|
||||
// direction need to be changed. on momentary switches first stop the Shutter
|
||||
if (Shutter.mode == SHT_PULSE_OPEN__PULSE_CLOSE) {
|
||||
// code for momentary shutters only small switch on to stop Shutter
|
||||
ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER);
|
||||
delay(100);
|
||||
} else {
|
||||
ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 1 : 0), 0, SRC_SHUTTER);
|
||||
ShutterDelayForMotorStop();
|
||||
}
|
||||
}
|
||||
if (Shutter.direction[index] != new_shutterdirection ) {
|
||||
ShutterStartInit(index, new_shutterdirection, Shutter.target_position[index]);
|
||||
Shutter.operations[index]++;
|
||||
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE) {
|
||||
ExecuteCommandPower(Settings.shutter_startrelay[index] , 0, SRC_SHUTTER);
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Delay5 5s, xdrv %d"), XdrvMailbox.payload);
|
||||
ShutterDelayForMotorStop();
|
||||
// Code for shutters with circuit safe configuration, switch the direction Relay
|
||||
ExecuteCommandPower(Settings.shutter_startrelay[index] +1, new_shutterdirection == 1 ? 0 : 1, SRC_SHUTTER);
|
||||
// power on
|
||||
ExecuteCommandPower(Settings.shutter_startrelay[index] , 1, SRC_SHUTTER);
|
||||
} else {
|
||||
// now start the motor for the right direction, work for momentary and normal shutters.
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start shutter in direction %d"), Shutter.direction[index]);
|
||||
ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER);
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Delay6 5s, xdrv %d"), XdrvMailbox.payload);
|
||||
}
|
||||
Shutter.switched_relay = 0;
|
||||
}
|
||||
} else {
|
||||
target_pos_percent = ShutterRealToPercentPosition(Shutter.real_position[index], index);
|
||||
}
|
||||
ResponseCmndIdxNumber(Settings.shutter_invert[index] ? 100 - target_pos_percent : target_pos_percent);
|
||||
}
|
||||
}
|
||||
|
||||
void CmndShutterOpenTime(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
||||
if (XdrvMailbox.data_len > 0) {
|
||||
Settings.shutter_opentime[XdrvMailbox.index-1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data));
|
||||
ShutterInit();
|
||||
}
|
||||
char time_chr[10];
|
||||
dtostrfd((float)(Settings.shutter_opentime[XdrvMailbox.index-1]) / 10, 1, time_chr);
|
||||
ResponseCmndIdxChar(time_chr);
|
||||
}
|
||||
}
|
||||
|
||||
void CmndShutterCloseTime(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
||||
if (XdrvMailbox.data_len > 0) {
|
||||
Settings.shutter_closetime[XdrvMailbox.index-1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data));
|
||||
ShutterInit();
|
||||
}
|
||||
char time_chr[10];
|
||||
dtostrfd((float)(Settings.shutter_closetime[XdrvMailbox.index-1]) / 10, 1, time_chr);
|
||||
ResponseCmndIdxChar(time_chr);
|
||||
}
|
||||
}
|
||||
|
||||
void CmndShutterRelay(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) {
|
||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 64)) {
|
||||
Settings.shutter_startrelay[XdrvMailbox.index-1] = XdrvMailbox.payload;
|
||||
if (XdrvMailbox.payload > 0) {
|
||||
Shutter.mask |= 3 << (XdrvMailbox.payload - 1);
|
||||
} else {
|
||||
Shutter.mask ^= 3 << (Settings.shutter_startrelay[XdrvMailbox.index-1] - 1);
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Relay %d is %d"), XdrvMailbox.index, XdrvMailbox.payload);
|
||||
Settings.shutter_startrelay[XdrvMailbox.index-1] = XdrvMailbox.payload;
|
||||
ShutterInit();
|
||||
// if payload is 0 to disable the relay there must be a reboot. Otherwhise does not work
|
||||
}
|
||||
ResponseCmndIdxNumber(Settings.shutter_startrelay[XdrvMailbox.index -1]);
|
||||
}
|
||||
}
|
||||
|
||||
void CmndShutterSetHalfway(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
||||
Settings.shutter_set50percent[XdrvMailbox.index-1] = Settings.shutter_invert[XdrvMailbox.index-1] ? 100 - XdrvMailbox.payload : XdrvMailbox.payload;
|
||||
ShutterInit();
|
||||
ResponseCmndIdxNumber(XdrvMailbox.payload); // ????
|
||||
} else {
|
||||
ResponseCmndIdxNumber(Settings.shutter_set50percent[XdrvMailbox.index-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CmndShutterSetClose(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
||||
Shutter.real_position[XdrvMailbox.index-1] = 0;
|
||||
ShutterStartInit(XdrvMailbox.index-1, 0, 0);
|
||||
Settings.shutter_position[XdrvMailbox.index-1] = 0;
|
||||
ResponseCmndChar(D_CONFIGURATION_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
void CmndShutterInvert(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
||||
Settings.shutter_invert[XdrvMailbox.index-1] = XdrvMailbox.payload;
|
||||
}
|
||||
ResponseCmndIdxNumber(Settings.shutter_invert[XdrvMailbox.index-1]);
|
||||
}
|
||||
}
|
||||
|
||||
void CmndShutterCalibration(void) // ????
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) {
|
||||
if (XdrvMailbox.data_len > 0) {
|
||||
ResponseCmndIdxChar(XdrvMailbox.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Interface
|
||||
\*********************************************************************************************/
|
||||
|
||||
bool Xdrv27(uint8_t function)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (Settings.flag3.shutter_mode) {
|
||||
switch (function) {
|
||||
case FUNC_PRE_INIT:
|
||||
ShutterInit();
|
||||
break;
|
||||
case FUNC_EVERY_50_MSECOND:
|
||||
ShutterUpdatePosition();
|
||||
break;
|
||||
case FUNC_EVERY_SECOND:
|
||||
ShutterReportPosition();
|
||||
break;
|
||||
case FUNC_COMMAND:
|
||||
result = DecodeCommand(kShutterCommands, ShutterCommand);
|
||||
break;
|
||||
case FUNC_JSON_APPEND:
|
||||
for (uint32_t i = 0; i < shutters_present; i++) {
|
||||
uint8_t position = Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i];
|
||||
ResponseAppend_P(",");
|
||||
ResponseAppend_P(JSON_SHUTTER_POS, i+1, position, Shutter.direction[i]);
|
||||
#ifdef USE_DOMOTICZ
|
||||
if ((0 == tele_period) && (0 == i)) {
|
||||
DomoticzSensor(DZ_SHUTTER, position);
|
||||
}
|
||||
#endif // USE_DOMOTICZ
|
||||
}
|
||||
break;
|
||||
case FUNC_SET_POWER:
|
||||
char stemp1[10];
|
||||
// extract the number of the relay that was switched and save for later in Update Position.
|
||||
Shutter.switched_relay = power ^ Shutter.old_power;
|
||||
Shutter.old_power = power;
|
||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Switched relay: %d by %s"), Shutter.switched_relay,GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource));
|
||||
ShutterRelayChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif //USE_SHUTTER
|
|
@ -89,7 +89,7 @@ void HlwCfInterrupt(void) // Service Power
|
|||
Hlw.cf_pulse_last_time = us;
|
||||
Hlw.energy_period_counter++;
|
||||
}
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[0] = 0;
|
||||
}
|
||||
|
||||
void HlwCf1Interrupt(void) // Service Voltage and Current
|
||||
|
@ -108,7 +108,7 @@ void HlwCf1Interrupt(void) // Service Voltage and Current
|
|||
Hlw.cf1_timer = 8; // We need up to HLW_SAMPLE_COUNT samples within 1 second (low current could take up to 0.3 second)
|
||||
}
|
||||
}
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[0] = 0;
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
@ -199,7 +199,7 @@ void HlwEvery200ms(void)
|
|||
|
||||
void HlwEverySecond(void)
|
||||
{
|
||||
if (Energy.data_valid > ENERGY_WATCHDOG) {
|
||||
if (Energy.data_valid[0] > ENERGY_WATCHDOG) {
|
||||
Hlw.cf1_voltage_pulse_length = 0;
|
||||
Hlw.cf1_current_pulse_length = 0;
|
||||
Hlw.cf_power_pulse_length = 0;
|
||||
|
|
|
@ -143,7 +143,7 @@ bool CseSerialInput(void)
|
|||
uint8_t checksum = 0;
|
||||
for (uint32_t i = 2; i < 23; i++) { checksum += serial_in_buffer[i]; }
|
||||
if (checksum == serial_in_buffer[23]) {
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[0] = 0;
|
||||
CseReceived();
|
||||
Cse.received = false;
|
||||
return true;
|
||||
|
@ -175,7 +175,7 @@ bool CseSerialInput(void)
|
|||
|
||||
void CseEverySecond(void)
|
||||
{
|
||||
if (Energy.data_valid > ENERGY_WATCHDOG) {
|
||||
if (Energy.data_valid[0] > ENERGY_WATCHDOG) {
|
||||
Cse.voltage_cycle = 0;
|
||||
Cse.current_cycle = 0;
|
||||
Cse.power_cycle = 0;
|
||||
|
|
|
@ -179,7 +179,7 @@ void PzemEvery200ms(void)
|
|||
if (data_ready) {
|
||||
float value = 0;
|
||||
if (PzemRecieve(pzem_responses[Pzem.read_state], &value)) {
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[Pzem.phase] = 0;
|
||||
switch (Pzem.read_state) {
|
||||
case 1: // Voltage as 230.2V
|
||||
Energy.voltage[Pzem.phase] = value;
|
||||
|
|
|
@ -455,6 +455,7 @@ void McpParseData(void)
|
|||
mcp_line_frequency = McpExtractInt(mcp_buffer, 22, 2);
|
||||
|
||||
if (Energy.power_on) { // Powered on
|
||||
Energy.data_valid[0] = 0;
|
||||
Energy.frequency[0] = (float)mcp_line_frequency / 1000;
|
||||
Energy.voltage[0] = (float)mcp_voltage_rms / 10;
|
||||
Energy.active_power[0] = (float)mcp_active_power / 100;
|
||||
|
@ -464,12 +465,8 @@ void McpParseData(void)
|
|||
Energy.current[0] = (float)mcp_current_rms / 10000;
|
||||
}
|
||||
} else { // Powered off
|
||||
Energy.frequency[0] = 0;
|
||||
Energy.voltage[0] = 0;
|
||||
Energy.active_power[0] = 0;
|
||||
Energy.current[0] = 0;
|
||||
Energy.data_valid[0] = ENERGY_WATCHDOG;
|
||||
}
|
||||
Energy.data_valid = 0;
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
@ -527,7 +524,7 @@ void McpSerialInput(void)
|
|||
|
||||
void McpEverySecond(void)
|
||||
{
|
||||
if (Energy.data_valid > ENERGY_WATCHDOG) {
|
||||
if (Energy.data_valid[0] > ENERGY_WATCHDOG) {
|
||||
mcp_voltage_rms = 0;
|
||||
mcp_current_rms = 0;
|
||||
mcp_active_power = 0;
|
||||
|
|
|
@ -62,7 +62,7 @@ void PzemAcEverySecond(void)
|
|||
if (error) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PAC: PzemAc %d error %d"), PZEM_AC_DEVICE_ADDRESS + PzemAc.phase, error);
|
||||
} else {
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[PzemAc.phase] = 0;
|
||||
if (10 == registers) {
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
||||
|
|
|
@ -62,7 +62,7 @@ void PzemDcEverySecond(void)
|
|||
if (error) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PDC: PzemDc %d error %d"), PZEM_DC_DEVICE_ADDRESS + PzemDc.channel, error);
|
||||
} else {
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[PzemDc.channel] = 0;
|
||||
if (8 == registers) {
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
||||
|
|
|
@ -155,6 +155,7 @@ void Ade7953GetData(void)
|
|||
Energy.voltage[0] = (float)Ade7953.voltage_rms / Settings.energy_voltage_calibration;
|
||||
|
||||
for (uint32_t channel = 0; channel < 2; channel++) {
|
||||
Energy.data_valid[channel] = 0;
|
||||
Energy.active_power[channel] = (float)Ade7953.active_power[channel] / (Settings.energy_power_calibration / 10);
|
||||
Energy.reactive_power[channel] = (float)reactive_power[channel] / (Settings.energy_power_calibration / 10);
|
||||
Energy.apparent_power[channel] = (float)apparent_power[channel] / (Settings.energy_power_calibration / 10);
|
||||
|
@ -165,13 +166,8 @@ void Ade7953GetData(void)
|
|||
}
|
||||
}
|
||||
} else { // Powered off
|
||||
Energy.voltage[0] = 0;
|
||||
for (uint32_t channel = 0; channel < 2; channel++) {
|
||||
Energy.current[channel] = 0;
|
||||
Energy.active_power[channel] = 0;
|
||||
Energy.reactive_power[channel] = 0;
|
||||
Energy.apparent_power[channel] = 0;
|
||||
}
|
||||
Energy.data_valid[0] = ENERGY_WATCHDOG;
|
||||
Energy.data_valid[1] = ENERGY_WATCHDOG;
|
||||
}
|
||||
|
||||
if (active_power_sum) {
|
||||
|
|
|
@ -85,7 +85,7 @@ void SDM120Every250ms(void)
|
|||
if (error) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: SDM120 error %d"), error);
|
||||
} else {
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[0] = 0;
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// SA FC BC Fh Fl Sh Sl Cl Ch
|
||||
|
|
|
@ -52,7 +52,7 @@ void Dds2382EverySecond(void)
|
|||
if (error) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "DDS2382 response error %d"), error);
|
||||
} else {
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[0] = 0;
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
||||
// SA FC BC EnergyTotal ExportActiv ImportActiv Volta Curre APowe RPowe PFact Frequ Crc--
|
||||
|
|
|
@ -78,7 +78,9 @@ void SDM630Every250ms(void)
|
|||
if (error) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: SDM630 error %d"), error);
|
||||
} else {
|
||||
Energy.data_valid = 0;
|
||||
Energy.data_valid[0] = 0;
|
||||
Energy.data_valid[1] = 0;
|
||||
Energy.data_valid[2] = 0;
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// SA FC BC Fh Fl Sh Sl Cl Ch
|
||||
|
|
|
@ -74,7 +74,11 @@ uint8_t OneWireReset(void)
|
|||
uint8_t retries = 125;
|
||||
|
||||
//noInterrupts();
|
||||
#ifdef DS18B20_INTERNAL_PULLUP
|
||||
pinMode(ds18x20_pin, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(ds18x20_pin, INPUT);
|
||||
#endif
|
||||
do {
|
||||
if (--retries == 0) {
|
||||
return 0;
|
||||
|
@ -84,7 +88,11 @@ uint8_t OneWireReset(void)
|
|||
pinMode(ds18x20_pin, OUTPUT);
|
||||
digitalWrite(ds18x20_pin, LOW);
|
||||
delayMicroseconds(480);
|
||||
#ifdef DS18B20_INTERNAL_PULLUP
|
||||
pinMode(ds18x20_pin, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(ds18x20_pin, INPUT);
|
||||
#endif
|
||||
delayMicroseconds(70);
|
||||
uint8_t r = !digitalRead(ds18x20_pin);
|
||||
//interrupts();
|
||||
|
@ -113,7 +121,11 @@ uint8_t OneWireReadBit(void)
|
|||
pinMode(ds18x20_pin, OUTPUT);
|
||||
digitalWrite(ds18x20_pin, LOW);
|
||||
delayMicroseconds(3);
|
||||
#ifdef DS18B20_INTERNAL_PULLUP
|
||||
pinMode(ds18x20_pin, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(ds18x20_pin, INPUT);
|
||||
#endif
|
||||
delayMicroseconds(10);
|
||||
uint8_t r = digitalRead(ds18x20_pin);
|
||||
//interrupts();
|
||||
|
@ -432,7 +444,7 @@ void Ds18x20Show(bool json)
|
|||
|
||||
if (json) {
|
||||
if (1 == ds18x20_sensors) {
|
||||
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}"), ds18x20_types, temperature);
|
||||
ResponseAppend_P(JSON_SNS_TEMP, ds18x20_types, temperature);
|
||||
} else {
|
||||
char address[17];
|
||||
for (uint32_t j = 0; j < 6; j++) {
|
||||
|
|
|
@ -1,245 +0,0 @@
|
|||
/*
|
||||
xsns_05_ds18x20_legacy.ino - DS18x20 temperature sensor support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Heiko Krupp and Theo Arends
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_DS18x20_LEGACY
|
||||
/*********************************************************************************************\
|
||||
* DS18B20 - Temperature
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define XSNS_05 5
|
||||
|
||||
#define DS18S20_CHIPID 0x10
|
||||
#define DS18B20_CHIPID 0x28
|
||||
#define MAX31850_CHIPID 0x3B
|
||||
|
||||
#define W1_SKIP_ROM 0xCC
|
||||
#define W1_CONVERT_TEMP 0x44
|
||||
#define W1_READ_SCRATCHPAD 0xBE
|
||||
|
||||
#define DS18X20_MAX_SENSORS 8
|
||||
|
||||
#include <OneWire.h>
|
||||
|
||||
OneWire *ds = nullptr;
|
||||
|
||||
uint8_t ds18x20_address[DS18X20_MAX_SENSORS][8];
|
||||
uint8_t ds18x20_index[DS18X20_MAX_SENSORS];
|
||||
uint8_t ds18x20_sensors = 0;
|
||||
char ds18x20_types[9];
|
||||
|
||||
void Ds18x20Init(void)
|
||||
{
|
||||
ds = new OneWire(pin[GPIO_DSB]);
|
||||
}
|
||||
|
||||
void Ds18x20Search(void)
|
||||
{
|
||||
uint8_t num_sensors=0;
|
||||
uint8_t sensor = 0;
|
||||
|
||||
ds->reset_search();
|
||||
for (num_sensors = 0; num_sensors < DS18X20_MAX_SENSORS; num_sensors) {
|
||||
if (!ds->search(ds18x20_address[num_sensors])) {
|
||||
ds->reset_search();
|
||||
break;
|
||||
}
|
||||
// If CRC Ok and Type DS18S20, DS18B20 or MAX31850
|
||||
if ((OneWire::crc8(ds18x20_address[num_sensors], 7) == ds18x20_address[num_sensors][7]) &&
|
||||
((ds18x20_address[num_sensors][0]==DS18S20_CHIPID) || (ds18x20_address[num_sensors][0]==DS18B20_CHIPID) || (ds18x20_address[num_sensors][0]==MAX31850_CHIPID))) {
|
||||
num_sensors++;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < num_sensors; i++) {
|
||||
ds18x20_index[i] = i;
|
||||
}
|
||||
for (uint32_t i = 0; i < num_sensors; i++) {
|
||||
for (uint32_t j = i + 1; j < num_sensors; j++) {
|
||||
if (uint32_t(ds18x20_address[ds18x20_index[i]]) > uint32_t(ds18x20_address[ds18x20_index[j]])) {
|
||||
std::swap(ds18x20_index[i], ds18x20_index[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
ds18x20_sensors = num_sensors;
|
||||
}
|
||||
|
||||
uint8_t Ds18x20Sensors(void)
|
||||
{
|
||||
return ds18x20_sensors;
|
||||
}
|
||||
|
||||
String Ds18x20Addresses(uint8_t sensor)
|
||||
{
|
||||
char address[20];
|
||||
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
sprintf(address+2*i, "%02X", ds18x20_address[ds18x20_index[sensor]][i]);
|
||||
}
|
||||
return String(address);
|
||||
}
|
||||
|
||||
void Ds18x20Convert(void)
|
||||
{
|
||||
ds->reset();
|
||||
ds->write(W1_SKIP_ROM); // Address all Sensors on Bus
|
||||
ds->write(W1_CONVERT_TEMP); // start conversion, no parasite power on at the end
|
||||
// delay(750); // 750ms should be enough for 12bit conv
|
||||
}
|
||||
|
||||
bool Ds18x20Read(uint8_t sensor, float &t)
|
||||
{
|
||||
uint8_t data[12];
|
||||
int8_t sign = 1;
|
||||
uint16_t temp12 = 0;
|
||||
int16_t temp14 = 0;
|
||||
float temp9 = 0.0;
|
||||
uint8_t present = 0;
|
||||
|
||||
t = NAN;
|
||||
|
||||
ds->reset();
|
||||
ds->select(ds18x20_address[ds18x20_index[sensor]]);
|
||||
ds->write(W1_READ_SCRATCHPAD); // Read Scratchpad
|
||||
|
||||
for (uint32_t i = 0; i < 9; i++) {
|
||||
data[i] = ds->read();
|
||||
}
|
||||
if (OneWire::crc8(data, 8) == data[8]) {
|
||||
switch(ds18x20_address[ds18x20_index[sensor]][0]) {
|
||||
case DS18S20_CHIPID:
|
||||
if (data[1] > 0x80) {
|
||||
data[0] = (~data[0]) +1;
|
||||
sign = -1; // App-Note fix possible sign error
|
||||
}
|
||||
temp9 = (float)(data[0] >> 1) * sign;
|
||||
t = ConvertTemp((temp9 - 0.25) + ((16.0 - data[6]) / 16.0));
|
||||
break;
|
||||
case DS18B20_CHIPID:
|
||||
temp12 = (data[1] << 8) + data[0];
|
||||
if (temp12 > 2047) {
|
||||
temp12 = (~temp12) +1;
|
||||
sign = -1;
|
||||
}
|
||||
t = ConvertTemp(sign * temp12 * 0.0625); // Divide by 16
|
||||
break;
|
||||
case MAX31850_CHIPID:
|
||||
temp14 = (data[1] << 8) + (data[0] & 0xFC);
|
||||
t = ConvertTemp(temp14 * 0.0625); // Divide by 16
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (!isnan(t));
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
void Ds18x20Type(uint8_t sensor)
|
||||
{
|
||||
strcpy_P(ds18x20_types, PSTR("DS18x20"));
|
||||
switch(ds18x20_address[ds18x20_index[sensor]][0]) {
|
||||
case DS18S20_CHIPID:
|
||||
strcpy_P(ds18x20_types, PSTR("DS18S20"));
|
||||
break;
|
||||
case DS18B20_CHIPID:
|
||||
strcpy_P(ds18x20_types, PSTR("DS18B20"));
|
||||
break;
|
||||
case MAX31850_CHIPID:
|
||||
strcpy_P(ds18x20_types, PSTR("MAX31850"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Ds18x20Show(bool json)
|
||||
{
|
||||
char stemp[10];
|
||||
float t;
|
||||
|
||||
uint8_t dsxflg = 0;
|
||||
for (uint32_t i = 0; i < Ds18x20Sensors(); i++) {
|
||||
if (Ds18x20Read(i, t)) { // Check if read failed
|
||||
Ds18x20Type(i);
|
||||
char temperature[33];
|
||||
dtostrfd(t, Settings.flag2.temperature_resolution, temperature);
|
||||
|
||||
if (json) {
|
||||
if (!dsxflg) {
|
||||
ResponseAppend_P(PSTR(",\"DS18x20\":{"));
|
||||
stemp[0] = '\0';
|
||||
}
|
||||
dsxflg++;
|
||||
ResponseAppend_P(PSTR("%s\"DS%d\":{\"" D_JSON_TYPE "\":\"%s\",\"" D_JSON_ADDRESS "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%s}"),
|
||||
stemp, i +1, ds18x20_types, Ds18x20Addresses(i).c_str(), temperature);
|
||||
strlcpy(stemp, ",", sizeof(stemp));
|
||||
#ifdef USE_DOMOTICZ
|
||||
if ((0 == tele_period) && (1 == dsxflg)) {
|
||||
DomoticzSensor(DZ_TEMP, temperature);
|
||||
}
|
||||
#endif // USE_DOMOTICZ
|
||||
#ifdef USE_KNX
|
||||
if ((0 == tele_period) && (1 == dsxflg)) {
|
||||
KnxSensor(KNX_TEMPERATURE, t);
|
||||
}
|
||||
#endif // USE_KNX
|
||||
#ifdef USE_WEBSERVER
|
||||
} else {
|
||||
snprintf_P(stemp, sizeof(stemp), PSTR("%s%c%d"), ds18x20_types, IndexSeparator(), i +1);
|
||||
WSContentSend_PD(HTTP_SNS_TEMP, stemp, temperature, TempUnit());
|
||||
#endif // USE_WEBSERVER
|
||||
}
|
||||
}
|
||||
}
|
||||
if (json) {
|
||||
if (dsxflg) {
|
||||
ResponseJsonEnd();
|
||||
}
|
||||
}
|
||||
Ds18x20Search(); // Check for changes in sensors number
|
||||
Ds18x20Convert(); // Start Conversion, takes up to one second
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Interface
|
||||
\*********************************************************************************************/
|
||||
|
||||
bool Xsns05(uint8_t function)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (pin[GPIO_DSB] < 99) {
|
||||
switch (function) {
|
||||
case FUNC_INIT:
|
||||
Ds18x20Init();
|
||||
break;
|
||||
case FUNC_PREP_BEFORE_TELEPERIOD:
|
||||
Ds18x20Search(); // Check for changes in sensors number
|
||||
Ds18x20Convert(); // Start Conversion, takes up to one second
|
||||
break;
|
||||
case FUNC_JSON_APPEND:
|
||||
Ds18x20Show(1);
|
||||
break;
|
||||
#ifdef USE_WEBSERVER
|
||||
case FUNC_WEB_SENSOR:
|
||||
Ds18x20Show(0);
|
||||
break;
|
||||
#endif // USE_WEBSERVER
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // USE_DS18x20_LEGACY
|
|
@ -124,14 +124,11 @@ void Sgp30Show(bool json)
|
|||
if (sgp30_ready) {
|
||||
char abs_hum[33];
|
||||
|
||||
if (global_update && global_humidity>0 && global_temperature!=9999) {
|
||||
// has humidity + temperature
|
||||
dtostrfd(sgp30_abshum,4,abs_hum);
|
||||
}
|
||||
|
||||
if (json) {
|
||||
ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d"), sgp.eCO2, sgp.TVOC);
|
||||
if (global_update) {
|
||||
if (global_update && global_humidity>0 && global_temperature!=9999) {
|
||||
// has humidity + temperature
|
||||
dtostrfd(sgp30_abshum,4,abs_hum);
|
||||
ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"),abs_hum);
|
||||
}
|
||||
ResponseJsonEnd();
|
||||
|
|
|
@ -32,48 +32,53 @@
|
|||
* - Execute command Sensor34 2 and follow messages shown
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define XSNS_34 34
|
||||
#define XSNS_34 34
|
||||
|
||||
#ifndef HX_MAX_WEIGHT
|
||||
#define HX_MAX_WEIGHT 20000 // Default max weight in gram
|
||||
#define HX_MAX_WEIGHT 20000 // Default max weight in gram
|
||||
#endif
|
||||
#ifndef HX_REFERENCE
|
||||
#define HX_REFERENCE 250 // Default reference weight for calibration in gram
|
||||
#define HX_REFERENCE 250 // Default reference weight for calibration in gram
|
||||
#endif
|
||||
#ifndef HX_SCALE
|
||||
#define HX_SCALE 120 // Default result of measured weight / reference weight when scale is 1
|
||||
#define HX_SCALE 120 // Default result of measured weight / reference weight when scale is 1
|
||||
#endif
|
||||
|
||||
#define HX_TIMEOUT 120 // A reading at default 10Hz (pin RATE to Gnd on HX711) can take up to 100 milliseconds
|
||||
#define HX_SAMPLES 10 // Number of samples for average calculation
|
||||
#define HX_CAL_TIMEOUT 15 // Calibration step window in number of seconds
|
||||
#define HX_TIMEOUT 120 // A reading at default 10Hz (pin RATE to Gnd on HX711) can take up to 100 milliseconds
|
||||
#define HX_SAMPLES 10 // Number of samples for average calculation
|
||||
#define HX_CAL_TIMEOUT 15 // Calibration step window in number of seconds
|
||||
|
||||
#define HX_GAIN_128 1 // Channel A, gain factor 128
|
||||
#define HX_GAIN_32 2 // Channel B, gain factor 32
|
||||
#define HX_GAIN_64 3 // Channel A, gain factor 64
|
||||
#define HX_GAIN_128 1 // Channel A, gain factor 128
|
||||
#define HX_GAIN_32 2 // Channel B, gain factor 32
|
||||
#define HX_GAIN_64 3 // Channel A, gain factor 64
|
||||
|
||||
#define D_JSON_WEIGHT_REF "WeightRef"
|
||||
#define D_JSON_WEIGHT_CAL "WeightCal"
|
||||
#define D_JSON_WEIGHT_MAX "WeightMax"
|
||||
#define D_JSON_WEIGHT_ITEM "WeightItem"
|
||||
#define D_JSON_WEIGHT_REF "WeightRef"
|
||||
#define D_JSON_WEIGHT_CAL "WeightCal"
|
||||
#define D_JSON_WEIGHT_MAX "WeightMax"
|
||||
#define D_JSON_WEIGHT_ITEM "WeightItem"
|
||||
#define D_JSON_WEIGHT_CHANGE "WeightChange"
|
||||
|
||||
enum HxCalibrationSteps { HX_CAL_END, HX_CAL_LIMBO, HX_CAL_FINISH, HX_CAL_FAIL, HX_CAL_DONE, HX_CAL_FIRST, HX_CAL_RESET, HX_CAL_START };
|
||||
|
||||
const char kHxCalibrationStates[] PROGMEM = D_HX_CAL_FAIL "|" D_HX_CAL_DONE "|" D_HX_CAL_REFERENCE "|" D_HX_CAL_REMOVE;
|
||||
|
||||
long hx_weight = 0;
|
||||
long hx_last_weight = 0;
|
||||
long hx_sum_weight = 0;
|
||||
long hx_offset = 0;
|
||||
long hx_scale = 1;
|
||||
uint8_t hx_type = 1;
|
||||
uint8_t hx_sample_count = 0;
|
||||
uint8_t hx_calibrate_step = HX_CAL_END;
|
||||
uint8_t hx_calibrate_timer = 0;
|
||||
uint8_t hx_calibrate_msg = 0;
|
||||
uint8_t hx_pin_sck;
|
||||
uint8_t hx_pin_dout;
|
||||
bool hx_tare_flg = false;
|
||||
struct HX {
|
||||
long weight = 0;
|
||||
long last_weight = 0;
|
||||
long sum_weight = 0;
|
||||
long offset = 0;
|
||||
long scale = 1;
|
||||
long weight_diff = 0;
|
||||
uint8_t type = 1;
|
||||
uint8_t sample_count = 0;
|
||||
uint8_t calibrate_step = HX_CAL_END;
|
||||
uint8_t calibrate_timer = 0;
|
||||
uint8_t calibrate_msg = 0;
|
||||
uint8_t pin_sck;
|
||||
uint8_t pin_dout;
|
||||
bool tare_flg = false;
|
||||
bool weight_changed = false;
|
||||
} Hx;
|
||||
|
||||
/*********************************************************************************************/
|
||||
|
||||
|
@ -81,8 +86,8 @@ bool HxIsReady(uint16_t timeout)
|
|||
{
|
||||
// A reading can take up to 100 mS or 600mS after power on
|
||||
uint32_t start = millis();
|
||||
while ((digitalRead(hx_pin_dout) == HIGH) && (millis() - start < timeout)) { yield(); }
|
||||
return (digitalRead(hx_pin_dout) == LOW);
|
||||
while ((digitalRead(Hx.pin_dout) == HIGH) && (millis() - start < timeout)) { yield(); }
|
||||
return (digitalRead(Hx.pin_dout) == LOW);
|
||||
}
|
||||
|
||||
long HxRead()
|
||||
|
@ -93,14 +98,14 @@ long HxRead()
|
|||
uint8_t filler = 0x00;
|
||||
|
||||
// pulse the clock pin 24 times to read the data
|
||||
data[2] = shiftIn(hx_pin_dout, hx_pin_sck, MSBFIRST);
|
||||
data[1] = shiftIn(hx_pin_dout, hx_pin_sck, MSBFIRST);
|
||||
data[0] = shiftIn(hx_pin_dout, hx_pin_sck, MSBFIRST);
|
||||
data[2] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST);
|
||||
data[1] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST);
|
||||
data[0] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST);
|
||||
|
||||
// set the channel and the gain factor for the next reading using the clock pin
|
||||
for (unsigned int i = 0; i < HX_GAIN_128; i++) {
|
||||
digitalWrite(hx_pin_sck, HIGH);
|
||||
digitalWrite(hx_pin_sck, LOW);
|
||||
digitalWrite(Hx.pin_sck, HIGH);
|
||||
digitalWrite(Hx.pin_sck, LOW);
|
||||
}
|
||||
|
||||
// Replicate the most significant bit to pad out a 32-bit signed integer
|
||||
|
@ -119,10 +124,10 @@ long HxRead()
|
|||
|
||||
void HxResetPart(void)
|
||||
{
|
||||
hx_tare_flg = true;
|
||||
hx_sum_weight = 0;
|
||||
hx_sample_count = 0;
|
||||
hx_last_weight = 0;
|
||||
Hx.tare_flg = true;
|
||||
Hx.sum_weight = 0;
|
||||
Hx.sample_count = 0;
|
||||
Hx.last_weight = 0;
|
||||
}
|
||||
|
||||
void HxReset(void)
|
||||
|
@ -135,8 +140,8 @@ void HxCalibrationStateTextJson(uint8_t msg_id)
|
|||
{
|
||||
char cal_text[30];
|
||||
|
||||
hx_calibrate_msg = msg_id;
|
||||
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, GetTextIndexed(cal_text, sizeof(cal_text), hx_calibrate_msg, kHxCalibrationStates));
|
||||
Hx.calibrate_msg = msg_id;
|
||||
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, GetTextIndexed(cal_text, sizeof(cal_text), Hx.calibrate_msg, kHxCalibrationStates));
|
||||
|
||||
if (msg_id < 3) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("Sensor34")); }
|
||||
}
|
||||
|
@ -156,6 +161,8 @@ void HxCalibrationStateTextJson(uint8_t msg_id)
|
|||
* Sensor34 6 - Show item weigth in decigram
|
||||
* Sensor34 6 <weight in decigram> - Set item weight
|
||||
* Sensor34 7 - Save current weight to be used as start weight on restart
|
||||
* Sensor34 8 0 - Disable JSON weight change message
|
||||
* Sensor34 8 1 - Enable JSON weight change message
|
||||
\*********************************************************************************************/
|
||||
|
||||
bool HxCommand(void)
|
||||
|
@ -177,10 +184,10 @@ bool HxCommand(void)
|
|||
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
||||
Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
|
||||
}
|
||||
hx_scale = 1;
|
||||
Hx.scale = 1;
|
||||
HxReset();
|
||||
hx_calibrate_step = HX_CAL_START;
|
||||
hx_calibrate_timer = 1;
|
||||
Hx.calibrate_step = HX_CAL_START;
|
||||
Hx.calibrate_timer = 1;
|
||||
HxCalibrationStateTextJson(3);
|
||||
break;
|
||||
case 3: // WeightRef to user reference
|
||||
|
@ -192,7 +199,7 @@ bool HxCommand(void)
|
|||
case 4: // WeightCal to user calculated value
|
||||
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
||||
Settings.weight_calibration = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
|
||||
hx_scale = Settings.weight_calibration;
|
||||
Hx.scale = Settings.weight_calibration;
|
||||
}
|
||||
show_parms = true;
|
||||
break;
|
||||
|
@ -209,18 +216,24 @@ bool HxCommand(void)
|
|||
show_parms = true;
|
||||
break;
|
||||
case 7: // WeightSave
|
||||
Settings.energy_frequency_calibration = hx_weight;
|
||||
Settings.energy_frequency_calibration = Hx.weight;
|
||||
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, D_JSON_DONE);
|
||||
break;
|
||||
case 8: // Json on weight change
|
||||
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
||||
Settings.SensorBits1.hx711_json_weight_change = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10) & 1;
|
||||
}
|
||||
show_parms = true;
|
||||
break;
|
||||
default:
|
||||
serviced = false;
|
||||
show_parms = true;
|
||||
}
|
||||
|
||||
if (show_parms) {
|
||||
char item[33];
|
||||
dtostrfd((float)Settings.weight_item / 10, 1, item);
|
||||
Response_P(PSTR("{\"Sensor34\":{\"" D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_MAX "\":%d,\"" D_JSON_WEIGHT_ITEM "\":%s}}"),
|
||||
Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000, item);
|
||||
Response_P(PSTR("{\"Sensor34\":{\"" D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_MAX "\":%d,\"" D_JSON_WEIGHT_ITEM "\":%s,\"" D_JSON_WEIGHT_CHANGE "\":\"%s\"}}"),
|
||||
Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000, item, GetStateText(Settings.SensorBits1.hx711_json_weight_change));
|
||||
}
|
||||
|
||||
return serviced;
|
||||
|
@ -230,123 +243,138 @@ bool HxCommand(void)
|
|||
|
||||
long HxWeight()
|
||||
{
|
||||
return (hx_calibrate_step < HX_CAL_FAIL) ? hx_weight : 0;
|
||||
return (Hx.calibrate_step < HX_CAL_FAIL) ? Hx.weight : 0;
|
||||
}
|
||||
|
||||
void HxInit(void)
|
||||
{
|
||||
hx_type = 0;
|
||||
Hx.type = 0;
|
||||
if ((pin[GPIO_HX711_DAT] < 99) && (pin[GPIO_HX711_SCK] < 99)) {
|
||||
hx_pin_sck = pin[GPIO_HX711_SCK];
|
||||
hx_pin_dout = pin[GPIO_HX711_DAT];
|
||||
Hx.pin_sck = pin[GPIO_HX711_SCK];
|
||||
Hx.pin_dout = pin[GPIO_HX711_DAT];
|
||||
|
||||
pinMode(hx_pin_sck, OUTPUT);
|
||||
pinMode(hx_pin_dout, INPUT);
|
||||
pinMode(Hx.pin_sck, OUTPUT);
|
||||
pinMode(Hx.pin_dout, INPUT);
|
||||
|
||||
digitalWrite(hx_pin_sck, LOW);
|
||||
digitalWrite(Hx.pin_sck, LOW);
|
||||
|
||||
if (HxIsReady(8 * HX_TIMEOUT)) { // Can take 600 milliseconds after power on
|
||||
if (!Settings.weight_max) { Settings.weight_max = HX_MAX_WEIGHT / 1000; }
|
||||
if (!Settings.weight_calibration) { Settings.weight_calibration = HX_SCALE; }
|
||||
if (!Settings.weight_reference) { Settings.weight_reference = HX_REFERENCE; }
|
||||
hx_scale = Settings.weight_calibration;
|
||||
Hx.scale = Settings.weight_calibration;
|
||||
HxRead();
|
||||
HxResetPart();
|
||||
hx_type = 1;
|
||||
Hx.type = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HxEvery100mSecond(void)
|
||||
{
|
||||
hx_sum_weight += HxRead();
|
||||
Hx.sum_weight += HxRead();
|
||||
|
||||
hx_sample_count++;
|
||||
if (HX_SAMPLES == hx_sample_count) {
|
||||
long average = hx_sum_weight / hx_sample_count; // grams
|
||||
long value = average - hx_offset; // grams
|
||||
hx_weight = value / hx_scale; // grams
|
||||
if (hx_weight < 0) {
|
||||
Hx.sample_count++;
|
||||
if (HX_SAMPLES == Hx.sample_count) {
|
||||
long average = Hx.sum_weight / Hx.sample_count; // grams
|
||||
long value = average - Hx.offset; // grams
|
||||
Hx.weight = value / Hx.scale; // grams
|
||||
if (Hx.weight < 0) {
|
||||
if (Settings.energy_frequency_calibration) {
|
||||
long difference = Settings.energy_frequency_calibration + hx_weight;
|
||||
hx_last_weight = difference;
|
||||
long difference = Settings.energy_frequency_calibration + Hx.weight;
|
||||
Hx.last_weight = difference;
|
||||
if (difference < 0) { HxReset(); } // Cancel last weight as there seems to be no more weight on the scale
|
||||
}
|
||||
hx_weight = 0;
|
||||
Hx.weight = 0;
|
||||
} else {
|
||||
hx_last_weight = Settings.energy_frequency_calibration;
|
||||
Hx.last_weight = Settings.energy_frequency_calibration;
|
||||
}
|
||||
|
||||
if (hx_tare_flg) {
|
||||
hx_tare_flg = false;
|
||||
hx_offset = average; // grams
|
||||
if (Hx.tare_flg) {
|
||||
Hx.tare_flg = false;
|
||||
Hx.offset = average; // grams
|
||||
}
|
||||
|
||||
if (hx_calibrate_step) {
|
||||
hx_calibrate_timer--;
|
||||
if (Hx.calibrate_step) {
|
||||
Hx.calibrate_timer--;
|
||||
|
||||
if (HX_CAL_START == hx_calibrate_step) { // Skip reset just initiated
|
||||
hx_calibrate_step--;
|
||||
hx_calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
|
||||
if (HX_CAL_START == Hx.calibrate_step) { // Skip reset just initiated
|
||||
Hx.calibrate_step--;
|
||||
Hx.calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
|
||||
}
|
||||
else if (HX_CAL_RESET == hx_calibrate_step) { // Wait for stable reset
|
||||
if (hx_calibrate_timer) {
|
||||
if (hx_weight < (long)Settings.weight_reference) {
|
||||
hx_calibrate_step--;
|
||||
hx_calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
|
||||
else if (HX_CAL_RESET == Hx.calibrate_step) { // Wait for stable reset
|
||||
if (Hx.calibrate_timer) {
|
||||
if (Hx.weight < (long)Settings.weight_reference) {
|
||||
Hx.calibrate_step--;
|
||||
Hx.calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
|
||||
HxCalibrationStateTextJson(2);
|
||||
}
|
||||
} else {
|
||||
hx_calibrate_step = HX_CAL_FAIL;
|
||||
Hx.calibrate_step = HX_CAL_FAIL;
|
||||
}
|
||||
}
|
||||
else if (HX_CAL_FIRST == hx_calibrate_step) { // Wait for first reference weight
|
||||
if (hx_calibrate_timer) {
|
||||
if (hx_weight > (long)Settings.weight_reference) {
|
||||
hx_calibrate_step--;
|
||||
else if (HX_CAL_FIRST == Hx.calibrate_step) { // Wait for first reference weight
|
||||
if (Hx.calibrate_timer) {
|
||||
if (Hx.weight > (long)Settings.weight_reference) {
|
||||
Hx.calibrate_step--;
|
||||
}
|
||||
} else {
|
||||
hx_calibrate_step = HX_CAL_FAIL;
|
||||
Hx.calibrate_step = HX_CAL_FAIL;
|
||||
}
|
||||
}
|
||||
else if (HX_CAL_DONE == hx_calibrate_step) { // Second stable reference weight
|
||||
if (hx_weight > (long)Settings.weight_reference) {
|
||||
hx_calibrate_step = HX_CAL_FINISH; // Calibration done
|
||||
Settings.weight_calibration = hx_weight / Settings.weight_reference;
|
||||
hx_weight = 0; // Reset calibration value
|
||||
else if (HX_CAL_DONE == Hx.calibrate_step) { // Second stable reference weight
|
||||
if (Hx.weight > (long)Settings.weight_reference) {
|
||||
Hx.calibrate_step = HX_CAL_FINISH; // Calibration done
|
||||
Settings.weight_calibration = Hx.weight / Settings.weight_reference;
|
||||
Hx.weight = 0; // Reset calibration value
|
||||
HxCalibrationStateTextJson(1);
|
||||
} else {
|
||||
hx_calibrate_step = HX_CAL_FAIL;
|
||||
Hx.calibrate_step = HX_CAL_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if (HX_CAL_FAIL == hx_calibrate_step) { // Calibration failed
|
||||
hx_calibrate_step--;
|
||||
hx_tare_flg = true; // Perform a reset using old scale
|
||||
if (HX_CAL_FAIL == Hx.calibrate_step) { // Calibration failed
|
||||
Hx.calibrate_step--;
|
||||
Hx.tare_flg = true; // Perform a reset using old scale
|
||||
HxCalibrationStateTextJson(0);
|
||||
}
|
||||
if (HX_CAL_FINISH == hx_calibrate_step) { // Calibration finished
|
||||
hx_calibrate_step--;
|
||||
hx_calibrate_timer = 3 * (10 / HX_SAMPLES);
|
||||
hx_scale = Settings.weight_calibration;
|
||||
if (HX_CAL_FINISH == Hx.calibrate_step) { // Calibration finished
|
||||
Hx.calibrate_step--;
|
||||
Hx.calibrate_timer = 3 * (10 / HX_SAMPLES);
|
||||
Hx.scale = Settings.weight_calibration;
|
||||
}
|
||||
|
||||
if (!hx_calibrate_timer) {
|
||||
hx_calibrate_step = HX_CAL_END; // End of calibration
|
||||
if (!Hx.calibrate_timer) {
|
||||
Hx.calibrate_step = HX_CAL_END; // End of calibration
|
||||
}
|
||||
} else {
|
||||
hx_weight += hx_last_weight; // grams
|
||||
Hx.weight += Hx.last_weight; // grams
|
||||
|
||||
if (Settings.SensorBits1.hx711_json_weight_change) {
|
||||
if (abs(Hx.weight - Hx.weight_diff) > 4) { // Use 4 gram threshold to decrease "ghost" weights
|
||||
Hx.weight_diff = Hx.weight;
|
||||
Hx.weight_changed = true;
|
||||
}
|
||||
else if (Hx.weight_changed && (Hx.weight == Hx.weight_diff)) {
|
||||
mqtt_data[0] = '\0';
|
||||
ResponseAppendTime();
|
||||
HxShow(true);
|
||||
ResponseJsonEnd();
|
||||
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
|
||||
Hx.weight_changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hx_sum_weight = 0;
|
||||
hx_sample_count = 0;
|
||||
Hx.sum_weight = 0;
|
||||
Hx.sample_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void HxSaveBeforeRestart()
|
||||
{
|
||||
Settings.energy_frequency_calibration = hx_weight;
|
||||
hx_sample_count = HX_SAMPLES +1; // Stop updating hx_weight
|
||||
Settings.energy_frequency_calibration = Hx.weight;
|
||||
Hx.sample_count = HX_SAMPLES +1; // Stop updating Hx.weight
|
||||
}
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
|
@ -364,14 +392,14 @@ void HxShow(bool json)
|
|||
|
||||
uint16_t count = 0;
|
||||
float weight = 0;
|
||||
if (hx_calibrate_step < HX_CAL_FAIL) {
|
||||
if (hx_weight && Settings.weight_item) {
|
||||
count = (hx_weight * 10) / Settings.weight_item;
|
||||
if (Hx.calibrate_step < HX_CAL_FAIL) {
|
||||
if (Hx.weight && Settings.weight_item) {
|
||||
count = (Hx.weight * 10) / Settings.weight_item;
|
||||
if (count > 1) {
|
||||
snprintf_P(scount, sizeof(scount), PSTR(",\"" D_JSON_COUNT "\":%d"), count);
|
||||
}
|
||||
}
|
||||
weight = (float)hx_weight / 1000; // kilograms
|
||||
weight = (float)Hx.weight / 1000; // kilograms
|
||||
}
|
||||
char weight_chr[33];
|
||||
dtostrfd(weight, Settings.flag2.weight_resolution, weight_chr);
|
||||
|
@ -384,9 +412,9 @@ void HxShow(bool json)
|
|||
if (count > 1) {
|
||||
WSContentSend_PD(HTTP_HX711_COUNT, count);
|
||||
}
|
||||
if (hx_calibrate_step) {
|
||||
if (Hx.calibrate_step) {
|
||||
char cal_text[30];
|
||||
WSContentSend_PD(HTTP_HX711_CAL, GetTextIndexed(cal_text, sizeof(cal_text), hx_calibrate_msg, kHxCalibrationStates));
|
||||
WSContentSend_PD(HTTP_HX711_CAL, GetTextIndexed(cal_text, sizeof(cal_text), Hx.calibrate_msg, kHxCalibrationStates));
|
||||
}
|
||||
#endif // USE_WEBSERVER
|
||||
}
|
||||
|
@ -497,11 +525,8 @@ bool Xsns34(uint8_t function)
|
|||
{
|
||||
bool result = false;
|
||||
|
||||
if (hx_type) {
|
||||
if (Hx.type) {
|
||||
switch (function) {
|
||||
case FUNC_INIT:
|
||||
HxInit();
|
||||
break;
|
||||
case FUNC_EVERY_100_MSECOND:
|
||||
HxEvery100mSecond();
|
||||
break;
|
||||
|
@ -532,6 +557,9 @@ bool Xsns34(uint8_t function)
|
|||
break;
|
||||
#endif // USE_HX711_GUI
|
||||
#endif // USE_WEBSERVER
|
||||
case FUNC_INIT:
|
||||
HxInit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -20,7 +20,11 @@
|
|||
Version Date Action Description
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
1.0.0.1 20190917 changed - rework of the inner loop to enable delays in the middle of I2C-reads
|
||||
changed - double send address change only for fw>0x25
|
||||
changed - use DEBUG_SENSOR_LOG, change ILLUMINANCE to DARKNESS
|
||||
changed - do not publish missing temperature reads, show fw-version as hex
|
||||
added - now really support the (slower) CHIRP!-Sensor
|
||||
---
|
||||
1.0.0.0 20190608 started - further development by Christian Baars - https://github.com/Staars/Sonoff-Tasmota
|
||||
forked - from arendst/tasmota - https://github.com/arendst/Sonoff-Tasmota
|
||||
|
@ -32,15 +36,16 @@
|
|||
#ifdef USE_CHIRP
|
||||
|
||||
/*********************************************************************************************\
|
||||
* CHIRP - Soil moisture sensor
|
||||
* CHIRP - Chirp!-sensor and I2C-soil-moisture-sensor
|
||||
* !! The I2C-soil-moisture-sensor is the preferred one !!
|
||||
*
|
||||
* I2C Address: 0x20 - standard address, is changeable
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define XSNS_48 48
|
||||
#define CHIRP_MAX_SENSOR_COUNT 3 // 127 is expectectd to be the max number
|
||||
#define XSNS_48 48
|
||||
#define CHIRP_MAX_SENSOR_COUNT 3 // 127 is expectectd to be the max number
|
||||
|
||||
#define CHIRP_ADDR_STANDARD 0x20 // standard address
|
||||
#define CHIRP_ADDR_STANDARD 0x20 // standard address
|
||||
|
||||
/*********************************************************************************************\
|
||||
* constants
|
||||
|
@ -71,24 +76,32 @@ enum CHIRP_Commands { // commands useable in con
|
|||
* command defines
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define CHIRP_GET_CAPACITANCE 0x00 // 16 bit, read
|
||||
#define CHIRP_SET_ADDRESS 0x01 // 8 bit, write
|
||||
#define CHIRP_GET_ADDRESS 0x02 // 8 bit, read
|
||||
#define CHIRP_MEASURE_LIGHT 0x03 // no value, write, -> initiate measurement, then wait at least 3 seconds
|
||||
#define CHIRP_GET_LIGHT 0x04 // 16 bit, read, -> higher value means darker environment, noisy data, not calibrated
|
||||
#define CHIRP_GET_TEMPERATURE 0x05 // 16 bit, read
|
||||
#define CHIRP_RESET 0x06 // no value, write
|
||||
#define CHIRP_GET_VERSION 0x07 // 8 bit, read, -> 22 means 2.2
|
||||
#define CHIRP_SLEEP 0x08 // no value, write
|
||||
#define CHIRP_GET_BUSY 0x09 // 8 bit, read, -> 1 = busy, 0 = otherwise
|
||||
#define CHIRP_GET_CAPACITANCE 0x00 // 16 bit, read
|
||||
#define CHIRP_SET_ADDRESS 0x01 // 8 bit, write
|
||||
#define CHIRP_GET_ADDRESS 0x02 // 8 bit, read
|
||||
#define CHIRP_MEASURE_LIGHT 0x03 // no value, write, -> initiate measurement, then wait at least 3 seconds
|
||||
#define CHIRP_GET_LIGHT 0x04 // 16 bit, read, -> higher value means darker environment, noisy data, not calibrated
|
||||
#define CHIRP_GET_TEMPERATURE 0x05 // 16 bit, read
|
||||
#define CHIRP_RESET 0x06 // no value, write
|
||||
#define CHIRP_GET_VERSION 0x07 // 8 bit, read, -> 0x22 means 2.2
|
||||
#define CHIRP_SLEEP 0x08 // no value, write
|
||||
#define CHIRP_GET_BUSY 0x09 // 8 bit, read, -> 1 = busy, 0 = otherwise
|
||||
|
||||
/*********************************************************************************************\
|
||||
* helper function
|
||||
\*********************************************************************************************/
|
||||
|
||||
bool I2cWriteReg(uint8_t addr, uint8_t reg)
|
||||
{
|
||||
return I2cWrite(addr, reg, 0, 0);
|
||||
void ChirpWriteI2CRegister(uint8_t addr, uint8_t reg) {
|
||||
Wire.beginTransmission(addr);
|
||||
Wire.write(reg);
|
||||
Wire.endTransmission();
|
||||
} // now the original CHIRP needs 1100 ms delay
|
||||
|
||||
uint16_t ChirpFinishReadI2CRegister16bit(uint8_t addr) {
|
||||
Wire.requestFrom(addr,(uint8_t)2);
|
||||
uint16_t t = Wire.read() << 8;
|
||||
t = t | Wire.read();
|
||||
return t;
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
@ -99,14 +112,14 @@ uint8_t chirp_current = 0; // current selected/active sensor
|
|||
uint8_t chirp_found_sensors = 0; // number of found sensors
|
||||
|
||||
char chirp_name[7];
|
||||
uint8_t chirp_next_job = 0; //0=reset, 1=auto-wake, 2=moisture+temperature, 3=light, 4 = pause; 5 = TELE done
|
||||
uint8_t chirp_next_job = 0; //0=reset, 1=auto-wake, 2-13 = various measure steps; 14 = TELE done
|
||||
uint32_t chirp_timeout_count = 0; //is handled every second, so value is equal to seconds (it is a slow sensor)
|
||||
|
||||
#pragma pack(1)
|
||||
struct ChirpSensor_t{
|
||||
uint16_t moisture = 0; // shall hold post-processed data, if implemented
|
||||
uint16_t light = 0; // light level, maybe already postprocessed depending on the firmware
|
||||
int16_t temperature= 0; // temperature in degrees CELSIUS * 10
|
||||
int16_t temperature = 0; // temperature in degrees CELSIUS * 10 , we will also store the I2C error code
|
||||
uint8_t version = 0; // firmware-version
|
||||
uint8_t address:7; // we need only 7bit so...
|
||||
uint8_t explicitSleep:1; // there is a free bit to play with ;)
|
||||
|
@ -118,7 +131,7 @@ ChirpSensor_t chirp_sensor[CHIRP_MAX_SENSOR_COUNT]; // should be 8 bytes p
|
|||
/********************************************************************************************/
|
||||
|
||||
void ChirpReset(uint8_t addr) {
|
||||
I2cWriteReg(addr, CHIRP_RESET);
|
||||
ChirpWriteI2CRegister(addr, CHIRP_RESET);
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
@ -140,7 +153,7 @@ void ChirpClockSet() { // set I2C for this slow sensor
|
|||
/********************************************************************************************/
|
||||
|
||||
void ChirpSleep(uint8_t addr) {
|
||||
I2cWriteReg(addr, CHIRP_SLEEP);
|
||||
ChirpWriteI2CRegister(addr, CHIRP_SLEEP);
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
@ -168,77 +181,42 @@ void ChirpSleep(uint8_t addr) {
|
|||
void ChirpSelect(uint8_t sensor) {
|
||||
if(sensor < chirp_found_sensors) { //TODO: show some infos
|
||||
chirp_current = sensor;
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: Sensor %u now active."), chirp_current);
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: Sensor %u now active."), chirp_current);
|
||||
}
|
||||
if (sensor == 255) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: Sensor %u active at address 0x%x."), chirp_current, chirp_sensor[chirp_current].address);
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: Sensor %u active at address 0x%x."), chirp_current, chirp_sensor[chirp_current].address);
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
bool ChirpMeasureLight(void) {
|
||||
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
|
||||
if (chirp_sensor[i].version && !chirp_sensor[i].explicitSleep) {
|
||||
uint8_t lightReady = I2cRead8(chirp_sensor[i].address, CHIRP_GET_BUSY);
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: busy status for light for sensor %u"), lightReady);
|
||||
if (lightReady == 1) {
|
||||
return false; // a measurement is still in progress, we stop everything and come back in the next loop = 1 second
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: init measure light for sensor %u"), i);
|
||||
I2cWriteReg(chirp_sensor[i].address, CHIRP_MEASURE_LIGHT);
|
||||
}
|
||||
}
|
||||
return true; // we could read all values (maybe at different times, but that does not really matter) and consider this job finished
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
void ChirpReadCapTemp() { // no timeout needed for both measurements, so we do it at once
|
||||
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
|
||||
if (chirp_sensor[i].version && !chirp_sensor[i].explicitSleep) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: now really read CapTemp for sensor at address 0x%x"), chirp_sensor[i].address);
|
||||
chirp_sensor[i].moisture = I2cRead16(chirp_sensor[i].address, CHIRP_GET_CAPACITANCE);
|
||||
chirp_sensor[i].temperature = I2cRead16(chirp_sensor[i].address, CHIRP_GET_TEMPERATURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
bool ChirpReadLight() { // sophisticated calculations could be done here
|
||||
bool success = false;
|
||||
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: will read light for sensor %u"), i);
|
||||
if (chirp_sensor[i].version) {
|
||||
if (I2cValidRead16(&chirp_sensor[i].light, chirp_sensor[i].address, CHIRP_GET_LIGHT)){
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: light read success"));
|
||||
success = true;
|
||||
}
|
||||
if(!chirp_sensor[i].explicitSleep){ success = true;}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
/******************************************************************************************************************/
|
||||
|
||||
uint8_t ChirpReadVersion(uint8_t addr) {
|
||||
return (I2cRead8(addr, CHIRP_GET_VERSION));
|
||||
return (I2cRead8(addr, CHIRP_GET_VERSION)); // the Chirp!-sensor does not provide fw-version and we will get 255
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
/******************************************************************************************************************/
|
||||
|
||||
bool ChirpSet(uint8_t addr) {
|
||||
if(addr < 128){
|
||||
if (I2cWrite8(chirp_sensor[chirp_current].address, CHIRP_SET_ADDRESS, addr)){
|
||||
I2cWrite8(chirp_sensor[chirp_current].address, CHIRP_SET_ADDRESS, addr); // two calls are needed for sensor firmware version 2.6
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: Wrote adress %u "), addr);
|
||||
if(chirp_sensor[chirp_current].version>0x25 && chirp_sensor[chirp_current].version != 255){
|
||||
delay(5);
|
||||
I2cWrite8(chirp_sensor[chirp_current].address, CHIRP_SET_ADDRESS, addr);
|
||||
// two calls are needed for sensor firmware version 2.6, but maybe dangerous before
|
||||
}
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: Wrote adress %u "), addr);
|
||||
ChirpReset(chirp_sensor[chirp_current].address);
|
||||
chirp_sensor[chirp_current].address = addr;
|
||||
chirp_timeout_count = 10;
|
||||
chirp_next_job = 0;
|
||||
if(chirp_sensor[chirp_current].version == 255){ // this should be Chirp! and it seems to need a power cycle (or RESET to GND)
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: wrote new address %u, please power off device"), addr);
|
||||
chirp_sensor[chirp_current].version == 0; // make it "invisible"
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: address %u incorrect and not used"), addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -256,11 +234,12 @@ bool ChirpScan() {
|
|||
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "CHIRP:", address);
|
||||
if(chirp_found_sensors<CHIRP_MAX_SENSOR_COUNT){
|
||||
chirp_sensor[chirp_found_sensors].address = address; // push next sensor, as long as there is space in the array
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: fw %u"), chirp_sensor[chirp_found_sensors].version);
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: fw %x"), chirp_sensor[chirp_found_sensors].version);
|
||||
}
|
||||
chirp_found_sensors++;
|
||||
}
|
||||
}
|
||||
// chirp_timeout_count = 11; // wait a second to read the real fw-version in the next step
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Found %u CHIRP sensor(s)."), chirp_found_sensors);
|
||||
if (chirp_found_sensors == 0) {return false;}
|
||||
else {return true;}
|
||||
|
@ -273,57 +252,138 @@ void ChirpDetect(void)
|
|||
if (chirp_next_job > 0) {
|
||||
return;
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: scan will start ..."));
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: scan will start ..."));
|
||||
if (ChirpScan()) {
|
||||
uint8_t chirp_model = 0; // TODO: ??
|
||||
GetTextIndexed(chirp_name, sizeof(chirp_name), chirp_model, kChirpTypes);
|
||||
}
|
||||
}
|
||||
/********************************************************************************************/
|
||||
|
||||
void ChirpServiceAllSensors(uint8_t job){
|
||||
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
|
||||
if (chirp_sensor[i].version && !chirp_sensor[i].explicitSleep) {
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare for sensor at address 0x%x"), chirp_sensor[i].address);
|
||||
switch(job){
|
||||
case 0:
|
||||
ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_CAPACITANCE);
|
||||
break;
|
||||
case 1:
|
||||
chirp_sensor[i].moisture = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address);
|
||||
break;
|
||||
case 2:
|
||||
ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_TEMPERATURE);
|
||||
break;
|
||||
case 3:
|
||||
chirp_sensor[i].temperature = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address);
|
||||
break;
|
||||
case 4:
|
||||
ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_MEASURE_LIGHT);
|
||||
break;
|
||||
case 5:
|
||||
ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_LIGHT);
|
||||
break;
|
||||
case 6:
|
||||
chirp_sensor[i].light = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
void ChirpEverySecond(void)
|
||||
void ChirpEvery100MSecond(void)
|
||||
{
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: every second"));
|
||||
// DEBUG_SENSOR_LOG(PSTR("CHIRP: every second"));
|
||||
if(chirp_timeout_count == 0) { //countdown complete, now do something
|
||||
switch(chirp_next_job) {
|
||||
case 0: //this should only be called after driver initialization
|
||||
AddLog_P2(LOG_LEVEL_DEBUG,PSTR( "CHIRP: reset all"));
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: reset all"));
|
||||
ChirpResetAll();
|
||||
chirp_timeout_count = 1;
|
||||
chirp_timeout_count = 10; // wait a second
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 1: // auto-sleep-wake seems to expose a fundamental I2C-problem of the sensor and is deactivated
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: auto-wake all"));
|
||||
// DEBUG_SENSOR_LOG(PSTR("CHIRP: auto-wake all"));
|
||||
// ChirpAutoWakeAll(); // this is only a wake-up call at the start of next read cycle
|
||||
chirp_next_job++; // go on, next job should start in a second
|
||||
break;
|
||||
case 2:
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: call CapTemp twice"));
|
||||
ChirpReadCapTemp(); // it is reported to be useful, to read twice, because otherwise old values are received
|
||||
ChirpReadCapTemp(); // this is the "real" read call, we simply overwrite the existing values
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: call measure light"));
|
||||
ChirpMeasureLight(); // prepare the next step -> initiate light read
|
||||
chirp_timeout_count = 2; // wait 3 seconds, no need to hurry ...
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare moisture read"));
|
||||
ChirpServiceAllSensors(0);
|
||||
chirp_timeout_count = 11; // wait 1.1 seconds,
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 3:
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: call read light"));
|
||||
if (ChirpReadLight()){ // now read light and if successful continue, otherwise come back in a second and try again
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: auto-sleep all"));
|
||||
// ChirpSleepAll(); // let all sensors auto-sleep
|
||||
chirp_next_job++;
|
||||
}
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish moisture read"));
|
||||
ChirpServiceAllSensors(1);
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 4:
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: paused, waiting for TELE"));
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare moisture read - 2nd"));
|
||||
ChirpServiceAllSensors(0);
|
||||
chirp_timeout_count = 11; // wait 1.1 seconds,
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 5:
|
||||
if (Settings.tele_period > 9){
|
||||
chirp_timeout_count = Settings.tele_period - 10; // sync it with the TELEPERIOD, we need about up to 10 seconds to measure, depending on the light level
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: timeout: %u, tele: %u"), chirp_timeout_count, Settings.tele_period);
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish moisture read - 2nd"));
|
||||
ChirpServiceAllSensors(1);
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 6:
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare temperature read"));
|
||||
ChirpServiceAllSensors(2);
|
||||
chirp_timeout_count = 11; // wait 1.1 seconds,
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 7:
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish temperature read"));
|
||||
ChirpServiceAllSensors(3);
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 8:
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare temperature read - 2nd"));
|
||||
ChirpServiceAllSensors(2);
|
||||
chirp_timeout_count = 11; // wait 1.1 seconds,
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 9:
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish temperature read - 2nd"));
|
||||
ChirpServiceAllSensors(3);
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 10:
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: start light measure process"));
|
||||
ChirpServiceAllSensors(4);
|
||||
chirp_timeout_count = 90; // wait 9 seconds,
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 11:
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare light read"));
|
||||
ChirpServiceAllSensors(5);
|
||||
chirp_timeout_count = 11; // wait 1.1 seconds,
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 12:
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish light read"));
|
||||
ChirpServiceAllSensors(6);
|
||||
chirp_next_job++;
|
||||
break;
|
||||
case 13:
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: paused, waiting for TELE"));
|
||||
break;
|
||||
case 14:
|
||||
if (Settings.tele_period > 16){
|
||||
chirp_timeout_count = (Settings.tele_period - 17) * 10; // sync it with the TELEPERIOD, we need about up to 17 seconds to measure
|
||||
DEBUG_SENSOR_LOG(PSTR("CHIRP: timeout 1/10 sec: %u, tele: %u"), chirp_timeout_count, Settings.tele_period);
|
||||
}
|
||||
else{
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: TELEPERIOD must be > 16 seconds !"));
|
||||
// we could overwrite it to i.e. 20 seconds here
|
||||
}
|
||||
chirp_next_job = 1; // back to step 1
|
||||
break;
|
||||
}
|
||||
|
@ -337,13 +397,15 @@ void ChirpEverySecond(void)
|
|||
// normaly in i18n.h
|
||||
|
||||
#define D_JSON_MOISTURE "Moisture"
|
||||
#define D_JSON_DARKNESS "Darkness"
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
// {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
|
||||
|
||||
const char HTTP_SNS_MOISTURE[] PROGMEM = "{s} " D_JSON_MOISTURE ": {m}%s %{e}";
|
||||
const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address: {m}0x%x{e}"
|
||||
"{s} FW-version: {m}%s {e}"; ;
|
||||
const char HTTP_SNS_MOISTURE[] PROGMEM = "{s} " D_JSON_MOISTURE "{m}%s %{e}";
|
||||
const char HTTP_SNS_DARKNESS[] PROGMEM = "{s} " D_JSON_DARKNESS "{m}%s %{e}";
|
||||
const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address{m}0x%x{e}"
|
||||
"{s} FW-version{m}%s {e}"; ;
|
||||
const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}";
|
||||
#endif // USE_WEBSERVER
|
||||
|
||||
|
@ -362,20 +424,28 @@ void ChirpShow(bool json)
|
|||
dtostrfd(t_temperature, Settings.flag2.temperature_resolution, str_temperature);
|
||||
char str_light[33];
|
||||
dtostrfd(chirp_sensor[i].light, 0, str_light);
|
||||
char str_version[33];
|
||||
dtostrfd(chirp_sensor[i].version, 0, str_version);
|
||||
char str_version[7];
|
||||
if(chirp_sensor[i].version == 0xff){
|
||||
strncpy_P(str_version, PSTR("Chirp!"), sizeof(str_version));
|
||||
}
|
||||
else{
|
||||
sprintf(str_version, "%x", chirp_sensor[i].version);
|
||||
}
|
||||
if (json) {
|
||||
if(!chirp_sensor[i].explicitSleep){
|
||||
ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%s,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_ILLUMINANCE "\":\"%s}"),
|
||||
chirp_name, i, str_moisture, str_temperature, str_light);}
|
||||
if(!chirp_sensor[i].explicitSleep) {
|
||||
ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%s"),chirp_name, i, str_moisture);
|
||||
if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature
|
||||
ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"),str_temperature);
|
||||
}
|
||||
ResponseAppend_P(PSTR(",\"" D_JSON_DARKNESS "\":%s}"),str_light);
|
||||
}
|
||||
else {
|
||||
ResponseAppend_P(PSTR(",\"%s%u\":{\"sleeping\"}"),
|
||||
chirp_name, i);
|
||||
ResponseAppend_P(PSTR(",\"%s%u\":{\"sleeping\"}"),chirp_name, i);
|
||||
}
|
||||
#ifdef USE_DOMOTICZ
|
||||
if (0 == tele_period) {
|
||||
DomoticzTempHumSensor(str_temperature, str_moisture);
|
||||
DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light);
|
||||
DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light); // this is not LUX!!
|
||||
}
|
||||
#endif // USE_DOMOTICZ
|
||||
#ifdef USE_WEBSERVER
|
||||
|
@ -386,8 +456,10 @@ void ChirpShow(bool json)
|
|||
}
|
||||
else {
|
||||
WSContentSend_PD(HTTP_SNS_MOISTURE, str_moisture);
|
||||
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, " ", chirp_sensor[i].light);
|
||||
WSContentSend_PD(HTTP_SNS_TEMP, " ",str_temperature, TempUnit());
|
||||
WSContentSend_PD(HTTP_SNS_DARKNESS, str_light);
|
||||
if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature
|
||||
WSContentSend_PD(HTTP_SNS_TEMP, " ",str_temperature, TempUnit());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_WEBSERVER
|
||||
|
@ -435,9 +507,9 @@ bool ChirpCmd(void) {
|
|||
Response_P(S_JSON_CHIRP_COMMAND, command, XdrvMailbox.payload);
|
||||
break;
|
||||
default:
|
||||
// else for Unknown command
|
||||
serviced = false;
|
||||
break;
|
||||
// else for Unknown command
|
||||
serviced = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return serviced;
|
||||
|
@ -456,9 +528,9 @@ bool Xsns48(uint8_t function)
|
|||
case FUNC_INIT:
|
||||
ChirpDetect(); // We can call CHIRPSCAN later to re-detect
|
||||
break;
|
||||
case FUNC_EVERY_SECOND:
|
||||
case FUNC_EVERY_100_MSECOND:
|
||||
if(chirp_found_sensors > 0){
|
||||
ChirpEverySecond();
|
||||
ChirpEvery100MSecond();
|
||||
}
|
||||
break;
|
||||
case FUNC_COMMAND:
|
||||
|
@ -466,7 +538,7 @@ bool Xsns48(uint8_t function)
|
|||
break;
|
||||
case FUNC_JSON_APPEND:
|
||||
ChirpShow(1);
|
||||
chirp_next_job = 5; // TELE done, now compute time for next measure cycle
|
||||
chirp_next_job = 14; // TELE done, now compute time for next measure cycle
|
||||
break;
|
||||
#ifdef USE_WEBSERVER
|
||||
case FUNC_WEB_SENSOR:
|
||||
|
|
|
@ -847,13 +847,15 @@ uint8_t Serial_peek() {
|
|||
return meter_ss[num-1]->peek();
|
||||
}
|
||||
|
||||
uint8_t sml_logindex;
|
||||
|
||||
void Dump2log(void) {
|
||||
|
||||
int16_t index=0,hcnt=0;
|
||||
uint32_t d_lastms;
|
||||
uint8_t dchars[16];
|
||||
|
||||
if (!SML_SAVAILABLE) return;
|
||||
//if (!SML_SAVAILABLE) return;
|
||||
|
||||
if (dump2log&8) {
|
||||
// combo mode
|
||||
|
@ -898,7 +900,25 @@ uint8_t dchars[16];
|
|||
}
|
||||
}
|
||||
} else {
|
||||
//while (SML_SAVAILABLE) {
|
||||
if (meter_desc_p[(dump2log&7)-1].type=='o') {
|
||||
// obis
|
||||
while (SML_SAVAILABLE) {
|
||||
char c=SML_SREAD&0x7f;
|
||||
if (c=='\n' || c=='\r') {
|
||||
log_data[sml_logindex]=0;
|
||||
AddLog(LOG_LEVEL_INFO);
|
||||
sml_logindex=2;
|
||||
log_data[0]=':';
|
||||
log_data[1]=' ';
|
||||
break;
|
||||
}
|
||||
log_data[sml_logindex]=c;
|
||||
if (sml_logindex<sizeof(log_data)-2) {
|
||||
sml_logindex++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//while (SML_SAVAILABLE) {
|
||||
index=0;
|
||||
log_data[index]=':';
|
||||
index++;
|
||||
|
@ -907,45 +927,36 @@ uint8_t dchars[16];
|
|||
d_lastms=millis();
|
||||
while ((millis()-d_lastms)<40) {
|
||||
if (SML_SAVAILABLE) {
|
||||
if (meter_desc_p[(dump2log&7)-1].type=='o') {
|
||||
// obis
|
||||
char c=SML_SREAD&0x7f;
|
||||
if (c=='\n' || c=='\r') break;
|
||||
log_data[index]=c;
|
||||
index++;
|
||||
unsigned char c;
|
||||
if (meter_desc_p[(dump2log&7)-1].type=='e') {
|
||||
// ebus
|
||||
c=SML_SREAD;
|
||||
sprintf(&log_data[index],"%02x ",c);
|
||||
index+=3;
|
||||
if (c==EBUS_SYNC) break;
|
||||
} else {
|
||||
unsigned char c;
|
||||
if (meter_desc_p[(dump2log&7)-1].type=='e') {
|
||||
// ebus
|
||||
c=SML_SREAD;
|
||||
sprintf(&log_data[index],"%02x ",c);
|
||||
index+=3;
|
||||
if (c==EBUS_SYNC) break;
|
||||
// sml
|
||||
if (sml_start==0x77) {
|
||||
sml_start=0;
|
||||
} else {
|
||||
// sml
|
||||
if (sml_start==0x77) {
|
||||
sml_start=0;
|
||||
} else {
|
||||
c=SML_SPEAK;
|
||||
if (c==0x77) {
|
||||
sml_start=c;
|
||||
break;
|
||||
}
|
||||
c=SML_SPEAK;
|
||||
if (c==0x77) {
|
||||
sml_start=c;
|
||||
break;
|
||||
}
|
||||
c=SML_SREAD;
|
||||
sprintf(&log_data[index],"%02x ",c);
|
||||
index+=3;
|
||||
}
|
||||
c=SML_SREAD;
|
||||
sprintf(&log_data[index],"%02x ",c);
|
||||
index+=3;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index>0) {
|
||||
if (index>2) {
|
||||
log_data[index]=0;
|
||||
AddLog(LOG_LEVEL_INFO);
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// skip sml entries
|
||||
|
@ -1750,6 +1761,7 @@ void SML_Show(boolean json) {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
#ifdef USE_DOMOTICZ
|
||||
if (json && !tele_period) {
|
||||
char str[16];
|
||||
|
@ -1761,6 +1773,7 @@ void SML_Show(boolean json) {
|
|||
DomoticzSensor(DZ_CURRENT, str); // Current
|
||||
}
|
||||
#endif // USE_DOMOTICZ
|
||||
*/
|
||||
}
|
||||
|
||||
struct SML_COUNTER {
|
||||
|
|
Loading…
Reference in New Issue