Merge branch 'development' into pre-release

This commit is contained in:
Theo Arends 2020-04-05 14:33:47 +02:00
commit 4b98e9dfb2
60 changed files with 4890 additions and 3049 deletions

View File

@ -13,7 +13,7 @@ Please DO NOT OPEN AN ISSUE:
- 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://tasmota.github.io/docs/#/help/FAQ) and [Troubleshooting](https://tasmota.github.io/docs/#/help/Troubleshooting)
- If your issue is a Wi-Fi problem or MQTT problem, please try the steps provided in the [FAQ](https://tasmota.github.io/docs/FAQ) and [Troubleshooting](https://tasmota.github.io/docs/Troubleshooting)
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.
@ -29,7 +29,7 @@ _Make sure your have performed every step and checked the applicable boxes befor
- [ ] Read the [Contributing Guide and Policy](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md) and [the Code of Conduct](https://github.com/arendst/Tasmota/blob/development/CODE_OF_CONDUCT.md)
- [ ] Searched the problem in [issues](https://github.com/arendst/Tasmota/issues)
- [ ] Searched the problem in the [docs](https://tasmota.github.io/docs/#/help/FAQ)
- [ ] Searched the problem in the [docs](https://tasmota.github.io/docs/FAQ)
- [ ] 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): _____

View File

@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Tasmota Docs
url: https://tasmota.github.io/docs/#/
url: https://tasmota.github.io/docs
about: All the information related to Tasmota
- name: Tasmota Support Chat
url: https://discord.gg/Ks2Kzd4

View File

@ -19,7 +19,7 @@ comment: >-
[Support Information](https://github.com/arendst/Sonoff-Tasmota/blob/development/SUPPORT.md)
[Wiki](https://tasmota.github.io/docs/#/) for more information.
[Wiki](https://tasmota.github.io/docs/) for more information.
[Chat](https://discord.gg/Ks2Kzd4) for more user experience.

2
API.md
View File

@ -2,7 +2,7 @@
# Basic API information
Tasmota can easily be extended by developers using provided function pointers as callback Ids. This document lists the available callback function Ids. Read [Sensor API](https://tasmota.github.io/docs/#/Sensor-API) for more information.
Tasmota can easily be extended by developers using provided function pointers as callback Ids. This document lists the available callback function Ids. Read [Sensor API](https://tasmota.github.io/docs/Sensor-API) for more information.
Callback availability can be checked by searching for either XdrvCall, XsnsCall, XdspCall, XnrgCall and XlgtCall.

View File

@ -71,7 +71,7 @@
| | | | | | | | |
| USE_ADC_VCC | x | x | - | - | - | - | - |
| USE_COUNTER | - | - | x | x | x | x | x |
| USE_DS18x20 | - | - | x | x | x | - | x |
| USE_DS18x20 | - | - | x | x | x | x | x |
| USE_DHT | - | - | x | x | x | x | x |
| USE_MAX31855 | - | - | - | - | x | - | - |
| USE_MAX31865 | - | - | - | - | - | - | - |

View File

@ -8,7 +8,7 @@ Everybody is welcome and invited to contribute to Tasmota Project by:
* Testing newly released features and reporting issues.
* Providing Pull Requests (Features, Proof of Concepts, Language files or Fixes)
* Contributing missing documentation for features and devices in our [documentation](https://tasmota.github.io/docs/#/Contributing)
* Contributing missing documentation for features and devices in our [documentation](https://tasmota.github.io/docs/Contributing)
This document describes rules that are in effect for this repository, meant for handling issues by contributors in the issue tracker and PRs.

View File

@ -23,7 +23,7 @@ Module | Description
17 WiOn | WiOn Wifi Smart Socket
18 Generic | Any ESP8266/ESP8285 device like WeMos and NodeMCU
19 Sonoff Dev | Sonoff Dev Wifi Development Board
20 H801 | H801 Wifi RGBWW Led Controller
20 H801 | H801 Wifi 5 Channel LED Controller
21 Sonoff SC | Sonoff SC Wifi Environmental Monitor
22 Sonoff BN-SZ | Sonoff BN-SZ01 Wifi Ceiling Led (Retired)
23 Sonoff 4CH Pro | Sonoff 4CH Pro 4-gang Wifi Smart Switch
@ -57,7 +57,7 @@ Module | Description
51 OBI Socket | OBI Wifi Smart Socket
52 Teckin | Teckin SP22 Wifi Smart Switch with Energy Monitoring
53 AplicWDP303075 | Aplic WDP 303075 CSL Wifi Smart Switch with Energy Monitoring
54 Tuya Dimmer | MIUO (and other Tuya based) Wifi Dimmer for Incandescent Lights and Led
54 TuyaMCU | Devices with an MCU using Tuya communication protocol for control
55 Gosund SP1 v23 | Gosund SP1 v2.3 Wifi Smart Switch with Energy Monitoring
56 ARMTR Dimmer | ARMtronix Wifi dimmer for Incandescent Lights and Led
57 SK03 Outdoor | SK03 Outdoor Wifi Smart Switch with Energy Monitoring
@ -65,7 +65,7 @@ Module | Description
59 Teckin US | Teckin SP20 and ZooZee SA102 Wifi Smart Switch with Energy Monitoring
60 Manzoku strip | Manzoku Wifi Smart Power Strip with four Relays
61 OBI Socket 2 | OBI 2 Wifi Smart Socket
62 YTF IR Bridge | YTF Infra Red Wifi Bridge
62 YTF IR Bridge | YTF Universal IR Bridge
63 Digoo DG-SP202 | Digoo DG-SP202 Dual Wifi Smart Switch with Energy Monitoring
64 KA10 | Smanergy KA10 Wifi Smart Wall Switch with Energy Monitoring
65 Luminea ZX2820 | Luminea ZX2820 Wifi Smart Switch with Energy Monitoring

View File

@ -25,9 +25,9 @@ Holding the power button, tapping the down button and then tapping or holding th
Holding the power button, tapping the up button and then tapping or holding the down or up button publishes an MQTT Event command. The command is sent/value is adjusted once every .5 seconds for as long as the button is held. The MQTT topic is as described above. The MQTT payload is Trigger#, where # is 3 if the down button is held or 4 if the up button is held.
Holding the down or up button alone for over 10 seconds executes the WiFiConfig 2 command.
Holding any button alone for over 10 seconds executes the WiFiConfig 2 command.
Pressing and releasing any button publishes an MQTT TOGGLE command for the button. Holding a button publishes an MQTT HOLD command followed by an MQTT OFF command when the button is released.
SetOption32 defines the button hold time. When the PWM Dimmer module is initially selected, SetOption32 is set to 5 (1/2 second). Button presses and holds execute the normal ButtonTopic and Rule processing. If ButtonTopic is set and SetOption61 is 0 or a the button press/hold matches a rule, the button press/hold is ignored by PWM Dimmer.
When Device Groups are enabled, the PWM Dimmer brightness presets are kept in sync across all switches in the group. The powered-off LED and LED timeout settings are specific to each switch. Changing them does not replicate the change to the other switches in the group.
@ -81,7 +81,7 @@ When Device Groups are enabled, the PWM Dimmer brightness presets are kept in sy
Remote device mode allows PWM Dimmer switches to control remote devices. With remote device mode enabled, each button controls a different device. Note that dimmer switches with toggle-style down/up buttons have limited functionality as remote device mode switches because you can not push the down and up buttons simultaneously.
To include remote device mode support in the build, define USE_PWM_DIMMER_REMOTE in your user_config_override. Remote device mode support requires device group support so USE_DEVICE_GROUPS is automatically defined if USE_PWM_DIMMER_REMOTE is defined. Remote device mode support adds 0.7K to the code size in addition to the code size required for device groups support.
To include remote device mode support in the build, define USE_PWM_DIMMER_REMOTE in your user_config_override. Remote device mode support requires device group support so USE_DEVICE_GROUPS is automatically defined if USE_PWM_DIMMER_REMOTE is defined. Remote device mode support adds 1.2K to the code size in addition to the code size required for device groups support.
To enable remote device mode, execute the command SetOption88 1 (the device will restart). Each remote device must be running firmware with device group support and have remote device support enabled. The remote devices do not need to be built with PWM dimmer support nor do they need to be switches.

View File

@ -42,10 +42,10 @@ We don't take any responsibility nor liability for using this software nor for t
## Note
Please do not ask to add new devices unless it requires additional code for new features. If the device is not listed as a module, try using [Templates](https://tasmota.github.io/docs/#/Templates) first. If it is not listed in the [Tasmota Device Templates Repository](http://blakadder.github.io/templates) create your own [Template](https://tasmota.github.io/docs/#/Templates?id=creating-your-template).
Please do not ask to add new devices unless it requires additional code for new features. If the device is not listed as a module, try using [Templates](https://tasmota.github.io/docs/Templates) first. If it is not listed in the [Tasmota Device Templates Repository](http://blakadder.github.io/templates) create your own [Template](https://tasmota.github.io/docs/Templates#creating-your-template).
## Quick Install
Download one of the released binaries from https://github.com/arendst/Tasmota/releases and flash it to your hardware [using our installation guide](https://tasmota.github.io/docs/#/installation/).
Download one of the released binaries from https://github.com/arendst/Tasmota/releases and flash it to your hardware [using our installation guide](https://tasmota.github.io/docs/Getting-Started).
## Important User Compilation Information
If you want to compile Tasmota yourself keep in mind the following:
@ -60,7 +60,7 @@ Please refer to the installation and configuration articles in our [documentatio
## Migration Information
See [wiki migration path](https://tasmota.github.io/docs/#/Upgrading?id=migration-path) for instructions how to migrate to a major version. Pay attention to the following version breaks due to dynamic settings updates:
See [wiki migration path](https://tasmota.github.io/docs/Upgrading#migration-path) for instructions how to migrate to a major version. Pay attention to the following version breaks due to dynamic settings updates:
1. Migrate to **Sonoff-Tasmota 3.9.x**
2. Migrate to **Sonoff-Tasmota 4.x**
@ -77,15 +77,15 @@ See [wiki migration path](https://tasmota.github.io/docs/#/Upgrading?id=migratio
<img src="https://user-images.githubusercontent.com/5904370/68332933-e6e5a600-00d7-11ea-885d-50395f7239a1.png" width=150 align="right" />
For a database of supported devices see [Tasmota Device Templates Repository](https://blakadder.github.io/templates)
For a database of supported devices see [Tasmota Device Templates Repository](https://templates.blakadder.com)
If you're looking for support on **Tasmota** there are some options available:
### Documentation
* [Documentation Site](https://tasmota.github.io/docs): For information on how to flash Tasmota, configure, use and expand it
* [FAQ and Troubleshooting](https://tasmota.github.io/docs/#/help/): For information on common problems and solutions.
* [Commands Information](https://tasmota.github.io/docs/#/Commands): For information on all the commands supported by Tasmota.
* [FAQ and Troubleshooting](https://tasmota.github.io/docs/FAQ/): For information on common problems and solutions.
* [Commands Information](https://tasmota.github.io/docs/Commands): For information on all the commands supported by Tasmota.
### Support's Community

View File

@ -4,7 +4,7 @@
## Migration Information
See [migration path](https://tasmota.github.io/docs/#/Upgrading?id=migration-path) for instructions how to migrate to a major version. Pay attention to the following version breaks due to dynamic settings updates:
See [migration path](https://tasmota.github.io/docs/Upgrading#migration-path) for instructions how to migrate to a major version. Pay attention to the following version breaks due to dynamic settings updates:
1. Migrate to **Sonoff-Tasmota 3.9.x**
2. Migrate to **Sonoff-Tasmota 4.x**
@ -21,13 +21,13 @@ While fallback or downgrading is common practice it was never supported due to S
## Supported Core versions
This release will be supported from ESP8266/Arduino library Core version **2.6.3 + 372a3ec** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
This release will be supported from ESP8266/Arduino library Core version **2.6.3 + e64cb61** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
Although it might still compile on previous Core versions all support will be removed in the near future.
## Support of TLS
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. Read [full documentation](https://tasmota.github.io/docs/#/integrations/AWS-IoT)
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. Read [full documentation](https://tasmota.github.io/docs/AWS-IoT)
## Initial configuration tools
@ -35,7 +35,7 @@ For initial configuration this release supports Webserver based **WifiManager**
## Provided Binary Downloads
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.6.3 + 372a3ec**.
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.6.3 + e64cb61**.
- **tasmota.bin** = The Tasmota version with most drivers. **RECOMMENDED RELEASE BINARY**
- **tasmota-BG.bin** to **tasmota-TW.bin** = The Tasmota version in different languages.
@ -52,76 +52,23 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
## Changelog
### Version 8.2.0 Elliot
### Version 8.2.1 Elliot
- Change default my_user_config.h driver and sensor support removing most sensors and adding most drivers to tasmota.bin
- Change DHT driver (#7468, #7717)
- Change Lights: simplified gamma correction and 10 bits internal computation
- Change commands ``Prefix``, ``Ssid``, ``StateText``, ``NTPServer``, and ``FriendlyName`` displaying all items
- Change Zigbee command prefix from ``Zigbee*`` to ``Zb*``
- Change MQTT message size with additional 200 characters
- Change display of some date and time messages from "Wed Feb 19 10:45:12 2020" to "2020-02-19T10:45:12"
- Change IRremoteESP8266 library updated to v2.7.4
- Fix ``PowerDelta`` zero power detection (#7515)
- Fix ``White`` added to light status (#7142)
- Fix ``WakeUp <x>`` ignores provided value (#7473)
- Fix ``RGBWWTable`` ignored (#7572)
- Fix commands ``Display`` and ``Counter`` from overruling command processing (#7322)
- Fix Sonoff Bridge, Sc, L1, iFan03 and CSE7766 serial interface to forced speed, config and disable logging
- Fix Improved fade linearity with gamma correction
- Fix PWM flickering at low levels (#7415)
- Fix LCD line and column positioning (#7387)
- Fix Display handling of hexadecimal escape characters (#7387)
- Fix exception 9 restart on log message in Ticker interrupt service routines NTP, Wemos and Hue emulation (#7496)
- Fix Hass sensor discovery by Federico Leoni (#7582, #7548)
- Fix MaxPower functionality (#7647)
- Fix relation between Wifi RSSI and signal strength
- Add command ``SetOption79 0/1`` to enable reset of counters at teleperiod time by Andre Thomas (#7355)
- Add command ``SetOption82 0/1`` to limit the CT range for Alexa to 200..380
- Add command ``SetOption84 0/1`` to send AWS IoT device shadow updates (alternative to retained)
- Add commands ``SetOption85 0/1`` and ``DevGroupShare`` supporting UDP Group command using ``GroupTopic`` without MQTT by Paul Diem (#7790)
- Add command ``SetOption86 0/1`` for PWM dimmer to turn brightness LED's off 5 seconds after last change
- Add command ``SetOption87 0/1`` for PWM dimmer to turn red LED on when powered off
- Add command ``SetOption88 0/1`` for PWM dimmer to let buttons control remote devices
- Add command ``SetOption89 0/1`` for Zigbee distinct MQTT topics per device for SENSOR, allowing retained messages (#7835)
- Add command ``ShutterButton <parameters>`` to control shutter(s) by to-scho (#7403)
- Add commands ``SwitchMode 8`` ToggleMulti, ``SwitchMode 9`` FollowMulti and ``SwitchMode 10`` FollowMultiInverted (#7522)
- Add commands ``SwitchMode 11`` PushHoldMulti and ``SwitchMode 12`` PushHoldInverted (#7603)
- Add commands ``SwitchMode 13`` PushOn and ``SwitchMode 14`` PushOnInverted (#7912)
- Add command ``Buzzer -1`` for infinite mode and command ``Buzzer -2`` for following led mode (#7623)
- Add command ``HumOffset -10.0 .. 10.0`` to set global humidity sensor offset (#7934)
- Add support for ``AdcParam`` parameters to control ADC0 Current Transformer Apparent Power formula by Jodi Dillon (#7100)
- Add optional parameter ``<startcolor>`` to command ``Scheme <scheme>, <startcolor>`` to control initial start color
- Add web page sliders when ``SetOption37 128`` is active allowing control of white(s)
- Add SerialConfig to ``Status 1``
- Add BootCount Reset Time as BCResetTime to ``Status 1``
- Add WifiPower to ``Status 5``
- Add most SetOptions as defines to my_user_config.h
- Add optional Wifi AccessPoint passphrase define WIFI_AP_PASSPHRASE in my_user_config.h (#7690)
- Add SoftwareSerial to CSE7766 driver allowing different GPIOs (#7563)
- Add rule trigger on one level deeper using syntax with two ``#`` like ``on zbreceived#vibration_sensor#aqaracubeside=0 do ...``
- Add Zigbee attribute decoder for Xiaomi Aqara Cube
- Add ``ZbZNPReceived``and ``ZbZCLReceived`` being published to MQTT when ``SetOption66 1``
- Add Zigbee enhanced commands decoding, added ``ZbPing``
- Add Zigbee features and improvements
- Add Zigbee support for Hue emulation by Stefan Hadinger
- Add HAss Discovery support for Button and Switch triggers by Federico Leoni (#7901)
- Add Dew Point to Temperature and Humidity sensors
- Add optional support for Prometheus using file xsns_91_prometheus.ino (#7216)
- Add support for gzipped binaries
- Add support for Romanian language translations by Augustin Marti
- Add support for sensors DS18x20 and DHT family on Shelly 1 and Shelly 1PM using Shelly Add-On adapter (#7469)
- Add support to BMP driver to enter reset state (sleep enable) when deep sleep is used in Tasmota
- Add support for DS1624, DS1621 Temperature sensor by Leonid Myravjev
- Add support for NRF24L01 as BLE-bridge for Mijia Bluetooth sensors by Christian Baars (#7394)
- Add support for MI-BLE sensors using HM-10 Bluetooth 4.0 module by Christian Staars (#7683)
- Add support for FiF LE-01MR energy meter by saper-2 (#7584)
- Add support for Sensors AHT10 and AHT15 by Martin Wagner (#7596)
- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764)
- Add support for La Crosse TX23 Anemometer by Norbert Richter (#3146, #7765)
- Add support for Martin Jerry/acenx/Tessan/NTONPOWER SD0x PWM dimmer switches by Paul Diem (#7791)
- Add support for UDP Group control without MQTT by Paul Diem (#7790)
- Add support for Jarolift rollers by Keeloq algorithm
- Add support for MaxBotix HRXL-MaxSonar ultrasonic range finders by Jon Little (#7814)
- Add support for HDC1080 Temperature and Humidity sensor by Luis Teixeira (#7888)
- Add support for ElectriQ iQ-wifiMOODL RGBW light by Ian King (#7947)
- Change HM-10 sensor type detection and add features (#7962)
- Change light scheme 2,3,4 cycle time speed from 24,48,72,... seconds to 4,6,12,24,36,48,... seconds (#8034)
- Change remove floating point libs from IRAM
- Change remove MQTT Info messages on restart for DeepSleep Wake (#8044)
- Fix possible Relay toggle on (OTA) restart
- Fix Zigbee sending wrong Sat value with Hue emulation
- Add Zigbee command ``ZbRestore`` to restore device configuration dumped with ``ZbStatus 2``
- Add Zigbee command ``ZbUnbind``
- Add Zigbee command ``ZbBindState`` and ``manuf``attribute
- Add commands ``CounterDebounceLow`` and ``CounterDebounceHigh`` to control debouncing (#8021)
- Add command ``SetOption90 1`` to disable non-json MQTT messages (#8044)
- Add command ``Sensor10 0/1/2`` to control BH1750 resolution - 0 = High (default), 1 = High2, 2 = Low (#8016)
- Add command ``Sensor10 31..254`` to control BH1750 measurement time which defaults to 69 (#8016)
- Add support for unreachable (unplugged) Zigbee devices in Philips Hue emulation and Alexa
- Add support for 64x48 SSD1306 OLED (#6740)
- Add support for up to four MQTT GroupTopics using the same optional Device Group names (#8014)
- Add console command history (#7483, #8015)
- Add support for longer template names

View File

@ -916,15 +916,22 @@ uint8_t *Adafruit_SSD1306::getBuffer(void) {
of graphics commands, as best needed by one's own application.
*/
void Adafruit_SSD1306::display(void) {
int16_t col_start = 0;
int16_t col_end = WIDTH - 1;
if ((64 == WIDTH) && (48 == HEIGHT)) { // for 64x48, we need to shift by 32 in both directions
col_start += 32;
col_end += 32;
}
TRANSACTION_START
static const uint8_t PROGMEM dlist1[] = {
SSD1306_PAGEADDR,
0, // Page start address
0xFF, // Page end (not really, but works here)
SSD1306_COLUMNADDR,
0 }; // Column start address
SSD1306_COLUMNADDR };
ssd1306_commandList(dlist1, sizeof(dlist1));
ssd1306_command1(WIDTH - 1); // Column end address
ssd1306_command1(col_start); // Column start address
ssd1306_command1(col_end); // Column end address
#if defined(ESP8266)
// ESP8266 needs a periodic yield() call to avoid watchdog reset.

View File

@ -57,7 +57,8 @@ default_envs =
framework = arduino
board = esp01_1m
board_build.flash_mode = dout
board_build.ldscript = eagle.flash.1m.ld
; board_build.ldscript = eagle.flash.1m.ld
board_build.ldscript = ${PROJECTSRC_DIR}/ld/eagle.flash.1m.ld_FP_IN_IROM
platform = ${core_active.platform}
platform_packages = ${core_active.platform_packages}
@ -85,6 +86,7 @@ extra_scripts = ${scripts_defaults.extra_scripts}
[scripts_defaults]
extra_scripts = pio/strip-floats.py
pio/name-firmware.py
pio/gzip-firmware.py
[esp82xx_defaults]
build_flags = -D NDEBUG
@ -107,7 +109,7 @@ build_flags = ${tasmota_core_stage.build_flags}
[tasmota_core_stage]
; *** Esp8266 core for Arduino version stable beta
platform = espressif8266@2.3.3
platform = espressif8266@2.4.0
platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#372a3ec297dfe8501bed1ec4552244695b5e8ced
build_flags = ${esp82xx_defaults.build_flags}
-DBEARSSL_SSL_BASIC

View File

@ -54,7 +54,6 @@ build_flags = ${core_active.build_flags}
upload_port = COM5
extra_scripts = ${scripts_defaults.extra_scripts}
pio/gzip-firmware.py
pio/obj-dump.py
; *** Upload file to OTA server using SCP
@ -79,9 +78,9 @@ extra_scripts = ${scripts_defaults.extra_scripts}
;platform_packages = ${core_2_6_3.platform_packages}
;build_flags = ${core_2_6_3.build_flags}
platform = ${tasmota_core_stage.platform}
platform_packages = ${tasmota_core_stage.platform_packages}
build_flags = ${tasmota_core_stage.build_flags}
platform = ${tasmota_feature_stage.platform}
platform_packages = ${tasmota_feature_stage.platform_packages}
build_flags = ${tasmota_feature_stage.build_flags}
;platform = ${core_stage.platform}
;platform_packages = ${core_stage.platform_packages}
@ -135,7 +134,7 @@ build_flags = ${esp82xx_defaults.build_flags}
[core_2_6_3]
; *** Esp8266 core for Arduino version 2.6.3
platform = espressif8266@2.3.3
platform = espressif8266@2.4.0
platform_packages =
build_flags = ${esp82xx_defaults.build_flags}
-DBEARSSL_SSL_BASIC
@ -177,10 +176,10 @@ build_flags = ${esp82xx_defaults.build_flags}
; -fexceptions
; -lstdc++-exc
[tasmota_core_stage]
; *** Esp8266 core for Arduino version stable beta
platform = espressif8266@2.3.3
platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#372a3ec297dfe8501bed1ec4552244695b5e8ced
[tasmota_feature_stage]
; *** Esp8266 core for Arduino version Tasmota feature stage
platform = espressif8266@2.4.0
platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#e64cb619f79a3c888dd56b957d8f48ce74f35735
build_flags = ${esp82xx_defaults.build_flags}
-DBEARSSL_SSL_BASIC
; NONOSDK221
@ -223,7 +222,7 @@ build_flags = ${esp82xx_defaults.build_flags}
[core_stage]
; *** Esp8266 core for Arduino version latest beta
platform = espressif8266@2.3.3
platform = espressif8266@2.4.0
platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git
build_flags = ${esp82xx_defaults.build_flags}
-DBEARSSL_SSL_BASIC

View File

@ -2,9 +2,40 @@
## Released
### 8.2.1 20200410
- Release Elliot
### 8.2.0.3 20200329
- Change light scheme 2,3,4 cycle time speed from 24,48,72,... seconds to 4,6,12,24,36,48,... seconds (#8034)
- Change remove floating point libs from IRAM
- Change remove MQTT Info messages on restart for DeepSleep Wake (#8044)
- Add support for longer template names
- Add Zigbee command ``ZbBindState`` and ``manuf``attribute
- Add commands ``CounterDebounceLow`` and ``CounterDebounceHigh`` to control debouncing (#8021)
- Add command ``SetOption90 1`` to disable non-json MQTT messages (#8044)
- Add command ``Sensor10 0/1/2`` to control BH1750 resolution - 0 = High (default), 1 = High2, 2 = Low (#8016)
- Add command ``Sensor10 31..254`` to control BH1750 measurement time which defaults to 69 (#8016)
### 8.2.0.2 20200328
- Add support for up to four MQTT GroupTopics using the same optional Device Group names (#8014)
- Add console command history (#7483, #8015)
### 8.2.0.1 20200321
- Change HM-10 sensor type detection and add features (#7962)
- Fix possible Relay toggle on (OTA) restart
- Fix Zigbee sending wrong Sat value with Hue emulation
- Add Zigbee command ``ZbRestore`` to restore device configuration dumped with ``ZbStatus 2``
- Add Zigbee command ``ZbUnbind``
- Add support for unreachable (unplugged) Zigbee devices in Philips Hue emulation and Alexa
- Add support for 64x48 SSD1306 OLED (#6740)
### 8.2.0 20200321
- Release
- Release Elliot
### 8.1.0.11 20200313
@ -127,7 +158,7 @@
### 8.1.0 20191225
- Release
- Release Doris
### 8.0.0.3 20191224
@ -153,7 +184,7 @@
### 7.2.0 20191221
- Release
- Release Constance
- Change basic version string to lite (#7291)
- Fix Arduino IDE compile error (#7277)
- Fix restore ShutterAccuracy, MqttLog, WifiConfig, WifiPower and SerialConfig (#7281)
@ -236,7 +267,7 @@
### 7.1.0 20191129
- Release
- Release Doris
### 7.0.0.6 20191122
@ -305,7 +336,7 @@
### 6.7.1 20191026
- Release
- Release Allison
- Fix on energy monitoring devices using PowerDelta Exception0 with epc1:0x4000dce5 = Divide by zero (#6750)
- Fix Script array bug (#6751)

View File

@ -196,6 +196,7 @@ void initPins(void) {
U0IE = 0;
U1IE = 0;
/*
for (int i = 0; i <= 5; ++i) {
pinMode(i, INPUT);
}
@ -203,6 +204,7 @@ void initPins(void) {
for (int i = 12; i <= 16; ++i) {
pinMode(i, INPUT);
}
*/
ETS_GPIO_INTR_ATTACH(interrupt_handler, &interrupt_reg);
ETS_GPIO_INTR_ENABLE();

View File

@ -505,6 +505,10 @@
#define D_JSON_ZIGBEE_RECEIVED "ZbReceived"
#define D_CMND_ZIGBEE_BIND "Bind"
#define D_JSON_ZIGBEE_BIND "ZbBind"
#define D_CMND_ZIGBEE_UNBIND "Unbind"
#define D_JSON_ZIGBEE_UNBIND "ZbUnbind"
#define D_CMND_ZIGBEE_BIND_STATE "BindState"
#define D_JSON_ZIGBEE_BIND_STATE "ZbBindState"
#define D_CMND_ZIGBEE_PING "Ping"
#define D_JSON_ZIGBEE_PING "ZbPing"
#define D_JSON_ZIGBEE_IEEE "IEEEAddr"
@ -514,6 +518,7 @@
#define D_JSON_ZIGBEE_STATUS_MSG "StatusMessage"
#define D_CMND_ZIGBEE_LIGHT "Light"
#define D_JSON_ZIGBEE_LIGHT "Light"
#define D_CMND_ZIGBEE_RESTORE "Restore"
// Commands xdrv_25_A4988_Stepper.ino
#define D_CMND_MOTOR "MOTOR"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,336 @@
/* Flash Split for 1M chips */
/* sketch @0x40200000 (~999KB) (1023984B) */
/* empty @0x402F9FF0 (~4KB) (4112B) */
/* spiffs @0x402FB000 (~0KB) (0B) */
/* eeprom @0x402FB000 (4KB) */
/* rfcal @0x402FC000 (4KB) */
/* wifi @0x402FD000 (12KB) */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0xf9ff0
}
PROVIDE ( _FS_start = 0x402FB000 );
PROVIDE ( _FS_end = 0x402FB000 );
PROVIDE ( _FS_page = 0x0 );
PROVIDE ( _FS_block = 0x0 );
PROVIDE ( _EEPROM_start = 0x402fb000 );
/* The following symbols are DEPRECATED and will be REMOVED in a future release */
PROVIDE ( _SPIFFS_start = 0x402FB000 );
PROVIDE ( _SPIFFS_end = 0x402FB000 );
PROVIDE ( _SPIFFS_page = 0x0 );
PROVIDE ( _SPIFFS_block = 0x0 );
/* This linker script generated from xt-genldscripts.tpp for LSP . */
/* Linker Script for ld -N */
PHDRS
{
dport0_0_phdr PT_LOAD;
dram0_0_phdr PT_LOAD;
dram0_0_bss_phdr PT_LOAD;
iram1_0_phdr PT_LOAD;
irom0_0_phdr PT_LOAD;
}
/* Default entry point: */
ENTRY(app_entry)
EXTERN(_DebugExceptionVector)
EXTERN(_DoubleExceptionVector)
EXTERN(_KernelExceptionVector)
EXTERN(_NMIExceptionVector)
EXTERN(_UserExceptionVector)
EXTERN(core_version)
PROVIDE(_memmap_vecbase_reset = 0x40000000);
/* Various memory-map dependent cache attribute settings: */
_memmap_cacheattr_wb_base = 0x00000110;
_memmap_cacheattr_wt_base = 0x00000110;
_memmap_cacheattr_bp_base = 0x00000220;
_memmap_cacheattr_unused_mask = 0xFFFFF00F;
_memmap_cacheattr_wb_trapnull = 0x2222211F;
_memmap_cacheattr_wba_trapnull = 0x2222211F;
_memmap_cacheattr_wbna_trapnull = 0x2222211F;
_memmap_cacheattr_wt_trapnull = 0x2222211F;
_memmap_cacheattr_bp_trapnull = 0x2222222F;
_memmap_cacheattr_wb_strict = 0xFFFFF11F;
_memmap_cacheattr_wt_strict = 0xFFFFF11F;
_memmap_cacheattr_bp_strict = 0xFFFFF22F;
_memmap_cacheattr_wb_allvalid = 0x22222112;
_memmap_cacheattr_wt_allvalid = 0x22222112;
_memmap_cacheattr_bp_allvalid = 0x22222222;
PROVIDE(_memmap_cacheattr_reset = _memmap_cacheattr_wb_trapnull);
SECTIONS
{
.dport0.rodata : ALIGN(4)
{
_dport0_rodata_start = ABSOLUTE(.);
*(.dport0.rodata)
*(.dport.rodata)
_dport0_rodata_end = ABSOLUTE(.);
} >dport0_0_seg :dport0_0_phdr
.dport0.literal : ALIGN(4)
{
_dport0_literal_start = ABSOLUTE(.);
*(.dport0.literal)
*(.dport.literal)
_dport0_literal_end = ABSOLUTE(.);
} >dport0_0_seg :dport0_0_phdr
.dport0.data : ALIGN(4)
{
_dport0_data_start = ABSOLUTE(.);
*(.dport0.data)
*(.dport.data)
_dport0_data_end = ABSOLUTE(.);
} >dport0_0_seg :dport0_0_phdr
.data : ALIGN(4)
{
_data_start = ABSOLUTE(.);
*(.data)
*(.data.*)
*(.gnu.linkonce.d.*)
*(.data1)
*(.sdata)
*(.sdata.*)
*(.gnu.linkonce.s.*)
*(.sdata2)
*(.sdata2.*)
*(.gnu.linkonce.s2.*)
*(.jcr)
. = ALIGN(4);
_Pri_3_HandlerAddress = ABSOLUTE(.);
_data_end = ABSOLUTE(.);
} >dram0_0_seg :dram0_0_phdr
.noinit : ALIGN(4)
{
*(.noinit)
} >dram0_0_seg :dram0_0_phdr
/* IRAM is split into .text and .text1 to allow for moving specific */
/* functions into IRAM that would be matched by the irom0.text matcher */
.text : ALIGN(4)
{
_stext = .;
_text_start = ABSOLUTE(.);
*(.UserEnter.text)
. = ALIGN(16);
*(.DebugExceptionVector.text)
. = ALIGN(16);
*(.NMIExceptionVector.text)
. = ALIGN(16);
*(.KernelExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN(16);
*(.UserExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN(16);
*(.DoubleExceptionVector.text)
LONG(0)
LONG(0)
LONG(0)
LONG(0)
. = ALIGN (16);
*(.entry.text)
*(.init.literal)
*(.init)
*(.text.app_entry*) /* The main startup code */
*(.text.gdbstub*, .text.gdb_init) /* Any GDB hooks */
/* all functional callers are placed in IRAM (including SPI/IRQ callbacks/etc) here */
*(.text._ZNKSt8functionIF*EE*) /* std::function<any(...)>::operator()() const */
} >iram1_0_seg :iram1_0_phdr
.irom0.text : ALIGN(4)
{
_irom0_text_start = ABSOLUTE(.);
*(.ver_number)
*.c.o(.literal*, .text*)
*.cpp.o(EXCLUDE_FILE (umm_malloc.cpp.o) .literal*, EXCLUDE_FILE (umm_malloc.cpp.o) .text*)
*.cc.o(.literal*, .text*)
/* #ifdef VTABLES_IN_FLASH */
*(.rodata._ZTV*) /* C++ vtables */
/* #endif */
*libgcc.a:unwind-dw2.o(.literal .text .rodata .literal.* .text.* .rodata.*)
*libgcc.a:unwind-dw2-fde.o(.literal .text .rodata .literal.* .text.* .rodata.*)
*libc.a:(.literal .text .literal.* .text.*)
*libm.a:(.literal .text .literal.* .text.*)
/* #ifdef FP_IN_IROM */
*libgcc.a:*f2.o(.literal .text)
*libgcc.a:*f3.o(.literal .text)
*libgcc.a:*fsi.o(.literal .text)
*libgcc.a:*fdi.o(.literal .text)
*libgcc.a:*ifs.o(.literal .text)
*libgcc.a:*idf.o(.literal .text)
/* #endif */
*libgcc.a:_umoddi3.o(.literal .text)
*libgcc.a:_udivdi3.o(.literal .text)
*libstdc++.a:( .literal .text .literal.* .text.*)
*libstdc++-exc.a:( .literal .text .literal.* .text.*)
*libsmartconfig.a:(.literal .text .literal.* .text.*)
*liblwip_gcc.a:(.literal .text .literal.* .text.*)
*liblwip_src.a:(.literal .text .literal.* .text.*)
*liblwip2-536.a:(.literal .text .literal.* .text.*)
*liblwip2-1460.a:(.literal .text .literal.* .text.*)
*liblwip2-536-feat.a:(.literal .text .literal.* .text.*)
*liblwip2-1460-feat.a:(.literal .text .literal.* .text.*)
*liblwip6-536-feat.a:(.literal .text .literal.* .text.*)
*liblwip6-1460-feat.a:(.literal .text .literal.* .text.*)
*libbearssl.a:(.literal .text .literal.* .text.*)
*libaxtls.a:(.literal .text .literal.* .text.*)
*libat.a:(.literal.* .text.*)
*libcrypto.a:(.literal.* .text.*)
*libespnow.a:(.literal.* .text.*)
*libjson.a:(.literal.* .text.*)
*liblwip.a:(.literal.* .text.*)
*libmesh.a:(.literal.* .text.*)
*libnet80211.a:(.literal.* .text.*)
*libsmartconfig.a:(.literal.* .text.*)
*libssl.a:(.literal.* .text.*)
*libupgrade.a:(.literal.* .text.*)
*libwpa.a:(.literal.* .text.*)
*libwpa2.a:(.literal.* .text.*)
*libwps.a:(.literal.* .text.*)
*(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom0.text.* .irom.text .irom.text.*)
/* Constant strings in flash (PSTRs) */
*(.irom0.pstr.*)
/* __FUNCTION__ locals */
*(.rodata._ZZ*__FUNCTION__)
*(.rodata._ZZ*__PRETTY_FUNCTION__)
*(.rodata._ZZ*__func__)
/* std::* exception strings, in their own section to allow string coalescing */
*(.irom.exceptiontext)
/* c++ typeof IDs, etc. */
*(.rodata._ZTIN* .rodata._ZTSN10* .rodata._ZTISt* .rodata._ZTSSt*)
/* Fundamental type info */
*(.rodata._ZTIPKc .rodata._ZTIc .rodata._ZTIv .rodata._ZTSv .rodata._ZTSc .rodata._ZTSPKc .rodata._ZTSi .rodata._ZTIi)
. = ALIGN(4);
*(.gcc_except_table .gcc_except_table.*)
. = ALIGN(4);
__eh_frame = ABSOLUTE(.);
KEEP(*(.eh_frame))
. = (. + 7) & ~ 3; /* Add a 0 entry to terminate the list */
_irom0_text_end = ABSOLUTE(.);
_flash_code_end = ABSOLUTE(.);
} >irom0_0_seg :irom0_0_phdr
.text1 : ALIGN(4)
{
*(.literal .text .iram.literal .iram.text .iram.text.* .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.fini.literal)
*(.fini)
*(.gnu.version)
_text_end = ABSOLUTE(.);
_etext = .;
} >iram1_0_seg :iram1_0_phdr
.rodata : ALIGN(4)
{
_rodata_start = ABSOLUTE(.);
*(.sdk.version)
*(.rodata)
*(.rodata.*)
*(.gnu.linkonce.r.*)
*(.rodata1)
__XT_EXCEPTION_TABLE__ = ABSOLUTE(.);
*(.xt_except_table)
*(.gcc_except_table)
*(.gnu.linkonce.e.*)
*(.gnu.version_r)
*(.eh_frame)
. = (. + 3) & ~ 3;
/* C++ constructor and destructor tables, properly ordered: */
__init_array_start = ABSOLUTE(.);
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
__init_array_end = ABSOLUTE(.);
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
/* C++ exception handlers table: */
__XT_EXCEPTION_DESCS__ = ABSOLUTE(.);
*(.xt_except_desc)
*(.gnu.linkonce.h.*)
__XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.);
*(.xt_except_desc_end)
*(.dynamic)
*(.gnu.version_d)
. = ALIGN(4); /* this table MUST be 4-byte aligned */
_bss_table_start = ABSOLUTE(.);
LONG(_bss_start)
LONG(_bss_end)
_bss_table_end = ABSOLUTE(.);
_rodata_end = ABSOLUTE(.);
} >dram0_0_seg :dram0_0_phdr
.bss ALIGN(8) (NOLOAD) : ALIGN(4)
{
. = ALIGN (8);
_bss_start = ABSOLUTE(.);
*(.dynsbss)
*(.sbss)
*(.sbss.*)
*(.gnu.linkonce.sb.*)
*(.scommon)
*(.sbss2)
*(.sbss2.*)
*(.gnu.linkonce.sb2.*)
*(.dynbss)
*(.bss)
*(.bss.*)
*(.gnu.linkonce.b.*)
*(COMMON)
. = ALIGN (8);
_bss_end = ABSOLUTE(.);
_heap_start = ABSOLUTE(.);
/* _stack_sentry = ALIGN(0x8); */
} >dram0_0_seg :dram0_0_bss_phdr
/* __stack = 0x3ffc8000; */
.lit4 : ALIGN(4)
{
_lit4_start = ABSOLUTE(.);
*(*.lit4)
*(.lit4.*)
*(.gnu.linkonce.lit4.*)
_lit4_end = ABSOLUTE(.);
} >iram1_0_seg :iram1_0_phdr
}
/* get ROM code address */
INCLUDE "../ld/eagle.rom.addr.v6.ld"

View File

@ -0,0 +1,352 @@
PROVIDE ( Cache_Read_Disable = 0x400047f0 );
PROVIDE ( Cache_Read_Enable = 0x40004678 );
PROVIDE ( FilePacketSendReqMsgProc = 0x400035a0 );
PROVIDE ( FlashDwnLdParamCfgMsgProc = 0x4000368c );
PROVIDE ( FlashDwnLdStartMsgProc = 0x40003538 );
PROVIDE ( FlashDwnLdStopReqMsgProc = 0x40003658 );
PROVIDE ( GetUartDevice = 0x40003f4c );
PROVIDE ( MD5Final = 0x40009900 );
PROVIDE ( MD5Init = 0x40009818 );
PROVIDE ( MD5Update = 0x40009834 );
PROVIDE ( MemDwnLdStartMsgProc = 0x400036c4 );
PROVIDE ( MemDwnLdStopReqMsgProc = 0x4000377c );
PROVIDE ( MemPacketSendReqMsgProc = 0x400036f0 );
PROVIDE ( RcvMsg = 0x40003eac );
PROVIDE ( SHA1Final = 0x4000b648 );
PROVIDE ( SHA1Init = 0x4000b584 );
PROVIDE ( SHA1Transform = 0x4000a364 );
PROVIDE ( SHA1Update = 0x4000b5a8 );
PROVIDE ( SPI_read_status = 0x400043c8 );
PROVIDE ( SPI_write_status = 0x40004400 );
PROVIDE ( SPI_write_enable = 0x4000443c );
PROVIDE ( Wait_SPI_Idle = 0x4000448c );
PROVIDE ( Enable_QMode = 0x400044c0 );
PROVIDE ( SPIEraseArea = 0x40004b44 );
PROVIDE ( SPIEraseBlock = 0x400049b4 );
PROVIDE ( SPIEraseChip = 0x40004984 );
PROVIDE ( SPIEraseSector = 0x40004a00 );
PROVIDE ( SPILock = 0x400048a8 );
PROVIDE ( SPIParamCfg = 0x40004c2c );
PROVIDE ( SPIRead = 0x40004b1c );
PROVIDE ( SPIReadModeCnfig = 0x400048ec );
PROVIDE ( SPIUnlock = 0x40004878 );
PROVIDE ( SPIWrite = 0x40004a4c );
PROVIDE ( SelectSpiFunction = 0x40003f58 );
PROVIDE ( SendMsg = 0x40003cf4 );
PROVIDE ( UartConnCheck = 0x40003230 );
PROVIDE ( UartConnectProc = 0x400037a0 );
PROVIDE ( UartDwnLdProc = 0x40003368 );
PROVIDE ( UartGetCmdLn = 0x40003ef4 );
PROVIDE ( UartRegReadProc = 0x4000381c );
PROVIDE ( UartRegWriteProc = 0x400037ac );
PROVIDE ( UartRxString = 0x40003c30 );
PROVIDE ( Uart_Init = 0x40003a14 );
PROVIDE ( _ResetHandler = 0x400000a4 );
PROVIDE ( _ResetVector = 0x40000080 );
PROVIDE ( __adddf3 = 0x4000c538 );
PROVIDE ( __addsf3 = 0x4000c180 );
PROVIDE ( __divdf3 = 0x4000cb94 );
PROVIDE ( __divdi3 = 0x4000ce60 );
PROVIDE ( __divsi3 = 0x4000dc88 );
PROVIDE ( __extendsfdf2 = 0x4000cdfc );
PROVIDE ( __fixdfsi = 0x4000ccb8 );
PROVIDE ( __fixunsdfsi = 0x4000cd00 );
PROVIDE ( __fixunssfsi = 0x4000c4c4 );
PROVIDE ( __floatsidf = 0x4000e2f0 );
PROVIDE ( __floatsisf = 0x4000e2ac );
PROVIDE ( __floatunsidf = 0x4000e2e8 );
PROVIDE ( __floatunsisf = 0x4000e2a4 );
PROVIDE ( __muldf3 = 0x4000c8f0 );
PROVIDE ( __muldi3 = 0x40000650 );
PROVIDE ( __mulsf3 = 0x4000c3dc );
PROVIDE ( __subdf3 = 0x4000c688 );
PROVIDE ( __subsf3 = 0x4000c268 );
PROVIDE ( __truncdfsf2 = 0x4000cd5c );
PROVIDE ( __udivdi3 = 0x4000d310 );
PROVIDE ( __udivsi3 = 0x4000e21c );
PROVIDE ( __umoddi3 = 0x4000d770 );
PROVIDE ( __umodsi3 = 0x4000e268 );
PROVIDE ( __umulsidi3 = 0x4000dcf0 );
PROVIDE ( _rom_store = 0x4000e388 );
PROVIDE ( _rom_store_table = 0x4000e328 );
PROVIDE ( _start = 0x4000042c );
PROVIDE ( _xtos_alloca_handler = 0x4000dbe0 );
PROVIDE ( _xtos_c_wrapper_handler = 0x40000598 );
PROVIDE ( _xtos_cause3_handler = 0x40000590 );
PROVIDE ( _xtos_ints_off = 0x4000bda4 );
PROVIDE ( _xtos_ints_on = 0x4000bd84 );
PROVIDE ( _xtos_l1int_handler = 0x4000048c );
PROVIDE ( _xtos_p_none = 0x4000dbf8 );
PROVIDE ( _xtos_restore_intlevel = 0x4000056c );
PROVIDE ( _xtos_return_from_exc = 0x4000dc54 );
PROVIDE ( _xtos_set_exception_handler = 0x40000454 );
PROVIDE ( _xtos_set_interrupt_handler = 0x4000bd70 );
PROVIDE ( _xtos_set_interrupt_handler_arg = 0x4000bd28 );
PROVIDE ( _xtos_set_intlevel = 0x4000dbfc );
PROVIDE ( _xtos_set_min_intlevel = 0x4000dc18 );
PROVIDE ( _xtos_set_vpri = 0x40000574 );
PROVIDE ( _xtos_syscall_handler = 0x4000dbe4 );
PROVIDE ( _xtos_unhandled_exception = 0x4000dc44 );
PROVIDE ( _xtos_unhandled_interrupt = 0x4000dc3c );
PROVIDE ( aes_decrypt = 0x400092d4 );
PROVIDE ( aes_decrypt_deinit = 0x400092e4 );
PROVIDE ( aes_decrypt_init = 0x40008ea4 );
PROVIDE ( aes_unwrap = 0x40009410 );
PROVIDE ( base64_decode = 0x40009648 );
PROVIDE ( base64_encode = 0x400094fc );
PROVIDE ( bzero = 0x4000de84 );
PROVIDE ( cmd_parse = 0x40000814 );
PROVIDE ( conv_str_decimal = 0x40000b24 );
PROVIDE ( conv_str_hex = 0x40000cb8 );
PROVIDE ( convert_para_str = 0x40000a60 );
PROVIDE ( dtm_get_intr_mask = 0x400026d0 );
PROVIDE ( dtm_params_init = 0x4000269c );
PROVIDE ( dtm_set_intr_mask = 0x400026c8 );
PROVIDE ( dtm_set_params = 0x400026dc );
PROVIDE ( eprintf = 0x40001d14 );
PROVIDE ( eprintf_init_buf = 0x40001cb8 );
PROVIDE ( eprintf_to_host = 0x40001d48 );
PROVIDE ( est_get_printf_buf_remain_len = 0x40002494 );
PROVIDE ( est_reset_printf_buf_len = 0x4000249c );
PROVIDE ( ets_bzero = 0x40002ae8 );
PROVIDE ( ets_char2xdigit = 0x40002b74 );
PROVIDE ( ets_delay_us = 0x40002ecc );
PROVIDE ( ets_enter_sleep = 0x400027b8 );
PROVIDE ( ets_external_printf = 0x40002578 );
PROVIDE ( ets_get_cpu_frequency = 0x40002f0c );
PROVIDE ( ets_getc = 0x40002bcc );
PROVIDE ( ets_install_external_printf = 0x40002450 );
PROVIDE ( ets_install_putc1 = 0x4000242c );
PROVIDE ( ets_install_putc2 = 0x4000248c );
PROVIDE ( ets_install_uart_printf = 0x40002438 );
/* Undocumented function to print character to UART */
PROVIDE ( ets_uart_putc1 = 0x40001dcc );
/* permanently hide reimplemented ets_intr_*lock(), see #6484
PROVIDE ( ets_intr_lock = 0x40000f74 );
PROVIDE ( ets_intr_unlock = 0x40000f80 );
*/
PROVIDE ( ets_isr_attach = 0x40000f88 );
PROVIDE ( ets_isr_mask = 0x40000f98 );
PROVIDE ( ets_isr_unmask = 0x40000fa8 );
PROVIDE ( ets_memcmp = 0x400018d4 );
PROVIDE ( ets_memcpy = 0x400018b4 );
PROVIDE ( ets_memmove = 0x400018c4 );
PROVIDE ( ets_memset = 0x400018a4 );
/* renamed to ets_post_rom(), see #6484
PROVIDE ( ets_post = 0x40000e24 );
*/
PROVIDE ( ets_post_rom = 0x40000e24 );
PROVIDE ( ets_printf = 0x400024cc );
PROVIDE ( ets_putc = 0x40002be8 );
PROVIDE ( ets_rtc_int_register = 0x40002a40 );
PROVIDE ( ets_run = 0x40000e04 );
PROVIDE ( ets_set_idle_cb = 0x40000dc0 );
PROVIDE ( ets_set_user_start = 0x40000fbc );
PROVIDE ( ets_str2macaddr = 0x40002af8 );
PROVIDE ( ets_strcmp = 0x40002aa8 );
PROVIDE ( ets_strcpy = 0x40002a88 );
PROVIDE ( ets_strlen = 0x40002ac8 );
PROVIDE ( ets_strncmp = 0x40002ab8 );
PROVIDE ( ets_strncpy = 0x40002a98 );
PROVIDE ( ets_strstr = 0x40002ad8 );
PROVIDE ( ets_task = 0x40000dd0 );
PROVIDE ( ets_timer_arm = 0x40002cc4 );
PROVIDE ( ets_timer_disarm = 0x40002d40 );
PROVIDE ( ets_timer_done = 0x40002d80 );
PROVIDE ( ets_timer_handler_isr = 0x40002da8 );
PROVIDE ( ets_timer_init = 0x40002e68 );
PROVIDE ( ets_timer_setfn = 0x40002c48 );
PROVIDE ( ets_uart_printf = 0x40002544 );
PROVIDE ( ets_update_cpu_frequency = 0x40002f04 );
PROVIDE ( ets_vprintf = 0x40001f00 );
PROVIDE ( ets_wdt_disable = 0x400030f0 );
PROVIDE ( ets_wdt_enable = 0x40002fa0 );
PROVIDE ( ets_wdt_get_mode = 0x40002f34 );
PROVIDE ( ets_wdt_init = 0x40003170 );
PROVIDE ( ets_wdt_restore = 0x40003158 );
PROVIDE ( ets_write_char = 0x40001da0 );
PROVIDE ( get_first_seg = 0x4000091c );
PROVIDE ( gpio_init = 0x40004c50 );
PROVIDE ( gpio_input_get = 0x40004cf0 );
PROVIDE ( gpio_intr_ack = 0x40004dcc );
PROVIDE ( gpio_intr_handler_register = 0x40004e28 );
PROVIDE ( gpio_intr_pending = 0x40004d88 );
PROVIDE ( gpio_intr_test = 0x40004efc );
PROVIDE ( gpio_output_set = 0x40004cd0 );
PROVIDE ( gpio_pin_intr_state_set = 0x40004d90 );
PROVIDE ( gpio_pin_wakeup_disable = 0x40004ed4 );
PROVIDE ( gpio_pin_wakeup_enable = 0x40004e90 );
PROVIDE ( gpio_register_get = 0x40004d5c );
PROVIDE ( gpio_register_set = 0x40004d04 );
PROVIDE ( hmac_md5 = 0x4000a2cc );
PROVIDE ( hmac_md5_vector = 0x4000a160 );
PROVIDE ( hmac_sha1 = 0x4000ba28 );
PROVIDE ( hmac_sha1_vector = 0x4000b8b4 );
PROVIDE ( lldesc_build_chain = 0x40004f40 );
PROVIDE ( lldesc_num2link = 0x40005050 );
PROVIDE ( lldesc_set_owner = 0x4000507c );
PROVIDE ( main = 0x40000fec );
PROVIDE ( md5_vector = 0x400097ac );
PROVIDE ( mem_calloc = 0x40001c2c );
PROVIDE ( mem_free = 0x400019e0 );
PROVIDE ( mem_init = 0x40001998 );
PROVIDE ( mem_malloc = 0x40001b40 );
PROVIDE ( mem_realloc = 0x40001c6c );
PROVIDE ( mem_trim = 0x40001a14 );
PROVIDE ( mem_zalloc = 0x40001c58 );
PROVIDE ( memcmp = 0x4000dea8 );
PROVIDE ( memcpy = 0x4000df48 );
PROVIDE ( memmove = 0x4000e04c );
PROVIDE ( memset = 0x4000e190 );
PROVIDE ( multofup = 0x400031c0 );
PROVIDE ( pbkdf2_sha1 = 0x4000b840 );
PROVIDE ( phy_get_romfuncs = 0x40006b08 );
PROVIDE ( rand = 0x40000600 );
PROVIDE ( rc4_skip = 0x4000dd68 );
PROVIDE ( recv_packet = 0x40003d08 );
PROVIDE ( remove_head_space = 0x40000a04 );
PROVIDE ( rijndaelKeySetupDec = 0x40008dd0 );
PROVIDE ( rijndaelKeySetupEnc = 0x40009300 );
PROVIDE ( rom_abs_temp = 0x400060c0 );
PROVIDE ( rom_ana_inf_gating_en = 0x40006b10 );
PROVIDE ( rom_cal_tos_v50 = 0x40007a28 );
PROVIDE ( rom_chip_50_set_channel = 0x40006f84 );
PROVIDE ( rom_chip_v5_disable_cca = 0x400060d0 );
PROVIDE ( rom_chip_v5_enable_cca = 0x400060ec );
PROVIDE ( rom_chip_v5_rx_init = 0x4000711c );
PROVIDE ( rom_chip_v5_sense_backoff = 0x4000610c );
PROVIDE ( rom_chip_v5_tx_init = 0x4000718c );
PROVIDE ( rom_dc_iq_est = 0x4000615c );
PROVIDE ( rom_en_pwdet = 0x400061b8 );
PROVIDE ( rom_get_bb_atten = 0x40006238 );
PROVIDE ( rom_get_corr_power = 0x40006260 );
PROVIDE ( rom_get_fm_sar_dout = 0x400062dc );
PROVIDE ( rom_get_noisefloor = 0x40006394 );
PROVIDE ( rom_get_power_db = 0x400063b0 );
PROVIDE ( rom_i2c_readReg = 0x40007268 );
PROVIDE ( rom_i2c_readReg_Mask = 0x4000729c );
PROVIDE ( rom_i2c_writeReg = 0x400072d8 );
PROVIDE ( rom_i2c_writeReg_Mask = 0x4000730c );
PROVIDE ( rom_iq_est_disable = 0x40006400 );
PROVIDE ( rom_iq_est_enable = 0x40006430 );
PROVIDE ( rom_linear_to_db = 0x40006484 );
PROVIDE ( rom_mhz2ieee = 0x400065a4 );
PROVIDE ( rom_pbus_dco___SA2 = 0x40007bf0 );
PROVIDE ( rom_pbus_debugmode = 0x4000737c );
PROVIDE ( rom_pbus_enter_debugmode = 0x40007410 );
PROVIDE ( rom_pbus_exit_debugmode = 0x40007448 );
PROVIDE ( rom_pbus_force_test = 0x4000747c );
PROVIDE ( rom_pbus_rd = 0x400074d8 );
PROVIDE ( rom_pbus_set_rxgain = 0x4000754c );
PROVIDE ( rom_pbus_set_txgain = 0x40007610 );
PROVIDE ( rom_pbus_workmode = 0x40007648 );
PROVIDE ( rom_pbus_xpd_rx_off = 0x40007688 );
PROVIDE ( rom_pbus_xpd_rx_on = 0x400076cc );
PROVIDE ( rom_pbus_xpd_tx_off = 0x400076fc );
PROVIDE ( rom_pbus_xpd_tx_on = 0x40007740 );
PROVIDE ( rom_pbus_xpd_tx_on__low_gain = 0x400077a0 );
PROVIDE ( rom_phy_reset_req = 0x40007804 );
PROVIDE ( rom_restart_cal = 0x4000781c );
PROVIDE ( rom_rfcal_pwrctrl = 0x40007eb4 );
PROVIDE ( rom_rfcal_rxiq = 0x4000804c );
PROVIDE ( rom_rfcal_rxiq_set_reg = 0x40008264 );
PROVIDE ( rom_rfcal_txcap = 0x40008388 );
PROVIDE ( rom_rfcal_txiq = 0x40008610 );
PROVIDE ( rom_rfcal_txiq_cover = 0x400088b8 );
PROVIDE ( rom_rfcal_txiq_set_reg = 0x40008a70 );
PROVIDE ( rom_rfpll_reset = 0x40007868 );
PROVIDE ( rom_rfpll_set_freq = 0x40007968 );
PROVIDE ( rom_rxiq_cover_mg_mp = 0x40008b6c );
PROVIDE ( rom_rxiq_get_mis = 0x40006628 );
PROVIDE ( rom_sar_init = 0x40006738 );
PROVIDE ( rom_set_ana_inf_tx_scale = 0x4000678c );
PROVIDE ( rom_set_channel_freq = 0x40006c50 );
PROVIDE ( rom_set_loopback_gain = 0x400067c8 );
PROVIDE ( rom_set_noise_floor = 0x40006830 );
PROVIDE ( rom_set_rxclk_en = 0x40006550 );
PROVIDE ( rom_set_txbb_atten = 0x40008c6c );
PROVIDE ( rom_set_txclk_en = 0x4000650c );
PROVIDE ( rom_set_txiq_cal = 0x40008d34 );
PROVIDE ( rom_start_noisefloor = 0x40006874 );
PROVIDE ( rom_start_tx_tone = 0x400068b4 );
PROVIDE ( rom_stop_tx_tone = 0x4000698c );
PROVIDE ( rom_tx_mac_disable = 0x40006a98 );
PROVIDE ( rom_tx_mac_enable = 0x40006ad4 );
PROVIDE ( rom_txtone_linear_pwr = 0x40006a1c );
PROVIDE ( rom_write_rfpll_sdm = 0x400078dc );
PROVIDE ( roundup2 = 0x400031b4 );
PROVIDE ( rtc_enter_sleep = 0x40002870 );
PROVIDE ( rtc_get_reset_reason = 0x400025e0 );
PROVIDE ( rtc_intr_handler = 0x400029ec );
PROVIDE ( rtc_set_sleep_mode = 0x40002668 );
PROVIDE ( save_rxbcn_mactime = 0x400027a4 );
PROVIDE ( save_tsf_us = 0x400027ac );
PROVIDE ( send_packet = 0x40003c80 );
PROVIDE ( sha1_prf = 0x4000ba48 );
PROVIDE ( sha1_vector = 0x4000a2ec );
PROVIDE ( sip_alloc_to_host_evt = 0x40005180 );
PROVIDE ( sip_get_ptr = 0x400058a8 );
PROVIDE ( sip_get_state = 0x40005668 );
PROVIDE ( sip_init_attach = 0x4000567c );
PROVIDE ( sip_install_rx_ctrl_cb = 0x4000544c );
PROVIDE ( sip_install_rx_data_cb = 0x4000545c );
PROVIDE ( sip_post = 0x400050fc );
PROVIDE ( sip_post_init = 0x400056c4 );
PROVIDE ( sip_reclaim_from_host_cmd = 0x4000534c );
PROVIDE ( sip_reclaim_tx_data_pkt = 0x400052c0 );
PROVIDE ( sip_send = 0x40005808 );
PROVIDE ( sip_to_host_chain_append = 0x40005864 );
PROVIDE ( sip_to_host_evt_send_done = 0x40005234 );
PROVIDE ( slc_add_credits = 0x400060ac );
PROVIDE ( slc_enable = 0x40005d90 );
PROVIDE ( slc_from_host_chain_fetch = 0x40005f24 );
PROVIDE ( slc_from_host_chain_recycle = 0x40005e94 );
PROVIDE ( slc_init_attach = 0x40005c50 );
PROVIDE ( slc_init_credit = 0x4000608c );
PROVIDE ( slc_pause_from_host = 0x40006014 );
PROVIDE ( slc_reattach = 0x40005c1c );
PROVIDE ( slc_resume_from_host = 0x4000603c );
PROVIDE ( slc_select_tohost_gpio = 0x40005dc0 );
PROVIDE ( slc_select_tohost_gpio_mode = 0x40005db8 );
PROVIDE ( slc_send_to_host_chain = 0x40005de4 );
PROVIDE ( slc_set_host_io_max_window = 0x40006068 );
PROVIDE ( slc_to_host_chain_recycle = 0x40005f10 );
PROVIDE ( software_reset = 0x4000264c );
PROVIDE ( spi_flash_attach = 0x40004644 );
PROVIDE ( srand = 0x400005f0 );
PROVIDE ( strcmp = 0x4000bdc8 );
PROVIDE ( strcpy = 0x4000bec8 );
PROVIDE ( strlen = 0x4000bf4c );
PROVIDE ( strncmp = 0x4000bfa8 );
PROVIDE ( strncpy = 0x4000c0a0 );
PROVIDE ( strstr = 0x4000e1e0 );
PROVIDE ( timer_insert = 0x40002c64 );
PROVIDE ( uartAttach = 0x4000383c );
PROVIDE ( uart_baudrate_detect = 0x40003924 );
PROVIDE ( uart_buff_switch = 0x400038a4 );
PROVIDE ( uart_rx_intr_handler = 0x40003bbc );
PROVIDE ( uart_rx_one_char = 0x40003b8c );
PROVIDE ( uart_rx_one_char_block = 0x40003b64 );
PROVIDE ( uart_rx_readbuff = 0x40003ec8 );
PROVIDE ( uart_tx_one_char = 0x40003b30 );
PROVIDE ( wepkey_128 = 0x4000bc40 );
PROVIDE ( wepkey_64 = 0x4000bb3c );
PROVIDE ( xthal_bcopy = 0x40000688 );
PROVIDE ( xthal_copy123 = 0x4000074c );
PROVIDE ( xthal_get_ccompare = 0x4000dd4c );
PROVIDE ( xthal_get_ccount = 0x4000dd38 );
PROVIDE ( xthal_get_interrupt = 0x4000dd58 );
PROVIDE ( xthal_get_intread = 0x4000dd58 );
PROVIDE ( xthal_memcpy = 0x400006c4 );
PROVIDE ( xthal_set_ccompare = 0x4000dd40 );
PROVIDE ( xthal_set_intclear = 0x4000dd60 );
PROVIDE ( xthal_spill_registers_into_stack_nw = 0x4000e320 );
PROVIDE ( xthal_window_spill = 0x4000e324 );
PROVIDE ( xthal_window_spill_nw = 0x4000e320 );
PROVIDE ( Te0 = 0x3fffccf0 );
PROVIDE ( Td0 = 0x3fffd100 );
PROVIDE ( Td4s = 0x3fffd500);
PROVIDE ( rcons = 0x3fffd0f0);
PROVIDE ( UartDev = 0x3fffde10 );
PROVIDE ( flashchip = 0x3fffc714);

View File

@ -684,4 +684,8 @@
#error "Select either USE_DISCOVERY or USE_MQTT_AWS_IOT, mDNS takes too much code space and is not needed for AWS IoT"
#endif
#if defined(USE_RULES) && defined(USE_SCRIPT)
#error "Select either USE_RULES or USE_SCRIPT. They can't both be used at the same time"
#endif
#endif // _MY_USER_CONFIG_H_

View File

@ -109,7 +109,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t powered_off_led : 1; // bit 5 (v8.1.0.9) - SetOption87 - PWM Dimmer Turn red LED on when powered off
uint32_t remote_device_mode : 1; // bit 6 (v8.1.0.9) - SetOption88 - PWM Dimmer Buttons control remote devices
uint32_t zigbee_distinct_topics : 1; // bit 7 (v8.1.0.10) - SetOption89 - Distinct MQTT topics per device for Zigbee (#7835)
uint32_t spare08 : 1;
uint32_t only_json_message : 1; // bit 8 (v8.2.0.3) - SetOption90 - Disable non-json MQTT response
uint32_t spare09 : 1;
uint32_t spare10 : 1;
uint32_t spare11 : 1;
@ -205,8 +205,7 @@ typedef union {
uint8_t spare1 : 1;
uint8_t spare2 : 1;
uint8_t spare3 : 1;
uint8_t spare4 : 1;
uint8_t spare5 : 1;
uint8_t bh1750_resolution : 2; // Sensor10 1,2,3
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)
};
@ -397,7 +396,10 @@ struct SYSCFG {
uint16_t mcp230xx_int_timer; // 718
uint8_t rgbwwTable[5]; // 71A
uint8_t user_template_base; // 71F
mytmplt user_template; // 720 29 bytes
char user_template_name[15]; // 720 15 bytes - Backward compatibility since v8.2.0.3
mytmplt user_template; // 72F 14 bytes
uint8_t novasds_startingoffset; // 73D
uint8_t web_color[18][3]; // 73E
uint16_t display_width; // 774
@ -469,8 +471,10 @@ struct SYSCFG {
uint8_t bri_preset_high; // F07
int8_t hum_comp; // F08
uint8_t free_f09[179]; // F09
uint8_t free_f09[175]; // F09
uint16_t pulse_counter_debounce_low; // FB8
uint16_t pulse_counter_debounce_high; // FBA
uint32_t keeloq_master_msb; // FBC
uint32_t keeloq_master_lsb; // FC0
uint32_t keeloq_serial; // FC4

View File

@ -589,6 +589,12 @@ char* SettingsText(uint32_t index)
* Config Save - Save parameters to Flash ONLY if any parameter has changed
\*********************************************************************************************/
void UpdateBackwardCompatibility(void)
{
// Perform updates for backward compatibility
strlcpy(Settings.user_template_name, SettingsText(SET_TEMPLATE_NAME), sizeof(Settings.user_template_name));
}
uint32_t GetSettingsAddress(void)
{
return settings_location * SPI_FLASH_SEC_SIZE;
@ -605,6 +611,7 @@ void SettingsSave(uint8_t rotate)
* stop_flash_rotate 1 = Allow only eeprom flash slot use (SetOption12 1)
*/
#ifndef FIRMWARE_MINIMAL
UpdateBackwardCompatibility();
if ((GetSettingsCrc32() != settings_crc32) || rotate) {
if (1 == rotate) { // Use eeprom flash slot only and disable flash rotate from now on (upgrade)
stop_flash_rotate = 1;
@ -1394,7 +1401,6 @@ void SettingsDelta(void)
Settings.mqtt_port = Settings.ex_mqtt_port; // 20A -> EFC
memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5); // 1E4 -> EFE
}
if (Settings.version < 0x08000000) {
char temp[strlen(Settings.text_pool) +1]; strncpy(temp, Settings.text_pool, sizeof(temp)); // Was ota_url
char temp21[strlen(Settings.ex_mqtt_prefix[0]) +1]; strncpy(temp21, Settings.ex_mqtt_prefix[0], sizeof(temp21));
@ -1466,6 +1472,9 @@ void SettingsDelta(void)
SettingsUpdateText(SET_FRIENDLYNAME3, Settings.ex_friendlyname[2]);
SettingsUpdateText(SET_FRIENDLYNAME4, Settings.ex_friendlyname[3]);
}
if (Settings.version < 0x08020003) {
SettingsUpdateText(SET_TEMPLATE_NAME, Settings.user_template_name);
}
Settings.version = VERSION;
SettingsSave(1);

View File

@ -1098,9 +1098,10 @@ bool ValidModule(uint32_t index)
String AnyModuleName(uint32_t index)
{
if (USER_MODULE == index) {
return String(Settings.user_template.name);
return String(SettingsText(SET_TEMPLATE_NAME));
} else {
return FPSTR(kModules[index].name);
char name[TOPSZ];
return String(GetTextIndexed(name, sizeof(name), index, kModuleNames));
}
}
@ -1153,6 +1154,8 @@ void ModuleDefault(uint32_t module)
{
if (USER_MODULE == module) { module = WEMOS; } // Generic
Settings.user_template_base = module;
char name[TOPSZ];
SettingsUpdateText(SET_TEMPLATE_NAME, GetTextIndexed(name, sizeof(name), module, kModuleNames));
memcpy_P(&Settings.user_template, &kModules[module], sizeof(mytmplt));
}
@ -1261,14 +1264,14 @@ bool JsonTemplate(const char* dataBuf)
if (strlen(dataBuf) < 9) { return false; } // Workaround exception if empty JSON like {} - Needs checks
StaticJsonBuffer<350> jb; // 331 from https://arduinojson.org/v5/assistant/
StaticJsonBuffer<400> jb; // 331 from https://arduinojson.org/v5/assistant/
JsonObject& obj = jb.parseObject(dataBuf);
if (!obj.success()) { return false; }
// All parameters are optional allowing for partial changes
const char* name = obj[D_JSON_NAME];
if (name != nullptr) {
strlcpy(Settings.user_template.name, name, sizeof(Settings.user_template.name));
SettingsUpdateText(SET_TEMPLATE_NAME, name);
}
if (obj[D_JSON_GPIO].success()) {
for (uint32_t i = 0; i < sizeof(mycfgio); i++) {
@ -1289,7 +1292,7 @@ bool JsonTemplate(const char* dataBuf)
void TemplateJson(void)
{
Response_P(PSTR("{\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), Settings.user_template.name);
Response_P(PSTR("{\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), SettingsText(SET_TEMPLATE_NAME));
for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) {
ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Settings.user_template.gp.io[i]);
}

View File

@ -107,9 +107,11 @@ void ResponseCmndIdxChar(const char* value)
void ResponseCmndAll(uint32_t text_index, uint32_t count)
{
uint32_t real_index = text_index;
mqtt_data[0] = '\0';
for (uint32_t i = 0; i < count; i++) {
ResponseAppend_P(PSTR("%c\"%s%d\":\"%s\""), (i) ? ',' : '{', XdrvMailbox.command, i +1, SettingsText(text_index +i));
if ((SET_MQTT_GRP_TOPIC == text_index) && (1 == i)) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
ResponseAppend_P(PSTR("%c\"%s%d\":\"%s\""), (i) ? ',' : '{', XdrvMailbox.command, i +1, SettingsText(real_index +i));
}
ResponseJsonEnd();
}
@ -1062,7 +1064,7 @@ void CmndTemplate(void)
if (Settings.module != USER_MODULE) {
ModuleDefault(Settings.module);
}
snprintf_P(Settings.user_template.name, sizeof(Settings.user_template.name), PSTR("Merged"));
SettingsUpdateText(SET_TEMPLATE_NAME, "Merged");
uint32_t j = 0;
for (uint32_t i = 0; i < sizeof(mycfgio); i++) {
if (6 == i) { j = 9; }

View File

@ -601,9 +601,7 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length)
}
}
}
else {
XdrvCall(FUNC_DEVICE_GROUP_ITEM);
}
XdrvCall(FUNC_DEVICE_GROUP_ITEM);
}
}

View File

@ -155,3 +155,24 @@ void* memmove_P(void *dest, const void *src, size_t n)
}
#endif // ARDUINO_ESP8266_RELEASE < 2_6_0
/*********************************************************************************************\
* Core overrides
\*********************************************************************************************/
// Add below line to tasmota_post.h
// extern "C" void resetPins();
void resetPins()
{
/*
for (int i = 0; i <= 5; ++i) {
pinMode(i, INPUT);
}
// pins 6-11 are used for the SPI flash interface
for (int i = 12; i <= 16; ++i) {
pinMode(i, INPUT);
}
*/
}

View File

@ -112,7 +112,7 @@ public:
}
size_t addBuffer(const uint8_t *buf2, size_t len2) {
if (len() + len2 <= size()) {
if ((buf2) && (len() + len2 <= size())) {
for (uint32_t i = 0; i < len2; i++) {
_buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]);
}
@ -121,7 +121,7 @@ public:
}
size_t addBuffer(const char *buf2, size_t len2) {
if (len() + len2 <= size()) {
if ((buf2) && (len() + len2 <= size())) {
for (uint32_t i = 0; i < len2; i++) {
_buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]);
}

View File

@ -130,11 +130,11 @@ char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopi
return stopic;
}
char* GetGroupTopic_P(char *stopic, const char* subtopic)
char* GetGroupTopic_P(char *stopic, const char* subtopic, uint32_t itopic)
{
// SetOption75 0: %prefix%/nothing/%topic% = cmnd/nothing/<grouptopic>/#
// SetOption75 1: cmnd/<grouptopic>
return GetTopic_P(stopic, (Settings.flag3.grouptopic_mode) ? CMND +8 : CMND, SettingsText(SET_MQTT_GRP_TOPIC), subtopic); // SetOption75 - GroupTopic replaces %topic% (0) or fixed topic cmnd/grouptopic (1)
return GetTopic_P(stopic, (Settings.flag3.grouptopic_mode) ? CMND +8 : CMND, SettingsText(itopic), subtopic); // SetOption75 - GroupTopic replaces %topic% (0) or fixed topic cmnd/grouptopic (1)
}
char* GetFallbackTopic_P(char *stopic, const char* subtopic)
@ -1286,9 +1286,9 @@ void SerialInput(void)
char hex_char[(serial_in_byte_counter * 2) + 2];
bool assume_json = (!Settings.flag.mqtt_serial_raw && (serial_in_buffer[0] == '{'));
Response_P(PSTR("{\"" D_JSON_SERIALRECEIVED "\":%s%s%s}"),
(assume_json) ? "" : """",
(assume_json) ? "" : "\"",
(Settings.flag.mqtt_serial_raw) ? ToHex_P((unsigned char*)serial_in_buffer, serial_in_byte_counter, hex_char, sizeof(hex_char)) : serial_in_buffer,
(assume_json) ? "" : """");
(assume_json) ? "" : "\"");
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_SERIALRECEIVED));
XdrvRulesProcess();
serial_in_byte_counter = 0;
@ -1297,6 +1297,18 @@ void SerialInput(void)
/********************************************************************************************/
void ResetPwm(void)
{
for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only
if (pin[GPIO_PWM1 +i] < 99) {
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range : 0);
// analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - Settings.pwm_value[i] : Settings.pwm_value[i]);
}
}
}
/********************************************************************************************/
void GpioInit(void)
{
uint32_t mpin;
@ -1421,6 +1433,18 @@ void GpioInit(void)
soft_spi_flg = ((pin[GPIO_SSPI_CS] < 99) && (pin[GPIO_SSPI_SCLK] < 99) && ((pin[GPIO_SSPI_MOSI] < 99) || (pin[GPIO_SSPI_MOSI] < 99)));
#endif // USE_SPI
// Set any non-used GPIO to INPUT - Related to resetPins() in support_legacy_cores.ino
// Doing it here solves relay toggles at restart.
for (uint32_t i = 0; i < sizeof(my_module.io); i++) {
mpin = ValidPin(i, my_module.io[i]);
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("INI: gpio pin %d, mpin %d"), i, mpin);
if (((i < 6) || (i > 11)) && (0 == mpin)) { // Skip SPI flash interface
if (!((1 == i) || (3 == i))) { // Skip serial
pinMode(i, INPUT);
}
}
}
#ifdef USE_I2C
i2c_flg = ((pin[GPIO_I2C_SCL] < 99) && (pin[GPIO_I2C_SDA] < 99));
if (i2c_flg) {

View File

@ -694,6 +694,7 @@ void WifiShutdown(bool option = false)
void EspRestart(void)
{
ResetPwm();
WifiShutdown(true);
CrashDumpClear(); // Clear the stack dump in RTC
// ESP.restart(); // This results in exception 3 on restarts on core 2.3.0

View File

@ -81,6 +81,7 @@ const uint8_t MAX_NTP_SERVERS = 3; // Max number of NTP servers
const uint8_t MAX_RULE_MEMS = 16; // Max number of saved vars
const uint8_t MAX_FRIENDLYNAMES = 8; // Max number of Friendly names
const uint8_t MAX_BUTTON_TEXT = 16; // Max number of GUI button labels
const uint8_t MAX_GROUP_TOPICS = 4; // Max number of Group Topics
const uint8_t MAX_HUE_DEVICES = 15; // Max number of Philips Hue device per emulation
@ -301,6 +302,7 @@ enum SettingsTextIndex { SET_OTAURL,
SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8,
SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16,
SET_MQTT_GRP_TOPIC2, SET_MQTT_GRP_TOPIC3, SET_MQTT_GRP_TOPIC4,
SET_TEMPLATE_NAME,
SET_MAX };
enum DeviceGroupMessageType { DGR_MSGTYP_FULL_STATUS, DGR_MSGTYP_PARTIAL_UPDATE, DGR_MSGTYP_UPDATE, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_MSGTYP_UPDATE_DIRECT, DGR_MSGTYP_REUPDATE };

View File

@ -41,6 +41,7 @@ void KNX_CB_Action(message_t const &msg, void *arg);
void DomoticzTempHumPressureSensor(float temp, float hum, float baro = -1);
char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween = '\0');
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack, uint32_t stack_end);
extern "C" void resetPins();
/*********************************************************************************************\
* Default global defines

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x08020000;
const uint32_t VERSION = 0x08020100;
// Lowest compatible version
const uint32_t VERSION_COMPATIBLE = 0x07010006;

View File

@ -31,7 +31,7 @@
#define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 11 as used by WifiManager web GUI
#endif
const uint16_t CHUNKED_BUFFER_SIZE = 400; // Chunk buffer size (should be smaller than half mqtt_date size = MESSZ)
const uint16_t CHUNKED_BUFFER_SIZE = (MESSZ / 2) - 100; // Chunk buffer size (should be smaller than half mqtt_data size = MESSZ)
const uint16_t HTTP_REFRESH_TIME = 2345; // milliseconds
#define HTTP_RESTART_RECONNECT_TIME 9000 // milliseconds
@ -184,7 +184,7 @@ const char HTTP_SCRIPT_CONSOL[] PROGMEM =
"clearTimeout(lt);"
"t=eb('t1');"
"if(p==1){"
"c=eb('c1');"
"c=eb('c1');" // Console command id
"o='&c1='+encodeURIComponent(c.value);"
"c.value='';"
"t.scrollTop=99999;"
@ -211,7 +211,21 @@ const char HTTP_SCRIPT_CONSOL[] PROGMEM =
"lt=setTimeout(l,%d);"
"return false;"
"}"
"wl(l);";
"wl(l);" // Load initial console text
// Console command history
"var hc=[],cn=0;" // hc = History commands, cn = Number of history being shown
"function h(){"
// "if(!(navigator.maxTouchPoints||'ontouchstart'in document.documentElement)){eb('c1').autocomplete='off';}" // No touch so stop browser autocomplete
"eb('c1').addEventListener('keydown',function(e){"
"var b=eb('c1'),c=e.keyCode;" // c1 = Console command id
"if(38==c||40==c){b.autocomplete='off';}" // ArrowUp or ArrowDown must be a keyboard so stop browser autocomplete
"38==c?(++cn>hc.length&&(cn=hc.length),b.value=hc[cn-1]||''):" // ArrowUp
"40==c?(0>--cn&&(cn=0),b.value=hc[cn-1]||''):" // ArrowDown
"13==c&&(hc.length>19&&hc.pop(),hc.unshift(b.value),cn=0)" // Enter, 19 = Max number -1 of commands in history
"});"
"}"
"wl(h);"; // Add console command key eventlistener after name has been synced with id (= wl(jd))
const char HTTP_MODULE_TEMPLATE_REPLACE[] PROGMEM =
"}2%d'>%s (%d}3"; // }2 and }3 are used in below os.replace
@ -399,7 +413,7 @@ const char HTTP_FORM_TEMPLATE[] PROGMEM =
const char HTTP_FORM_TEMPLATE_FLAG[] PROGMEM =
"<p></p>" // Keep close so do not use <br>
"<fieldset><legend><b>&nbsp;" D_TEMPLATE_FLAGS "&nbsp;</b></legend><p>"
// "<input id='c0' name='c0' type='checkbox'><b>" D_OPTION_TEXT "</b><br>"
// "<label><input id='c0' name='c0' type='checkbox'><b>" D_OPTION_TEXT "</b></label><br>"
"</p></fieldset>";
const char HTTP_FORM_MODULE[] PROGMEM =
@ -412,9 +426,9 @@ const char HTTP_FORM_WIFI[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_WIFI_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='wi'>"
"<p><b>" D_AP1_SSID "</b> (" STA_SSID1 ")<br><input id='s1' placeholder='" STA_SSID1 "' value='%s'></p>"
"<p><b>" D_AP1_PASSWORD "</b><input type='checkbox' onclick='sp(\"p1\")'><br><input id='p1' type='password' placeholder='" D_AP1_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><label><b>" D_AP1_PASSWORD "</b><input type='checkbox' onclick='sp(\"p1\")'></label><br><input id='p1' type='password' placeholder='" D_AP1_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><b>" D_AP2_SSID "</b> (" STA_SSID2 ")<br><input id='s2' placeholder='" STA_SSID2 "' value='%s'></p>"
"<p><b>" D_AP2_PASSWORD "</b><input type='checkbox' onclick='sp(\"p2\")'><br><input id='p2' type='password' placeholder='" D_AP2_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><label><b>" D_AP2_PASSWORD "</b><input type='checkbox' onclick='sp(\"p2\")'></label><br><input id='p2' type='password' placeholder='" D_AP2_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><b>" D_HOSTNAME "</b> (%s)<br><input id='h' placeholder='%s' value='%s'></p>"
"<p><b>" D_CORS_DOMAIN "</b><input id='c' placeholder='" CORS_DOMAIN "' value='%s'></p>";
@ -432,12 +446,12 @@ const char HTTP_FORM_OTHER[] PROGMEM =
"<p></p>"
"<fieldset><legend><b>&nbsp;" D_TEMPLATE "&nbsp;</b></legend>"
"<p><input id='t1' placeholder='" D_TEMPLATE "' value='%s'></p>"
"<p><input id='t2' type='checkbox'%s><b>" D_ACTIVATE "</b></p>"
"<p><label><input id='t2' type='checkbox'%s><b>" D_ACTIVATE "</b></label></p>"
"</fieldset>"
"<br>"
"<b>" D_WEB_ADMIN_PASSWORD "</b><input type='checkbox' onclick='sp(\"wp\")'><br><input id='wp' type='password' placeholder='" D_WEB_ADMIN_PASSWORD "' value='" D_ASTERISK_PWD "'><br>"
"<label><b>" D_WEB_ADMIN_PASSWORD "</b><input type='checkbox' onclick='sp(\"wp\")'></label><br><input id='wp' type='password' placeholder='" D_WEB_ADMIN_PASSWORD "' value='" D_ASTERISK_PWD "'><br>"
"<br>"
"<input id='b1' type='checkbox'%s><b>" D_MQTT_ENABLE "</b><br>"
"<label><input id='b1' type='checkbox'%s><b>" D_MQTT_ENABLE "</b></label><br>"
"<br>";
const char HTTP_FORM_END[] PROGMEM =
@ -745,7 +759,7 @@ void _WSContentSend(const String& content) // Low level sendContent for a
#ifdef USE_DEBUG_DRIVER
ShowFreeMem(PSTR("WSContentSend"));
#endif
DEBUG_CORE_LOG(PSTR("WEB: Chunk size %d"), len);
DEBUG_CORE_LOG(PSTR("WEB: Chunk size %d/%d"), len, sizeof(mqtt_data));
}
void WSContentFlush(void)
@ -1164,7 +1178,7 @@ void HandleRoot(void)
int32_t ShutterWebButton;
if (ShutterWebButton = IsShutterWebButton(idx)) {
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx,
(set_button) ? SettingsText(SET_BUTTON1 + idx -1) : ((Settings.shutter_options[abs(ShutterWebButton)-1] & 2) /* is locked */ ? "-" : ((Settings.shutter_options[abs(ShutterWebButton)-1] & 8) /* invert web buttons */ ? ((ShutterWebButton>0) ? "" : "") : ((ShutterWebButton>0) ? "" : ""))),
(set_button) ? SettingsText(SET_BUTTON1 + idx -1) : ((Settings.shutter_options[abs(ShutterWebButton)-1] & 2) /* is locked */ ? "-" : ((Settings.shutter_options[abs(ShutterWebButton)-1] & 8) /* invert web buttons */ ? ((ShutterWebButton>0) ? "&#9660;" : "&#9650;") : ((ShutterWebButton>0) ? "&#9650;" : "&#9660;"))),
"");
continue;
}
@ -1494,9 +1508,9 @@ void HandleTemplateConfiguration(void)
void TemplateSaveSettings(void)
{
char tmp[sizeof(Settings.user_template.name)]; // WebGetArg NAME and GPIO/BASE/FLAG byte value
char tmp[TOPSZ]; // WebGetArg NAME and GPIO/BASE/FLAG byte value
char webindex[5]; // WebGetArg name
char svalue[128]; // Template command string
char svalue[200]; // Template command string
WebGetArg("s1", tmp, sizeof(tmp)); // NAME
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_TEMPLATE " {\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), tmp);
@ -1512,11 +1526,11 @@ void TemplateSaveSettings(void)
j++;
}
WebGetArg("g17", tmp, sizeof(tmp)); // FLAG - ADC0
WebGetArg("g17", tmp, sizeof(tmp)); // FLAG - ADC0
uint32_t flag = atoi(tmp);
for (uint32_t i = 0; i < GPIO_FLAG_USED; i++) {
snprintf_P(webindex, sizeof(webindex), PSTR("c%d"), i);
uint32_t state = WebServer->hasArg(webindex) << i +4; // FLAG
uint32_t state = WebServer->hasArg(webindex) << i +4; // FLAG
flag += state;
}
WebGetArg("g99", tmp, sizeof(tmp)); // BASE
@ -1735,7 +1749,7 @@ void HandleWifiConfiguration(void)
//display networks in page
for (uint32_t i = 0; i < n; i++) {
if (-1 == indices[i]) { continue; } // skip dups
int32_t rssi = WiFi.RSSI(indices[i])
int32_t rssi = WiFi.RSSI(indices[i]);
DEBUG_CORE_LOG(PSTR(D_LOG_WIFI D_SSID " %s, " D_BSSID " %s, " D_CHANNEL " %d, " D_RSSI " %d"),
WiFi.SSID(indices[i]).c_str(), WiFi.BSSIDstr(indices[i]).c_str(), WiFi.channel(indices[i]), rssi);
int quality = WifiGetRssiAsQuality(rssi);
@ -2126,8 +2140,13 @@ void HandleInformation(void)
WSContentSend_P(PSTR("}1" D_MQTT_USER "}2%s"), SettingsText(SET_MQTT_USER));
WSContentSend_P(PSTR("}1" D_MQTT_CLIENT "}2%s"), mqtt_client);
WSContentSend_P(PSTR("}1" D_MQTT_TOPIC "}2%s"), SettingsText(SET_MQTT_TOPIC));
// WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC "}2%s"), SettingsText(SET_MQTT_GRP_TOPIC));
WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC "}2%s"), GetGroupTopic_P(stopic, ""));
uint32_t real_index = SET_MQTT_GRP_TOPIC;
for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) {
if (1 == i) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
if (strlen(SettingsText(real_index +i))) {
WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC " %d}2%s"), 1 +i, GetGroupTopic_P(stopic, "", real_index +i));
}
}
WSContentSend_P(PSTR("}1" D_MQTT_FULL_TOPIC "}2%s"), GetTopic_P(stopic, CMND, mqtt_topic, ""));
WSContentSend_P(PSTR("}1" D_MQTT " " D_FALLBACK_TOPIC "}2%s"), GetFallbackTopic_P(stopic, ""));
} else {

View File

@ -440,9 +440,11 @@ void MqttPublishPowerState(uint32_t device)
Response_P(S_JSON_COMMAND_SVALUE, scommand, GetStateText(bitRead(power, device -1)));
MqttPublish(stopic);
GetTopic_P(stopic, STAT, mqtt_topic, scommand);
Response_P(GetStateText(bitRead(power, device -1)));
MqttPublish(stopic, Settings.flag.mqtt_power_retain); // CMND_POWERRETAIN
if (!Settings.flag4.only_json_message) { // SetOption90 - Disable non-json MQTT response
GetTopic_P(stopic, STAT, mqtt_topic, scommand);
Response_P(GetStateText(bitRead(power, device -1)));
MqttPublish(stopic, Settings.flag.mqtt_power_retain); // CMND_POWERRETAIN
}
#ifdef USE_SONOFF_IFAN
}
#endif // USE_SONOFF_IFAN
@ -503,15 +505,23 @@ void MqttConnected(void)
Response_P(PSTR(D_ONLINE));
MqttPublish(stopic, true);
// Satisfy iobroker (#299)
mqtt_data[0] = '\0';
MqttPublishPrefixTopic_P(CMND, S_RSLT_POWER);
if (!Settings.flag4.only_json_message) { // SetOption90 - Disable non-json MQTT response
// Satisfy iobroker (#299)
mqtt_data[0] = '\0';
MqttPublishPrefixTopic_P(CMND, S_RSLT_POWER);
}
GetTopic_P(stopic, CMND, mqtt_topic, PSTR("#"));
MqttSubscribe(stopic);
if (strstr_P(SettingsText(SET_MQTT_FULLTOPIC), MQTT_TOKEN_TOPIC) != nullptr) {
GetGroupTopic_P(stopic, PSTR("#")); // SetOption75 0: %prefix%/nothing/%topic% = cmnd/nothing/<grouptopic>/# or SetOption75 1: cmnd/<grouptopic>
MqttSubscribe(stopic);
uint32_t real_index = SET_MQTT_GRP_TOPIC;
for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) {
if (1 == i) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
if (strlen(SettingsText(real_index +i))) {
GetGroupTopic_P(stopic, PSTR("#"), real_index +i); // SetOption75 0: %prefix%/nothing/%topic% = cmnd/nothing/<grouptopic>/# or SetOption75 1: cmnd/<grouptopic>
MqttSubscribe(stopic);
}
}
GetFallbackTopic_P(stopic, PSTR("#"));
MqttSubscribe(stopic);
}
@ -520,30 +530,33 @@ void MqttConnected(void)
}
if (Mqtt.initial_connection_state) {
char stopic2[TOPSZ];
Response_P(PSTR("{\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}"),
ModuleName().c_str(), my_version, my_image, GetFallbackTopic_P(stopic, ""), GetGroupTopic_P(stopic2, ""));
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "1"));
if (ResetReason() != REASON_DEEP_SLEEP_AWAKE) {
char stopic2[TOPSZ];
Response_P(PSTR("{\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}"),
ModuleName().c_str(), my_version, my_image, GetFallbackTopic_P(stopic, ""), GetGroupTopic_P(stopic2, "", SET_MQTT_GRP_TOPIC));
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "1"));
#ifdef USE_WEBSERVER
if (Settings.webserver) {
if (Settings.webserver) {
#if LWIP_IPV6
Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"IPv6Address\":\"%s\"}"),
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str(),WifiGetIPv6().c_str());
Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"IPv6Address\":\"%s\"}"),
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str(),WifiGetIPv6().c_str());
#else
Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\"}"),
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str());
Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\"}"),
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str());
#endif // LWIP_IPV6 = 1
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "2"));
}
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "2"));
}
#endif // USE_WEBSERVER
Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":"));
if (CrashFlag()) {
CrashDump();
} else {
ResponseAppend_P(PSTR("\"%s\""), GetResetReason().c_str());
Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":"));
if (CrashFlag()) {
CrashDump();
} else {
ResponseAppend_P(PSTR("\"%s\""), GetResetReason().c_str());
}
ResponseJsonEnd();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "3"));
}
ResponseJsonEnd();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "3"));
MqttPublishAllPowerState();
if (Settings.tele_period) {
tele_period = Settings.tele_period -5; // Enable TelePeriod in 5 seconds
@ -881,26 +894,52 @@ void CmndPublish(void)
void CmndGroupTopic(void)
{
#ifdef USE_DEVICE_GROUPS
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
uint32_t settings_text_index = (XdrvMailbox.index <= 1 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + XdrvMailbox.index - 2);
#endif // USE_DEVICE_GROUPS
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
#ifdef USE_DEVICE_GROUPS
SettingsUpdateText(settings_text_index, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
#else // USE_DEVICE_GROUPS
SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
#endif // USE_DEVICE_GROUPS
restart_flag = 2;
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_GROUP_TOPICS)) {
if (XdrvMailbox.data_len > 0) {
uint32_t settings_text_index = (1 == XdrvMailbox.index) ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + XdrvMailbox.index - 2;
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
SettingsUpdateText(settings_text_index, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
// Eliminate duplicates, have at least one and fill from index 1
char stemp[MAX_GROUP_TOPICS][TOPSZ];
uint32_t read_index = 0;
uint32_t real_index = SET_MQTT_GRP_TOPIC;
for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) {
if (1 == i) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
if (strlen(SettingsText(real_index +i))) {
bool not_equal = true;
for (uint32_t j = 0; j < read_index; j++) {
if (!strcmp(SettingsText(real_index +i), stemp[j])) { // Topics are case-sensitive
not_equal = false;
}
}
if (not_equal) {
strncpy(stemp[read_index], SettingsText(real_index +i), sizeof(stemp[read_index]));
read_index++;
}
}
}
if (0 == read_index) {
SettingsUpdateText(SET_MQTT_GRP_TOPIC, MQTT_GRPTOPIC);
} else {
uint32_t write_index = 0;
uint32_t real_index = SET_MQTT_GRP_TOPIC;
for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) {
if (1 == i) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
if (write_index < read_index) {
SettingsUpdateText(real_index +i, stemp[write_index]);
write_index++;
} else {
SettingsUpdateText(real_index +i, "");
}
}
}
restart_flag = 2;
}
ResponseCmndAll(SET_MQTT_GRP_TOPIC, MAX_GROUP_TOPICS);
}
#ifdef USE_DEVICE_GROUPS
ResponseCmndChar(SettingsText(settings_text_index));
}
#else // USE_DEVICE_GROUPS
ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC));
#endif // USE_DEVICE_GROUPS
}
void CmndTopic(void)
@ -1203,7 +1242,7 @@ const char HTTP_FORM_MQTT1[] PROGMEM =
"<p><b>" D_CLIENT "</b> (%s)<br><input id='mc' placeholder='%s' value='%s'></p>";
const char HTTP_FORM_MQTT2[] PROGMEM =
"<p><b>" D_USER "</b> (" MQTT_USER ")<br><input id='mu' placeholder='" MQTT_USER "' value='%s'></p>"
"<p><b>" D_PASSWORD "</b><input type='checkbox' onclick='sp(\"mp\")'><br><input id='mp' type='password' placeholder='" D_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><label><b>" D_PASSWORD "</b><input type='checkbox' onclick='sp(\"mp\")'></label><br><input id='mp' type='password' placeholder='" D_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><b>" D_TOPIC "</b> = %%topic%% (%s)<br><input id='mt' placeholder='%s' value='%s'></p>"
"<p><b>" D_FULL_TOPIC "</b> (%s)<br><input id='mf' placeholder='%s' value='%s'></p>";

View File

@ -273,6 +273,9 @@ struct LIGHT {
bool fade_initialized = false; // dont't fade at startup
bool fade_running = false;
#ifdef USE_DEVICE_GROUPS
bool devgrp_no_channels_out = false; // don't share channels with device group (e.g. if scheme set by other device)
#endif // USE_DEVICE_GROUPS
uint16_t fade_start_10[LST_MAX] = {0,0,0,0,0};
uint16_t fade_cur_10[LST_MAX];
uint16_t fade_end_10[LST_MAX]; // 10 bits resolution target channel values
@ -1429,6 +1432,9 @@ void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value)
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Light signal %d"), signal);
light_controller.changeRGB(signal, 255 - signal, 0, true); // keep bri
Settings.light_scheme = 0;
#ifdef USE_DEVICE_GROUPS
LightUpdateScheme();
#endif // USE_DEVICE_GROUPS
if (0 == light_state.getBri()) {
light_controller.changeBri(50);
}
@ -1612,8 +1618,9 @@ void LightPreparePower(power_t channels = 0xFFFFFFFF) { // 1 = only RGB, 2 =
void LightCycleColor(int8_t direction)
{
if (Light.strip_timer_counter % (Settings.light_speed * 2)) {
return;
// if (Light.strip_timer_counter % (Settings.light_speed * 2)) { return; } // Speed 1: 24sec, 2: 48sec, 3: 72sec, etc
if (Settings.light_speed > 3) {
if (Light.strip_timer_counter % (Settings.light_speed - 2)) { return; } // Speed 4: 24sec, 5: 36sec, 6: 48sec, etc
}
if (0 == direction) {
@ -1630,6 +1637,9 @@ void LightCycleColor(int8_t direction)
// direction = (Light.random < Light.wheel) ? -1 : 1;
direction = (Light.random &0x01) ? 1 : -1;
}
// if (Settings.light_speed < 3) { direction <<= (3 - Settings.light_speed); } // Speed 1: 12/4=3sec, 2: 12/2=6sec, 3: 12sec
if (Settings.light_speed < 3) { direction *= (4 - Settings.light_speed); } // Speed 1: 12/3=4sec, 2: 12/2=6sec, 3: 12sec
Light.wheel += direction;
uint16_t hue = changeUIntScale(Light.wheel, 0, 255, 0, 359); // Scale to hue to keep amount of steps low (max 255 instead of 359)
@ -1738,6 +1748,9 @@ void LightAnimate(void)
Light.wakeup_active = 0;
Settings.light_scheme = LS_POWER;
#ifdef USE_DEVICE_GROUPS
LightUpdateScheme();
#endif // USE_DEVICE_GROUPS
}
}
break;
@ -1771,7 +1784,7 @@ void LightAnimate(void)
}
if (Light.update) {
#ifdef USE_DEVICE_GROUPS
if (Light.power) LightSendDeviceGroupStatus();
if (Light.power) LightSendDeviceGroupStatus(false);
#endif // USE_DEVICE_GROUPS
uint16_t cur_col_10[LST_MAX]; // 10 bits resolution
@ -2093,25 +2106,36 @@ void calcGammaBulbs(uint16_t cur_col_10[5]) {
}
#ifdef USE_DEVICE_GROUPS
void LightSendDeviceGroupStatus()
void LightSendDeviceGroupStatus(bool force)
{
if (Light.subtype > LST_SINGLE) {
static uint8_t last_channels[LST_MAX];
static uint8_t last_bri;
uint8_t bri = light_state.getBri();
bool send_bri_update = (force || bri != last_bri);
if (Light.subtype > LST_SINGLE && !Light.devgrp_no_channels_out) {
uint8_t channels[LST_MAX];
light_state.getChannels(channels);
SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_LIGHT_CHANNELS, channels);
if (force || memcmp(last_channels, channels, LST_MAX)) {
memcpy(last_channels, channels, LST_MAX);
SendLocalDeviceGroupMessage((send_bri_update ? DGR_MSGTYP_PARTIAL_UPDATE : DGR_MSGTYP_UPDATE), DGR_ITEM_LIGHT_CHANNELS, channels);
}
}
if (send_bri_update) {
last_bri = bri;
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_BRI, light_state.getBri());
}
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme,
DGR_ITEM_LIGHT_BRI, light_state.getBri());
}
void LightHandleDeviceGroupItem()
void LightHandleDeviceGroupItem(void)
{
static bool send_state = false;
static bool restore_power = false;
bool more_to_come;
uint32_t value = XdrvMailbox.payload;
#ifdef USE_PWM_DIMMER_REMOTE
if (XdrvMailbox.index & 0xff00) return; // Ignore updates from other device groups
if (XdrvMailbox.index & 0xff0000) return; // Ignore updates from other device groups
#endif // USE_PWM_DIMMER_REMOTE
switch (XdrvMailbox.command_code) {
case DGR_ITEM_EOL:
@ -2140,6 +2164,7 @@ void LightHandleDeviceGroupItem()
case DGR_ITEM_LIGHT_SCHEME:
if (Settings.light_scheme != value) {
Settings.light_scheme = value;
Light.devgrp_no_channels_out = (value != 0);
send_state = true;
}
break;
@ -2158,6 +2183,7 @@ void LightHandleDeviceGroupItem()
light_controller.changeChannels(Light.entry_color);
light_controller.changeBri(old_bri);
Settings.light_scheme = 0;
Light.devgrp_no_channels_out = false;
}
else {
light_state.setColorMode(LCM_CT);
@ -2184,11 +2210,22 @@ void LightHandleDeviceGroupItem()
break;
case DGR_ITEM_STATUS:
SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade,
DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
LightSendDeviceGroupStatus();
DGR_ITEM_LIGHT_SPEED, Settings.light_speed, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme);
LightSendDeviceGroupStatus(true);
break;
}
}
void LightUpdateScheme(void)
{
static uint8_t last_scheme;
if (Settings.light_scheme != last_scheme) {
last_scheme = Settings.light_scheme;
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme);
}
Light.devgrp_no_channels_out = false;
}
#endif // USE_DEVICE_GROUPS
/*********************************************************************************************\
@ -2287,6 +2324,9 @@ void CmndSupportColor(void)
}
Settings.light_scheme = 0;
#ifdef USE_DEVICE_GROUPS
LightUpdateScheme();
#endif // USE_DEVICE_GROUPS
coldim = true;
} else { // Color3, 4, 5 and 6
for (uint32_t i = 0; i < LST_RGB; i++) {
@ -2454,7 +2494,7 @@ void CmndScheme(void)
}
Settings.light_scheme = XdrvMailbox.payload;
#ifdef USE_DEVICE_GROUPS
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme);
LightUpdateScheme();
#endif // USE_DEVICE_GROUPS
if (LS_WAKEUP == Settings.light_scheme) {
Light.wakeup_active = 3;
@ -2479,6 +2519,9 @@ void CmndWakeup(void)
}
Light.wakeup_active = 3;
Settings.light_scheme = LS_WAKEUP;
#ifdef USE_DEVICE_GROUPS
LightUpdateScheme();
#endif // USE_DEVICE_GROUPS
LightPowerOn();
ResponseCmndChar(D_JSON_STARTED);
}

View File

@ -73,9 +73,9 @@ void SerialBridgeInput(void)
char hex_char[(serial_bridge_in_byte_counter * 2) + 2];
bool assume_json = (!serial_bridge_raw && (serial_bridge_buffer[0] == '{'));
Response_P(PSTR("{\"" D_JSON_SSERIALRECEIVED "\":%s%s%s}"),
(assume_json) ? "" : """",
(assume_json) ? "" : "\"",
(serial_bridge_raw) ? ToHex_P((unsigned char*)serial_bridge_buffer, serial_bridge_in_byte_counter, hex_char, sizeof(hex_char)) : serial_bridge_buffer,
(assume_json) ? "" : """");
(assume_json) ? "" : "\"");
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_SSERIALRECEIVED));
XdrvRulesProcess();
serial_bridge_in_byte_counter = 0;

View File

@ -648,7 +648,11 @@ const char HTTP_TIMER_SCRIPT6[] PROGMEM =
"o=qs('#mw');for(i=0;i<=15;i++){ce((i<10)?('0'+i):i,o);}" // Create window minutes select options
"o=qs('#d1');for(i=0;i<%d;i++){ce(i+1,o);}" // Create outputs
"var a='" D_DAY3LIST "';"
"s='';for(i=0;i<7;i++){s+=\"<input id='w\"+i+\"' type='checkbox'><b>\"+a.substring(i*3,(i*3)+3)+\"</b> \"}"
// "s='';for(i=0;i<7;i++){s+=\"<input id='w\"+i+\"' type='checkbox'><b>\"+a.substring(i*3,(i*3)+3)+\"</b> \"}"
// "s='';for(i=0;i<7;i++){s+=\"<input id='w\"+i+\"' type='checkbox'><label for='w\"+i+\"'>\"+a.substring(i*3,(i*3)+3)+\"</label> \"}"
"s='';for(i=0;i<7;i++){s+=\"<label><input id='w\"+i+\"' type='checkbox'><b>\"+a.substring(i*3,(i*3)+3)+\"</b></label> \"}"
"eb('ds').innerHTML=s;" // Create weekdays
"eb('dP').click();" // Get the element with id='dP' and click on it
"}"
@ -659,22 +663,22 @@ const char HTTP_FORM_TIMER1[] PROGMEM =
"<fieldset style='min-width:470px;text-align:center;'>"
"<legend style='text-align:left;'><b>&nbsp;" D_TIMER_PARAMETERS "&nbsp;</b></legend>"
"<form method='post' action='" WEB_HANDLE_TIMER "' onsubmit='return st();'>"
"<br><input id='e0' type='checkbox'%s><b>" D_TIMER_ENABLE "</b><br><br><hr>"
"<br><label><input id='e0' type='checkbox'%s><b>" D_TIMER_ENABLE "</b></label><br><br><hr>"
"<input id='t0' value='";
const char HTTP_FORM_TIMER2[] PROGMEM =
"' hidden><div id='bt'></div><br><br><br>"
"<div id='oa' name='oa'></div><br>"
"<div>"
"<input id='a0' type='checkbox'><b>" D_TIMER_ARM "</b>&emsp;"
"<input id='r0' type='checkbox'><b>" D_TIMER_REPEAT "</b>"
"<label><input id='a0' type='checkbox'><b>" D_TIMER_ARM "</b></label>&emsp;"
"<label><input id='r0' type='checkbox'><b>" D_TIMER_REPEAT "</b></label>"
"</div><br>"
"<div>";
#ifdef USE_SUNRISE
const char HTTP_FORM_TIMER3[] PROGMEM =
"<fieldset style='width:%dpx;margin:auto;text-align:left;border:0;'>"
"<input id='b0' name='rd' type='radio' value='0' onclick='gt();'><b>" D_TIMER_TIME "</b><br>"
"<input id='b1' name='rd' type='radio' value='1' onclick='gt();'><b>" D_SUNRISE "</b> (%s)<br>"
"<input id='b2' name='rd' type='radio' value='2' onclick='gt();'><b>" D_SUNSET "</b> (%s)<br>"
"<label><input id='b0' name='rd' type='radio' value='0' onclick='gt();'><b>" D_TIMER_TIME "</b></label><br>"
"<label><input id='b1' name='rd' type='radio' value='1' onclick='gt();'><b>" D_SUNRISE "</b> (%s)</label><br>"
"<label><input id='b2' name='rd' type='radio' value='2' onclick='gt();'><b>" D_SUNSET "</b> (%s)</label><br>"
"</fieldset>"
"<p></p>"
"<span><select style='width:46px;' id='dr'></select></span>"

View File

@ -1508,9 +1508,9 @@ chknext:
lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
SCRIPT_SKIP_SPACES
fvar=fvar1;
if ((*opp=='<' && fvar1<fvar2) ||
(*opp=='>' && fvar1>fvar2) ||
(*opp=='=' && fvar1==fvar2))
if ((*opp=='<' && fvar1<fvar2) ||
(*opp=='>' && fvar1>fvar2) ||
(*opp=='=' && fvar1==fvar2))
{
if (*lp!='<' && *lp!='>' && *lp!='=' && *lp!=')' && *lp!=SCRIPT_EOL) {
float fvar3;
@ -1759,13 +1759,18 @@ chknext:
float fvar3;
lp=GetNumericResult(lp,OPER_EQU,&fvar3,0);
fvar=SML_SetBaud(fvar1,fvar3);
} else {
} else if (fvar2==1) {
char str[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,str,0);
fvar=SML_Write(fvar1,str);
} else {
#ifdef ED300L
fvar=SML_Status(fvar1);
#else
fvar=0;
#endif
}
lp++;
fvar=0;
len=0;
goto exit;
}
@ -3101,7 +3106,7 @@ const char HTTP_FORM_SCRIPT[] PROGMEM =
const char HTTP_FORM_SCRIPT1[] PROGMEM =
"<div style='text-align:right' id='charNum'> </div>"
"<input style='width:3%%;' id='c%d' name='c%d' type='checkbox'%s><b>" D_SCRIPT_ENABLE "</b><br/>"
"<label><input style='width:3%%;' id='c%d' name='c%d' type='checkbox'%s><b>" D_SCRIPT_ENABLE "</b></label><br/>"
"<br><textarea id='t1' name='t1' rows='8' cols='80' maxlength='%d' style='font-size: 12pt' >";
const char HTTP_FORM_SCRIPT1b[] PROGMEM =

View File

@ -760,13 +760,13 @@ const char HTTP_FORM_KNX[] PROGMEM =
"<input style='width:12%%;' type='number' name='line' min='0' max='15' value='%d'> . "
"<input style='width:12%%;' type='number' name='member' min='0' max='255' value='%d'>"
"<br><br>" D_KNX_PHYSICAL_ADDRESS_NOTE "<br><br>"
"<input id='b1' type='checkbox'";
"<label><input id='b1' type='checkbox'";
const char HTTP_FORM_KNX1[] PROGMEM =
"><b>" D_KNX_ENABLE "</b>&emsp;<input id='b2' type='checkbox'";
"><b>" D_KNX_ENABLE "</b></label>&emsp;<label><input id='b2' type='checkbox'";
const char HTTP_FORM_KNX2[] PROGMEM =
"><b>" D_KNX_ENHANCEMENT "</b><br></center><br>"
"><b>" D_KNX_ENHANCEMENT "</b></label><br></center><br>"
"<fieldset><center>"
"<b>" D_KNX_GROUP_ADDRESS_TO_WRITE "</b><hr>"

View File

@ -298,7 +298,7 @@ void HueLightStatus1(uint8_t device, String *response)
prev_x_str[0] = prev_y_str[0] = 0;
}
hue = changeUIntScale(hue, 0, 359, 0, 65535);
hue = changeUIntScale(hue, 0, 360, 0, 65535);
if ((hue > prev_hue ? hue - prev_hue : prev_hue - hue) < 400) {
hue = prev_hue;
} else { // if hue was changed outside of Alexa, reset xy
@ -598,7 +598,7 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
uint8_t rr,gg,bb;
LightStateClass::XyToRgb(x, y, &rr, &gg, &bb);
LightStateClass::RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
prev_hue = changeUIntScale(hue, 0, 359, 0, 65535); // calculate back prev_hue
prev_hue = changeUIntScale(hue, 0, 360, 0, 65535); // calculate back prev_hue
prev_sat = (sat > 254 ? 254 : sat);
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, "XY RGB (%d %d %d) HS (%d %d)", rr,gg,bb,hue,sat);
if (resp) { response += ","; }
@ -619,8 +619,8 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
device_id, "hue", hue);
response += buf;
if (LST_RGB <= Light.subtype) {
// change range from 0..65535 to 0..359
hue = changeUIntScale(hue, 0, 65535, 0, 359);
// change range from 0..65535 to 0..360
hue = changeUIntScale(hue, 0, 65535, 0, 360);
g_gotct = false;
change = true;
}

View File

@ -393,88 +393,36 @@ typedef struct Z_StatusLine {
const char * status_msg;
} Z_StatusLine;
ZF(SUCCESS)
ZF(FAILURE)
ZF(NOT_AUTHORIZED)
ZF(RESERVED_FIELD_NOT_ZERO)
ZF(MALFORMED_COMMAND)
ZF(UNSUP_CLUSTER_COMMAND)
ZF(UNSUP_GENERAL_COMMAND)
ZF(UNSUP_MANUF_CLUSTER_COMMAND)
ZF(UNSUP_MANUF_GENERAL_COMMAND)
ZF(INVALID_FIELD)
ZF(UNSUPPORTED_ATTRIBUTE)
ZF(INVALID_VALUE)
ZF(READ_ONLY)
ZF(INSUFFICIENT_SPACE)
ZF(DUPLICATE_EXISTS)
ZF(NOT_FOUND)
ZF(UNREPORTABLE_ATTRIBUTE)
ZF(INVALID_DATA_TYPE)
ZF(INVALID_SELECTOR)
ZF(WRITE_ONLY)
ZF(INCONSISTENT_STARTUP_STATE)
ZF(DEFINED_OUT_OF_BAND)
ZF(INCONSISTENT)
ZF(ACTION_DENIED)
ZF(TIMEOUT)
ZF(ABORT)
ZF(INVALID_IMAGE)
ZF(WAIT_FOR_DATA)
ZF(NO_IMAGE_AVAILABLE)
ZF(REQUIRE_MORE_IMAGE)
ZF(NOTIFICATION_PENDING)
ZF(HARDWARE_FAILURE)
ZF(SOFTWARE_FAILURE)
ZF(CALIBRATION_ERROR)
ZF(UNSUPPORTED_CLUSTER)
// Undocumented Zigbee ZCL code here: https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/Zigbee-Error-Codes-in-the-Log
String getZigbeeStatusMessage(uint8_t status) {
static const char StatusMsg[] PROGMEM = "SUCCESS|FAILURE|NOT_AUTHORIZED|RESERVED_FIELD_NOT_ZERO|MALFORMED_COMMAND|UNSUP_CLUSTER_COMMAND|UNSUP_GENERAL_COMMAND"
"|UNSUP_MANUF_CLUSTER_COMMAND|UNSUP_MANUF_GENERAL_COMMAND|INVALID_FIELD|UNSUPPORTED_ATTRIBUTE|INVALID_VALE|READ_ONLY"
"|INSUFFICIENT_SPACE|DUPLICATE_EXISTS|NOT_FOUND|UNREPORTABLE_ATTRIBUTE|INVALID_DATA_TYPE|INVALID_SELECTOR|WRITE_ONLY"
"|INCONSISTENT_STARTUP_STATE|DEFINED_OUT_OF_BAND|INCONSISTENT|ACTION_DENIED|TIMEOUT|ABORT|INVALID_IMAGE|WAIT_FOR_DATA"
"|NO_IMAGE_AVAILABLE|REQUIRE_MORE_IMAGE|NOTIFICATION_PENDING|HARDWARE_FAILURE|SOFTWARE_FAILURE|CALIBRATION_ERROR|UNSUPPORTED_CLUSTER|NO_ROUTE"
"|CHANNEL_ACCESS_FAILURE|NO_ACK|NO_APP_ACK|NO_ROUTE"
;
static const uint8_t StatusIdx[] PROGMEM = { 0x00, 0x01, 0x7E, 0x7F, 0x80, 0x81, 0x82,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9A, 0xC0, 0xC1, 0xC2, 0xC3, 0xCD,
0xE1, 0xE9, 0xA7, 0xD0};
const Z_StatusLine Z_Status[] PROGMEM = {
0x00, Z(SUCCESS),
0x01, Z(FAILURE),
0x7E, Z(NOT_AUTHORIZED),
0x7F, Z(RESERVED_FIELD_NOT_ZERO),
0x80, Z(MALFORMED_COMMAND),
0x81, Z(UNSUP_CLUSTER_COMMAND),
0x82, Z(UNSUP_GENERAL_COMMAND),
0x83, Z(UNSUP_MANUF_CLUSTER_COMMAND),
0x84, Z(UNSUP_MANUF_GENERAL_COMMAND),
0x85, Z(INVALID_FIELD),
0x86, Z(UNSUPPORTED_ATTRIBUTE),
0x87, Z(INVALID_VALUE),
0x88, Z(READ_ONLY),
0x89, Z(INSUFFICIENT_SPACE),
0x8A, Z(DUPLICATE_EXISTS),
0x8B, Z(NOT_FOUND),
0x8C, Z(UNREPORTABLE_ATTRIBUTE),
0x8D, Z(INVALID_DATA_TYPE),
0x8E, Z(INVALID_SELECTOR),
0x8F, Z(WRITE_ONLY),
0x90, Z(INCONSISTENT_STARTUP_STATE),
0x91, Z(DEFINED_OUT_OF_BAND),
0x92, Z(INCONSISTENT),
0x93, Z(ACTION_DENIED),
0x94, Z(TIMEOUT),
0x95, Z(ABORT),
0x96, Z(INVALID_IMAGE),
0x97, Z(WAIT_FOR_DATA),
0x98, Z(NO_IMAGE_AVAILABLE),
0x99, Z(REQUIRE_MORE_IMAGE),
0x9A, Z(NOTIFICATION_PENDING),
0xC0, Z(HARDWARE_FAILURE),
0xC1, Z(SOFTWARE_FAILURE),
0xC2, Z(CALIBRATION_ERROR),
0xC3, Z(UNSUPPORTED_CLUSTER),
};
const __FlashStringHelper* getZigbeeStatusMessage(uint8_t status) {
for (uint32_t i = 0; i < sizeof(Z_Status) / sizeof(Z_Status[0]); i++) {
const Z_StatusLine *statl = &Z_Status[i];
if (statl->status == status) {
return (const __FlashStringHelper*) statl->status_msg;
char msg[32];
int32_t idx = -1;
for (uint32_t i = 0; i < sizeof(StatusIdx); i++) {
if (status == pgm_read_byte(&StatusIdx[i])) {
idx = i;
break;
}
}
return F("");
if (idx >= 0) {
GetTextIndexed(msg, sizeof(msg), idx, StatusMsg);
} else {
*msg = 0x00; // empty string
}
return String(msg);
}
#endif // USE_ZIGBEE

View File

@ -43,6 +43,42 @@ JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) {
return *(JsonVariant*)nullptr;
}
// get the result as a string (const char*) and nullptr if there is no field or the string is empty
const char * getCaseInsensitiveConstCharNull(const JsonObject &json, const char *needle) {
const JsonVariant &val = getCaseInsensitive(json, needle);
if (&val) {
const char *val_cs = val.as<const char*>();
if (strlen(val_cs)) {
return val_cs;
}
}
return nullptr;
}
// Get an JSON attribute, with case insensitive key search starting with *needle
JsonVariant &startsWithCaseInsensitive(const JsonObject &json, const char *needle) {
// key can be in PROGMEM
if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
return *(JsonVariant*)nullptr;
}
String needle_s(needle);
needle_s.toLowerCase();
for (auto kv : json) {
String key_s(kv.key);
key_s.toLowerCase();
JsonVariant &value = kv.value;
if (key_s.startsWith(needle_s)) {
return value;
}
}
// if not found
return *(JsonVariant*)nullptr;
}
uint32_t parseHex(const char **data, size_t max_len = 8) {
uint32_t ret = 0;
for (uint32_t i = 0; i < max_len; i++) {

View File

@ -26,7 +26,9 @@
#endif
const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds
typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value);
/*********************************************************************************************\
* Structures for device configuration
\*********************************************************************************************/
const size_t endpoints_max = 8; // we limit to 8 endpoints
@ -44,7 +46,7 @@ typedef struct Z_Device {
uint8_t seqNumber;
// Light information for Hue integration integration, last known values
int8_t bulbtype; // number of channel for the bulb: 0-5, or 0xFF if no Hue integration
uint8_t power; // power state (boolean)
uint8_t power; // power state (boolean), MSB (0x80) stands for reachable
uint8_t colormode; // 0x00: Hue/Sat, 0x01: XY, 0x02: CT
uint8_t dimmer; // last Dimmer value: 0-254
uint8_t sat; // last Sat: 0..254
@ -53,17 +55,26 @@ typedef struct Z_Device {
uint16_t x, y; // last color [x,y]
} Z_Device;
/*********************************************************************************************\
* Structures for deferred callbacks
\*********************************************************************************************/
typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value);
// Category for Deferred actions, this allows to selectively remove active deferred or update them
typedef enum Z_Def_Category {
Z_CAT_NONE = 0, // no category, it will happen anyways
Z_CAT_READ_ATTR, // Attribute reporting, either READ_ATTRIBUTE or REPORT_ATTRIBUTE, we coalesce all attributes reported if we can
Z_CAT_VIRTUAL_ATTR, // Creation of a virtual attribute, typically after a time-out. Ex: Aqara presence sensor
Z_CAT_REACHABILITY, // timer set to measure reachability of device, i.e. if we don't get an answer after 1s, it is marked as unreachable (for Alexa)
Z_CAT_READ_0006, // Read 0x0006 cluster
Z_CAT_READ_0008, // Read 0x0008 cluster
Z_CAT_READ_0102, // Read 0x0300 cluster
Z_CAT_READ_0300, // Read 0x0300 cluster
} Z_Def_Category;
const uint32_t Z_CAT_REACHABILITY_TIMEOUT = 1000; // 1000 ms or 1s
typedef struct Z_Deferred {
// below are per device timers, used for example to query the new state of the device
uint32_t timer; // millis() when to fire the timer, 0 if no timer
@ -76,6 +87,10 @@ typedef struct Z_Deferred {
Z_DeviceTimer func; // function to call when timer occurs
} Z_Deferred;
/*********************************************************************************************\
* Singleton for device configuration
\*********************************************************************************************/
// All devices are stored in a Vector
// Invariants:
// - shortaddr is unique if not null
@ -105,15 +120,14 @@ public:
// Add an endpoint to a device
void addEndpoint(uint16_t shortaddr, uint8_t endpoint);
// Add cluster
void addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster);
void clearEndpoints(uint16_t shortaddr);
void setManufId(uint16_t shortaddr, const char * str);
void setModelId(uint16_t shortaddr, const char * str);
void setFriendlyName(uint16_t shortaddr, const char * str);
const char * getFriendlyName(uint16_t shortaddr) const;
const char * getModelId(uint16_t shortaddr) const;
void setReachable(uint16_t shortaddr, bool reachable);
// get next sequence number for (increment at each all)
uint8_t getNextSeqNumber(uint16_t shortaddr);
@ -121,20 +135,23 @@ public:
// Dump json
String dumpLightState(uint16_t shortaddr) const;
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
int32_t deviceRestore(const JsonObject &json);
// Hue support
void setHueBulbtype(uint16_t shortaddr, int8_t bulbtype);
int8_t getHueBulbtype(uint16_t shortaddr) const ;
void updateHueState(uint16_t shortaddr,
const uint8_t *power, const uint8_t *colormode,
const bool *power, const uint8_t *colormode,
const uint8_t *dimmer, const uint8_t *sat,
const uint16_t *ct, const uint16_t *hue,
const uint16_t *x, const uint16_t *y);
const uint16_t *x, const uint16_t *y,
const bool *reachable);
bool getHueState(uint16_t shortaddr,
uint8_t *power, uint8_t *colormode,
bool *power, uint8_t *colormode,
uint8_t *dimmer, uint8_t *sat,
uint16_t *ct, uint16_t *hue,
uint16_t *x, uint16_t *y) const ;
uint16_t *x, uint16_t *y,
bool *reachable) const ;
// Timers
void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category);
@ -191,13 +208,22 @@ private:
// Create a new entry in the devices list - must be called if it is sure it does not already exist
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
void freeDeviceEntry(Z_Device *device);
void setStringAttribute(char*& attr, const char * str);
};
/*********************************************************************************************\
* Singleton variable
\*********************************************************************************************/
Z_Devices zigbee_devices = Z_Devices();
// Local coordinator information
uint64_t localIEEEAddr = 0;
/*********************************************************************************************\
* Implementation
\*********************************************************************************************/
// https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/
template < typename T>
bool Z_Devices::findInVector(const std::vector<T> & vecOfElements, const T & element) {
@ -242,7 +268,7 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
0, // seqNumber
// Hue support
-1, // no Hue support
0, // power
0x80, // power off + reachable
0, // colormode
0, // dimmer
0, // sat
@ -450,6 +476,20 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
}
}
//
// Clear all endpoints
//
void Z_Devices::clearEndpoints(uint16_t shortaddr) {
if (!shortaddr) { return; }
Z_Device &device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
for (uint32_t i = 0; i < endpoints_max; i++) {
device.endpoints[i] = 0;
// no dirty here because it doesn't make sense to store it, does it?
}
}
//
// Add an endpoint to a shortaddr
//
@ -480,73 +520,55 @@ uint8_t Z_Devices::findFirstEndpoint(uint16_t shortaddr) const {
return device.endpoints[0]; // returns 0x00 if no endpoint
}
void Z_Devices::setManufId(uint16_t shortaddr, const char * str) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
void Z_Devices::setStringAttribute(char*& attr, const char * str) {
size_t str_len = str ? strlen(str) : 0; // len, handle both null ptr and zero length string
if ((!device.manufacturerId) && (0 == str_len)) { return; } // if both empty, don't do anything
if (device.manufacturerId) {
if ((nullptr == attr) && (0 == str_len)) { return; } // if both empty, don't do anything
if (attr) {
// we already have a value
if (strcmp(device.manufacturerId, str) != 0) {
if (strcmp(attr, str) != 0) {
// new value
free(device.manufacturerId); // free previous value
device.manufacturerId = nullptr;
free(attr); // free previous value
attr = nullptr;
} else {
return; // same value, don't change anything
}
}
if (str_len) {
device.manufacturerId = (char*) malloc(str_len + 1);
strlcpy(device.manufacturerId, str, str_len + 1);
attr = (char*) malloc(str_len + 1);
strlcpy(attr, str, str_len + 1);
}
dirty();
}
//
// Sets the ManufId for a device.
// No action taken if the device does not exist.
// Inputs:
// - shortaddr: 16-bits short address of the device. No action taken if the device is unknown
// - str: string pointer, if nullptr it is considered as empty string
// Impact:
// - Any actual change in ManufId (i.e. setting a different value) triggers a `dirty()` and saving to Flash
//
void Z_Devices::setManufId(uint16_t shortaddr, const char * str) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
setStringAttribute(device.manufacturerId, str);
}
void Z_Devices::setModelId(uint16_t shortaddr, const char * str) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
size_t str_len = str ? strlen(str) : 0; // len, handle both null ptr and zero length string
if ((!device.modelId) && (0 == str_len)) { return; } // if both empty, don't do anything
if (device.modelId) {
// we already have a value
if (strcmp(device.modelId, str) != 0) {
// new value
free(device.modelId); // free previous value
device.modelId = nullptr;
} else {
return; // same value, don't change anything
}
}
if (str_len) {
device.modelId = (char*) malloc(str_len + 1);
strlcpy(device.modelId, str, str_len + 1);
}
dirty();
setStringAttribute(device.modelId, str);
}
void Z_Devices::setFriendlyName(uint16_t shortaddr, const char * str) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
size_t str_len = str ? strlen(str) : 0; // len, handle both null ptr and zero length string
if ((!device.friendlyName) && (0 == str_len)) { return; } // if both empty, don't do anything
if (device.friendlyName) {
// we already have a value
if (strcmp(device.friendlyName, str) != 0) {
// new value
free(device.friendlyName); // free previous value
device.friendlyName = nullptr;
} else {
return; // same value, don't change anything
}
}
if (str_len) {
device.friendlyName = (char*) malloc(str_len + 1);
strlcpy(device.friendlyName, str, str_len + 1);
}
dirty();
setStringAttribute(device.friendlyName, str);
}
const char * Z_Devices::getFriendlyName(uint16_t shortaddr) const {
@ -567,6 +589,12 @@ const char * Z_Devices::getModelId(uint16_t shortaddr) const {
return nullptr;
}
void Z_Devices::setReachable(uint16_t shortaddr, bool reachable) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
bitWrite(device.power, 7, reachable);
}
// get the next sequance number for the device, or use the global seq number if device is unknown
uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) {
int32_t short_found = findShortAddr(shortaddr);
@ -600,12 +628,13 @@ int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr) const {
// Hue support
void Z_Devices::updateHueState(uint16_t shortaddr,
const uint8_t *power, const uint8_t *colormode,
const bool *power, const uint8_t *colormode,
const uint8_t *dimmer, const uint8_t *sat,
const uint16_t *ct, const uint16_t *hue,
const uint16_t *x, const uint16_t *y) {
const uint16_t *x, const uint16_t *y,
const bool *reachable) {
Z_Device &device = getShortAddr(shortaddr);
if (power) { device.power = *power; }
if (power) { bitWrite(device.power, 0, *power); }
if (colormode){ device.colormode = *colormode; }
if (dimmer) { device.dimmer = *dimmer; }
if (sat) { device.sat = *sat; }
@ -613,18 +642,20 @@ void Z_Devices::updateHueState(uint16_t shortaddr,
if (hue) { device.hue = *hue; }
if (x) { device.x = *x; }
if (y) { device.y = *y; }
if (reachable){ bitWrite(device.power, 7, *reachable); }
}
// return true if ok
bool Z_Devices::getHueState(uint16_t shortaddr,
uint8_t *power, uint8_t *colormode,
bool *power, uint8_t *colormode,
uint8_t *dimmer, uint8_t *sat,
uint16_t *ct, uint16_t *hue,
uint16_t *x, uint16_t *y) const {
uint16_t *x, uint16_t *y,
bool *reachable) const {
int32_t found = findShortAddr(shortaddr);
if (found >= 0) {
const Z_Device &device = *(_devices[found]);
if (power) { *power = device.power; }
if (power) { *power = bitRead(device.power, 0); }
if (colormode){ *colormode = device.colormode; }
if (dimmer) { *dimmer = device.dimmer; }
if (sat) { *sat = device.sat; }
@ -632,6 +663,7 @@ bool Z_Devices::getHueState(uint16_t shortaddr,
if (hue) { *hue = device.hue; }
if (x) { *x = device.x; }
if (y) { *y = device.y; }
if (reachable){ *reachable = bitRead(device.power, 7); }
return true;
} else {
return false;
@ -935,7 +967,8 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const {
dev[F(D_JSON_ZIGBEE_LIGHT)] = device.bulbtype; // sign extend, 0xFF changed as -1
if (0 <= device.bulbtype) {
// bulbtype is defined
dev[F("Power")] = device.power;
dev[F("Power")] = bitRead(device.power, 0);
dev[F("Reachable")] = bitRead(device.power, 7);
if (1 <= device.bulbtype) {
dev[F("Dimmer")] = device.dimmer;
}
@ -994,6 +1027,9 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
if (device.modelId) {
dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId;
}
if (device.bulbtype >= 0) {
dev[F(D_JSON_ZIGBEE_LIGHT)] = device.bulbtype; // sign extend, 0xFF changed as -1
}
if (device.manufacturerId) {
dev[F("Manufacturer")] = device.manufacturerId;
}
@ -1013,4 +1049,77 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
return payload;
}
// Restore a single device configuration based on json export
// Input: json element as expported by `ZbStatus2``
// Mandatory attribue: `Device`
//
// Returns:
// 0 : Ok
// <0 : Error
//
// Ex: {"Device":"0x5ADF","Name":"IKEA_Light","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
int32_t Z_Devices::deviceRestore(const JsonObject &json) {
// params
uint16_t device = 0x0000; // 0x0000 is coordinator so considered invalid
uint64_t ieeeaddr = 0x0000000000000000LL; // 0 means unknown
const char * modelid = nullptr;
const char * manufid = nullptr;
const char * friendlyname = nullptr;
int8_t bulbtype = 0xFF;
size_t endpoints_len = 0;
// read mandatory "Device"
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = strToUInt(val_device);
} else {
return -1; // missing "Device" attribute
}
// read "IEEEAddr" 64 bits in format "0x0000000000000000"
const JsonVariant &val_ieeeaddr = getCaseInsensitive(json, PSTR("IEEEAddr"));
if (nullptr != &val_ieeeaddr) {
ieeeaddr = strtoull(val_ieeeaddr.as<const char*>(), nullptr, 0);
}
// read "Name"
friendlyname = getCaseInsensitiveConstCharNull(json, PSTR("Name"));
// read "ModelId"
modelid = getCaseInsensitiveConstCharNull(json, PSTR("ModelId"));
// read "Manufacturer"
manufid = getCaseInsensitiveConstCharNull(json, PSTR("Manufacturer"));
// read "Light"
const JsonVariant &val_bulbtype = getCaseInsensitive(json, PSTR(D_JSON_ZIGBEE_LIGHT));
if (nullptr != &val_bulbtype) { bulbtype = strToUInt(val_bulbtype);; }
// update internal device information
updateDevice(device, ieeeaddr);
if (modelid) { setModelId(device, modelid); }
if (manufid) { setManufId(device, manufid); }
if (friendlyname) { setFriendlyName(device, friendlyname); }
if (&val_bulbtype) { setHueBulbtype(device, bulbtype); }
// read "Endpoints"
const JsonVariant &val_endpoints = getCaseInsensitive(json, PSTR("Endpoints"));
if ((nullptr != &val_endpoints) && (val_endpoints.is<JsonArray>())) {
const JsonArray &arr_ep = val_endpoints.as<const JsonArray&>();
endpoints_len = arr_ep.size();
clearEndpoints(device); // clear even if array is empty
if (endpoints_len) {
for (auto ep_elt : arr_ep) {
uint8_t ep = strToUInt(ep_elt);
if (ep) {
addEndpoint(device, ep);
}
}
}
}
return 0;
}
#endif // USE_ZIGBEE

View File

@ -24,23 +24,29 @@
// idx: index in the list of zigbee_devices
void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, String *response) {
uint8_t power, colormode, bri, sat;
static const char HUE_LIGHTS_STATUS_JSON1_SUFFIX_ZIGBEE[] PROGMEM =
"%s\"alert\":\"none\","
"\"effect\":\"none\","
"\"reachable\":%s}";
bool power, reachable;
uint8_t colormode, bri, sat;
uint16_t ct, hue;
uint16_t x, y;
String light_status = "";
uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
zigbee_devices.getHueState(shortaddr, &power, &colormode, &bri, &sat, &ct, &hue, &x, &y);
zigbee_devices.getHueState(shortaddr, &power, &colormode, &bri, &sat, &ct, &hue, &x, &y, &reachable);
if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
if (bri < 1) bri = 1;
if (sat > 254) sat = 254; // Philips Hue only accepts 254 as max hue
uint8_t hue8 = changeUIntScale(hue, 0, 360, 0, 254); // default hue is 0..254, we don't use extended hue
if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
if (bri < 1) bri = 1;
if (sat > 254) sat = 254; // Philips Hue only accepts 254 as max hue
uint16_t hue16 = changeUIntScale(hue, 0, 360, 0, 65535);
const size_t buf_size = 256;
char * buf = (char*) malloc(buf_size); // temp buffer for strings, avoid stack
snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), (power & 1) ? "true" : "false");
snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), power ? "true" : "false");
// Brightness for all devices with PWM
if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo
snprintf_P(buf, buf_size, PSTR("%s\"bri\":%d,"), buf, bri);
@ -56,12 +62,12 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri
float y_f = y / 65536.0f;
snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, String(x, 5).c_str(), String(y, 5).c_str());
}
snprintf_P(buf, buf_size, PSTR("%s\"hue\":%d,\"sat\":%d,"), buf, hue, sat);
snprintf_P(buf, buf_size, PSTR("%s\"hue\":%d,\"sat\":%d,"), buf, hue16, sat);
}
if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp
snprintf_P(buf, buf_size, PSTR("%s\"ct\":%d,"), buf, ct > 0 ? ct : 284);
}
snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON1_SUFFIX, buf);
snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON1_SUFFIX_ZIGBEE, buf, reachable ? "true" : "false");
*response += buf;
free(buf);
@ -123,46 +129,51 @@ void ZigbeeHueGroups(String * lights) {
// Send commands
// Power On/Off
void ZigbeeHuePower(uint16_t shortaddr, uint8_t power) {
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0006, power, "");
zigbee_devices.updateHueState(shortaddr, &power, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
void ZigbeeHuePower(uint16_t shortaddr, bool power) {
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0006, power ? 1 : 0, "");
zigbee_devices.updateHueState(shortaddr, &power, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
}
// Dimmer
void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t dimmer) {
if (dimmer > 0xFE) { dimmer = 0xFE; }
char param[8];
snprintf_P(param, sizeof(param), PSTR("%02X0A00"), dimmer);
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0008, 0x04, param);
zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, &dimmer, nullptr, nullptr, nullptr, nullptr, nullptr);
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0008, 0x04, param);
zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, &dimmer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
}
// CT
void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) {
if (ct > 0xFEFF) { ct = 0xFEFF; }
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeHueCT 0x%04X - %d"), shortaddr, ct);
char param[12];
snprintf_P(param, sizeof(param), PSTR("%02X%02X0A00"), ct & 0xFF, ct >> 8);
uint8_t colormode = 2; // "ct"
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0300, 0x0A, param);
zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, &ct, nullptr, nullptr, nullptr);
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x0A, param);
zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, &ct, nullptr, nullptr, nullptr, nullptr);
}
// XY
void ZigbeeHueXY(uint16_t shortaddr, uint16_t x, uint16_t y) {
char param[16];
if (x > 0xFEFF) { x = 0xFEFF; }
if (y > 0xFEFF) { y = 0xFEFF; }
snprintf_P(param, sizeof(param), PSTR("%02X%02X%02X%02X0A00"), x & 0xFF, x >> 8, y & 0xFF, y >> 8);
uint8_t colormode = 1; // "xy"
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0300, 0x07, param);
zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, nullptr, nullptr, &x, &y);
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x07, param);
zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, nullptr, nullptr, &x, &y, nullptr);
}
// HueSat
void ZigbeeHueHS(uint16_t shortaddr, uint16_t hue, uint8_t sat) {
char param[16];
uint8_t hue8 = changeUIntScale(hue, 0, 360, 0, 254);
snprintf_P(param, sizeof(param), PSTR("%02X%02X0A00"), hue8, sat);
if (sat > 0xFE) { sat = 0xFE; }
snprintf_P(param, sizeof(param), PSTR("%02X%02X0000"), hue8, sat);
uint8_t colormode = 0; // "hs"
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0300, 0x06, param);
zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, &sat, nullptr, &hue, nullptr, nullptr);
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x06, param);
zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, &sat, nullptr, &hue, nullptr, nullptr, nullptr);
}
void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
@ -245,8 +256,8 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
device_id, "hue", hue);
response += buf;
if (LST_RGB <= bulbtype) {
// change range from 0..65535 to 0..359
hue = changeUIntScale(hue, 0, 65535, 0, 359);
// change range from 0..65535 to 0..360
hue = changeUIntScale(hue, 0, 65535, 0, 360);
huesat_changed = true;
}
resp = true;

View File

@ -202,13 +202,6 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
case 0xFF: // unk
break;
case 0x10: // bool
{
uint8_t val_bool = buf.get8(i++);
if (0xFF != val_bool) {
json[attrid_str] = (bool) (val_bool ? true : false);
}
}
break;
case 0x20: // uint8
case 0x30: // enum8
{
@ -1215,38 +1208,38 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
// see if we need to update the Hue bulb status
if ((cluster == 0x0006) && ((attribute == 0x0000) || (attribute == 0x8000))) {
uint8_t power = value;
bool power = value;
zigbee_devices.updateHueState(shortaddr, &power, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr);
nullptr, nullptr, nullptr, nullptr, nullptr);
} else if ((cluster == 0x0008) && (attribute == 0x0000)) {
uint8_t dimmer = value;
zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, &dimmer, nullptr,
nullptr, nullptr, nullptr, nullptr);
nullptr, nullptr, nullptr, nullptr, nullptr);
} else if ((cluster == 0x0300) && (attribute == 0x0000)) {
uint16_t hue8 = value;
uint16_t hue = changeUIntScale(hue8, 0, 254, 0, 360); // change range from 0..254 to 0..360
zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr,
nullptr, &hue, nullptr, nullptr);
nullptr, &hue, nullptr, nullptr, nullptr);
} else if ((cluster == 0x0300) && (attribute == 0x0001)) {
uint8_t sat = value;
zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, &sat,
nullptr, nullptr, nullptr, nullptr);
nullptr, nullptr, nullptr, nullptr, nullptr);
} else if ((cluster == 0x0300) && (attribute == 0x0003)) {
uint16_t x = value;
zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, &x, nullptr);
nullptr, nullptr, &x, nullptr, nullptr);
} else if ((cluster == 0x0300) && (attribute == 0x0004)) {
uint16_t y = value;
zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, &y);
nullptr, nullptr, nullptr, &y, nullptr), nullptr;
} else if ((cluster == 0x0300) && (attribute == 0x0007)) {
uint16_t ct = value;
zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr,
&ct, nullptr, nullptr, nullptr);
&ct, nullptr, nullptr, nullptr, nullptr);
} else if ((cluster == 0x0300) && (attribute == 0x0008)) {
uint8_t colormode = value;
zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr);
nullptr, nullptr, nullptr, nullptr, nullptr);
}
// Iterate on filter

View File

@ -19,6 +19,10 @@
#ifdef USE_ZIGBEE
/*********************************************************************************************\
* ZCL Command Structures
\*********************************************************************************************/
typedef struct Z_CommandConverter {
const char * tasmota_cmd;
uint16_t cluster;
@ -130,6 +134,9 @@ const Z_CommandConverter Z_Commands[] PROGMEM = {
{ Z(GetSceneMembership),0x0005, 0x06, 0x82,Z(xxyyzzzz) }, // specific
};
/*********************************************************************************************\
* ZCL Read Light status based on cluster number
\*********************************************************************************************/
#define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian
// Below are the attributes we wand to read from each cluster
@ -162,7 +169,15 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
break;
}
if (attrs) {
ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr));
ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, 0, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr));
}
}
// This callback is registered after a an attribute read command was made to a light, and fires if we don't get any response after 1000 ms
int32_t Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
if (shortaddr) {
zigbee_devices.setReachable(shortaddr, false); // mark device as reachable
}
}
@ -185,6 +200,9 @@ void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
}
if (wait_ms) {
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
if (shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}
}
}
@ -288,11 +306,15 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
}
if (z_cat >= 0) {
uint8_t endpoint = 0;
if (!groupaddr) {
if (shortaddr) {
endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
}
if ((endpoint) || (groupaddr)) { // send only if we know the endpoint
if ((!shortaddr) || (endpoint)) { // send if group address or endpoint is known
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, z_cat, 0 /* value */, &Z_ReadAttrCallback);
if (shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}
}
}
}

View File

@ -20,7 +20,7 @@
#ifdef USE_ZIGBEE
// Status code used for ZigbeeStatus MQTT message
// Ex: {"ZigbeeStatus":{"Status": 3,"Message":"Configured, starting coordinator"}}
// Ex: {"ZbStatus":{"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
@ -33,7 +33,6 @@ 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_DEVICE_INDICATION = 34; // Device announces its address
//const uint8_t ZIGBEE_STATUS_DEVICE_IEEE = 35; // Request of device address
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
@ -50,9 +49,6 @@ typedef union Zigbee_Instruction {
} i;
const void *p; // pointer
} Zigbee_Instruction;
//
// Zigbee_Instruction z1 = { .i = {1,2,3}};
// Zigbee_Instruction z3 = { .p = nullptr };
typedef struct Zigbee_Instruction_Type {
uint8_t instr;
@ -60,7 +56,7 @@ typedef struct Zigbee_Instruction_Type {
} Zigbee_Instruction_Type;
enum Zigbee_StateMachine_Instruction_Set {
// 2 bytes instructions
// 4 bytes instructions
ZGB_INSTR_4_BYTES = 0,
ZGB_INSTR_NOOP = 0, // do nothing
ZGB_INSTR_LABEL, // define a label
@ -71,7 +67,7 @@ enum Zigbee_StateMachine_Instruction_Set {
ZGB_INSTR_WAIT_FOREVER, // wait forever but state machine still active
ZGB_INSTR_STOP, // stop state machine with optional error code
// 6 bytes instructions
// 8 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
@ -81,7 +77,7 @@ enum Zigbee_StateMachine_Instruction_Set {
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
// 12 bytes instructions
ZGB_INSTR_12_BYTES = 0xF0,
ZGB_INSTR_WAIT_RECV_CALL, // wait for a filtered message and call function upon receive
};
@ -488,7 +484,6 @@ void ZigbeeStateMachine_Run(void) {
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(D_LOG_ZIGBEE "timeout occured pc=%d"), zigbee.pc);
if (!zigbee.state_no_timeout) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "timeout, goto label %d"), zigbee.on_timeout_goto);
ZigbeeGotoLabel(zigbee.on_timeout_goto);
@ -505,7 +500,6 @@ void ZigbeeStateMachine_Run(void) {
zigbee.recv_until = false;
zigbee.state_no_timeout = false; // reset the no_timeout for next instruction
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeStateMachine_Run PC = %d, Mem1 = %d"), zigbee.pc, ESP.getFreeHeap());
if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Invalid pc: %d, aborting"), zigbee.pc);
zigbee.pc = -1;
@ -516,7 +510,6 @@ void ZigbeeStateMachine_Run(void) {
}
// load current instruction details
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "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);
@ -553,7 +546,6 @@ void ZigbeeStateMachine_Run(void) {
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;
@ -617,4 +609,82 @@ void ZigbeeStateMachine_Run(void) {
}
}
//
// Process a bytes buffer and call any callback that matches the received message
//
int32_t ZigbeeProcessInput(class SBuffer &buf) {
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
// apply the receive filter, acts as 'startsWith()'
bool recv_filter_match = true;
bool recv_prefix_match = false; // do the first 2 bytes match the response
if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) {
if (zigbee.recv_filter_len >= 2) {
recv_prefix_match = false;
if ( (pgm_read_byte(&zigbee.recv_filter[0]) == buf.get8(0)) &&
(pgm_read_byte(&zigbee.recv_filter[1]) == buf.get8(1)) ) {
recv_prefix_match = true;
}
}
for (uint32_t i = 0; i < zigbee.recv_filter_len; i++) {
if (pgm_read_byte(&zigbee.recv_filter[i]) != buf.get8(i)) {
recv_filter_match = false;
break;
}
}
}
// if there is a recv_callback, call it now
int32_t res = -1; // default to ok
// res = 0 - proceed to next state
// res > 0 - proceed to the specified state
// res = -1 - silently ignore the message
// res <= -2 - move to error state
// pre-compute the suggested value
if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) {
if (!recv_prefix_match) {
res = -1; // ignore
} else { // recv_prefix_match
if (recv_filter_match) {
res = 0; // ok
} else {
if (zigbee.recv_until) {
res = -1; // ignore until full match
} else {
res = -2; // error, because message is expected but wrong value
}
}
}
} else { // we don't have any filter, ignore message by default
res = -1;
}
if (recv_prefix_match) {
if (zigbee.recv_func) {
res = (*zigbee.recv_func)(res, buf);
}
}
if (-1 == res) {
// if frame was ignored up to now
if (zigbee.recv_unexpected) {
res = (*zigbee.recv_unexpected)(res, buf);
}
}
// change state accordingly
if (0 == res) {
// if ok, continue execution
zigbee.state_waiting = false;
} else if (res > 0) {
ZigbeeGotoLabel(res); // if >0 then go to specified label
} else if (-1 == res) {
// -1 means ignore message
// just do nothing
} else {
// any other negative value means error
ZigbeeGotoLabel(zigbee.on_error_goto);
}
}
#endif // USE_ZIGBEE

View File

@ -19,6 +19,13 @@
#ifdef USE_ZIGBEE
/*********************************************************************************************\
* Parsers for incoming ZNP messages
\*********************************************************************************************/
//
// Handle a "Receive Device Info" incoming message
//
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
// Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991
// IEEE Adr (8 bytes) = 0x00124B001D156362
@ -76,12 +83,12 @@ int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) {
}
}
const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog";
int32_t Z_Reboot(int32_t res, class SBuffer &buf) {
// print information about the reboot of device
// 4180.02.02.00.02.06.03
//
static const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog";
uint8_t reason = buf.get8(2);
uint8_t transport_rev = buf.get8(3);
uint8_t product_id = buf.get8(4);
@ -140,6 +147,9 @@ int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
}
}
//
// Helper function, checks if the incoming buffer matches the 2-bytes prefix, i.e. message type in PMEM
//
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)) ) {
@ -149,6 +159,9 @@ bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) {
}
}
//
// Handle Permit Join response
//
int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
// we received a PermitJoin status change
uint8_t duration = buf.get8(2);
@ -176,22 +189,6 @@ int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
return -1;
}
// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address
void Z_SendIEEEAddrReq(uint16_t shortaddr) {
uint8_t IEEEAddrReq[] = { Z_SREQ | Z_ZDO, ZDO_IEEE_ADDR_REQ,
Z_B0(shortaddr), Z_B1(shortaddr), 0x00, 0x00 };
ZigbeeZNPSend(IEEEAddrReq, sizeof(IEEEAddrReq));
}
// 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) };
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
}
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
@ -226,6 +223,9 @@ int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
return -1;
}
//
// Porcess Receive Active Endpoint
//
int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
// Received ZDO_ACTIVE_EP_RSP
Z_ShortAddress srcAddr = buf.get16(2);
@ -254,37 +254,14 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
return -1;
}
void Z_SendAFInfoRequest(uint16_t shortaddr) {
uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
if (0x00 == endpoint) { endpoint = 0x01; } // if we don't know the endpoint, try 0x01
uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr);
SBuffer buf(100);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST); // 01
buf.add16(shortaddr);
buf.add8(endpoint); // dest endpoint
buf.add8(0x01); // source endpoint
buf.add16(0x0000);
buf.add8(transacid);
buf.add8(0x30); // 30 options
buf.add8(0x1E); // 1E radius
buf.add8(3 + 2*sizeof(uint16_t)); // Len = 0x07
buf.add8(0x00); // Frame Control Field
buf.add8(transacid); // Transaction Sequence Number
buf.add8(ZCL_READ_ATTRIBUTES); // 00 Command
buf.add16(0x0004); // 0400 ManufacturerName
buf.add16(0x0005); // 0500 ModelIdentifier
ZigbeeZNPSend(buf.getBuffer(), buf.len());
}
//
// Handle IEEEAddr incoming message
//
int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) {
uint8_t status = buf.get8(2);
Z_IEEEAddress ieeeAddr = buf.get64(3);
Z_ShortAddress nwkAddr = buf.get16(11);
// uint8_t startIndex = buf.get8(13);
// uint8_t startIndex = buf.get8(13); // not used
// uint8_t numAssocDev = buf.get8(14);
if (0 == status) { // SUCCESS
@ -308,34 +285,6 @@ int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) {
}
return -1;
}
int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) {
Z_ShortAddress nwkAddr = buf.get16(2);
uint8_t status = buf.get8(4);
char status_message[32];
strncpy_P(status_message, (const char*) getZigbeeStatusMessage(status), sizeof(status_message));
status_message[sizeof(status_message)-1] = 0; // truncate if needed, strlcpy is safer but strlcpy_P does not exist
const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
if (friendlyName) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, friendlyName, status, status_message);
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, status, status_message);
}
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
return -1;
}
//
// Report any AF_DATA_CONFIRM message
// Ex: {"ZbConfirm":{"Endpoint":1,"Status":0,"StatusMessage":"SUCCESS"}}
@ -343,17 +292,13 @@ int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) {
int32_t Z_DataConfirm(int32_t res, const class SBuffer &buf) {
uint8_t status = buf.get8(2);
uint8_t endpoint = buf.get8(3);
//uint8_t transId = buf.get8(4);
char status_message[32];
//uint8_t transId = buf.get8(4); // unused
if (status) { // only report errors
strncpy_P(status_message, (const char*) getZigbeeStatusMessage(status), sizeof(status_message));
status_message[sizeof(status_message)-1] = 0; // truncate if needed, strlcpy is safer but strlcpy_P does not exist
Response_P(PSTR("{\"" D_JSON_ZIGBEE_CONFIRM "\":{\"" D_CMND_ZIGBEE_ENDPOINT "\":%d"
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), endpoint, status, status_message);
"}}"), endpoint, status, getZigbeeStatusMessage(status).c_str());
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
}
@ -361,6 +306,11 @@ int32_t Z_DataConfirm(int32_t res, const class SBuffer &buf) {
return -1;
}
//
// Handle Receive End Device Announce incoming message
// This message is also received when a previously paired device is powered up
// Send back Active Ep Req message
//
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
Z_ShortAddress srcAddr = buf.get16(2);
Z_ShortAddress nwkAddr = buf.get16(4);
@ -379,6 +329,9 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
(capabilities & 0x08) ? "true" : "false",
(capabilities & 0x40) ? "true" : "false"
);
// query the state of the bulb (for Alexa)
uint32_t wait_ms = 2000; // wait for 2s
Z_Query_Bulb(nwkAddr, wait_ms);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
@ -386,7 +339,10 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
return -1;
}
//
// Handle Receive TC Dev Ind incoming message
// 45CA
//
int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) {
Z_ShortAddress srcAddr = buf.get16(2);
Z_IEEEAddress ieeeAddr = buf.get64(4);
@ -404,15 +360,174 @@ int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) {
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
//Z_SendActiveEpReq(srcAddr);
return -1;
}
//
// Handle Bind Rsp incoming message
//
int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) {
Z_ShortAddress nwkAddr = buf.get16(2);
uint8_t status = buf.get8(4);
const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
if (friendlyName) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, friendlyName, status, getZigbeeStatusMessage(status).c_str());
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, status, getZigbeeStatusMessage(status).c_str());
}
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
return -1;
}
//
// Handle Unbind Rsp incoming message
//
int32_t Z_UnbindRsp(int32_t res, const class SBuffer &buf) {
Z_ShortAddress nwkAddr = buf.get16(2);
uint8_t status = buf.get8(4);
const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
if (friendlyName) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_UNBIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, friendlyName, status, getZigbeeStatusMessage(status).c_str());
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_UNBIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, status, getZigbeeStatusMessage(status).c_str());
}
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
return -1;
}
//
// Handle MgMt Bind Rsp incoming message
//
int32_t Z_MgmtBindRsp(int32_t res, const class SBuffer &buf) {
uint16_t shortaddr = buf.get16(2);
uint8_t status = buf.get8(4);
uint8_t bind_total = buf.get8(5);
uint8_t bind_start = buf.get8(6);
uint8_t bind_len = buf.get8(7);
const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND_STATE "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), shortaddr);
if (friendlyName) {
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName);
}
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
",\"BindingsTotal\":%d"
//",\"BindingsStart\":%d"
",\"Bindings\":["
), status, getZigbeeStatusMessage(status).c_str(), bind_total);
uint32_t idx = 8;
for (uint32_t i = 0; i < bind_len; i++) {
if (idx + 14 > buf.len()) { break; } // overflow, frame size is between 14 and 21
//uint64_t srcaddr = buf.get16(idx); // unused
uint8_t srcep = buf.get8(idx + 8);
uint8_t cluster = buf.get16(idx + 9);
uint8_t addrmode = buf.get8(idx + 11);
uint16_t group = 0x0000;
uint64_t dstaddr = 0;
uint8_t dstep = 0x00;
if (Z_Addr_Group == addrmode) { // Group address mode
group = buf.get16(idx + 12);
idx += 14;
} else if (Z_Addr_IEEEAddress == addrmode) { // IEEE address mode
dstaddr = buf.get64(idx + 12);
dstep = buf.get8(idx + 20);
idx += 21;
} else {
//AddLog_P2(LOG_LEVEL_INFO, PSTR("Z_MgmtBindRsp unknwon address mode %d"), addrmode);
break; // abort for any other value since we don't know the length of the field
}
if (i > 0) {
ResponseAppend_P(PSTR(","));
}
ResponseAppend_P(PSTR("{\"Cluster\":\"0x%04X\",\"Endpoint\":%d,"), cluster, srcep);
if (Z_Addr_Group == addrmode) { // Group address mode
ResponseAppend_P(PSTR("\"ToGroup\":%d}"), group);
} else if (Z_Addr_IEEEAddress == addrmode) { // IEEE address mode
char hex[20];
Uint64toHex(dstaddr, hex, 64);
ResponseAppend_P(PSTR("\"ToDevice\":\"0x%s\",\"ToEndpoint\":%d}"), hex, dstep);
}
}
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_BIND_STATE));
XdrvRulesProcess();
return -1;
}
/*********************************************************************************************\
* Send specific ZNP messages
\*********************************************************************************************/
//
// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address
//
void Z_SendIEEEAddrReq(uint16_t shortaddr) {
uint8_t IEEEAddrReq[] = { Z_SREQ | Z_ZDO, ZDO_IEEE_ADDR_REQ, Z_B0(shortaddr), Z_B1(shortaddr), 0x00, 0x00 };
ZigbeeZNPSend(IEEEAddrReq, sizeof(IEEEAddrReq));
}
//
// 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) };
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
}
//
// Send AF Info Request
//
void Z_SendAFInfoRequest(uint16_t shortaddr) {
uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
if (0x00 == endpoint) { endpoint = 0x01; } // if we don't know the endpoint, try 0x01
uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr);
uint8_t AFInfoReq[] = { Z_SREQ | Z_AF, AF_DATA_REQUEST, Z_B0(shortaddr), Z_B1(shortaddr), endpoint,
0x01, 0x00, 0x00, transacid, 0x30, 0x1E, 3 + 2*sizeof(uint16_t),
0x00, transacid, ZCL_READ_ATTRIBUTES, 0x04, 0x00, 0x05, 0x00
};
ZigbeeZNPSend(AFInfoReq, sizeof(AFInfoReq));
}
/*********************************************************************************************\
* Callbacks
\*********************************************************************************************/
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint8_t endpoint, const JsonObject *json) {
static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
// Read OCCUPANCY value if any
const JsonVariant &val_endpoint = getCaseInsensitive(*json, PSTR(OCCUPANCY));
if (nullptr != &val_endpoint) {
@ -436,6 +551,10 @@ int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t clu
return 1;
}
/*********************************************************************************************\
* Global dispatcher for incoming messages
\*********************************************************************************************/
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
uint16_t groupid = buf.get16(2);
uint16_t clusterid = buf.get16(4);
@ -490,6 +609,10 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
// Add linkquality
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
// since we just receveived data from the device, it is reachable
zigbee_devices.resetTimersForDevice(srcaddr, 0 /* groupaddr */, Z_CAT_REACHABILITY); // remove any reachability timer already there
zigbee_devices.setReachable(srcaddr, true); // mark device as reachable
if (defer_attributes) {
// Prepare for publish
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
@ -506,22 +629,26 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
return -1;
}
// Structure for the Dispatcher callbacks table
typedef struct Z_Dispatcher {
const uint8_t* match;
ZB_RecvMsgFunc func;
} Z_Dispatcher;
// Filters for ZCL frames
ZBM(AREQ_AF_DATA_CONFIRM, Z_AREQ | Z_AF, AF_DATA_CONFIRM) // 4480
ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA
ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
ZBM(AREQ_ZDO_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) // 4581
ZBM(AREQ_ZDO_BIND_RSP, Z_AREQ | Z_ZDO, ZDO_BIND_RSP) // 45A1
// Ffilters based on ZNP frames
ZBM(AREQ_AF_DATA_CONFIRM, Z_AREQ | Z_AF, AF_DATA_CONFIRM) // 4480
ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA
ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
ZBM(AREQ_ZDO_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) // 4581
ZBM(AREQ_ZDO_BIND_RSP, Z_AREQ | Z_ZDO, ZDO_BIND_RSP) // 45A1
ZBM(AREQ_ZDO_UNBIND_RSP, Z_AREQ | Z_ZDO, ZDO_UNBIND_RSP) // 45A2
ZBM(AREQ_ZDO_MGMT_BIND_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_BIND_RSP) // 45B3
// Dispatcher callbacks table
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
{ AREQ_AF_DATA_CONFIRM, &Z_DataConfirm },
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
@ -532,8 +659,14 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
{ AREQ_ZDO_IEEE_ADDR_RSP, &Z_ReceiveIEEEAddr },
{ AREQ_ZDO_BIND_RSP, &Z_BindRsp },
{ AREQ_ZDO_UNBIND_RSP, &Z_UnbindRsp },
{ AREQ_ZDO_MGMT_BIND_RSP, &Z_MgmtBindRsp },
};
/*********************************************************************************************\
* Default resolver
\*********************************************************************************************/
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
// Default message handler for new messages
if (zigbee.init_phase) {
@ -549,45 +682,57 @@ int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
}
}
/*********************************************************************************************\
* Functions called by State Machine
\*********************************************************************************************/
//
// Callback for loading Zigbee configuration from Flash, called by the state machine
//
int32_t Z_Load_Devices(uint8_t value) {
// try to hidrate from known devices
loadZigbeeDevices();
return 0; // continue
}
//
// Query the state of a bulb (light) if its type allows it
//
void Z_Query_Bulb(uint16_t shortaddr, uint32_t &wait_ms) {
const uint32_t inter_message_ms = 100; // wait 100ms between messages
if (0 <= zigbee_devices.getHueBulbtype(shortaddr)) {
uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
if (endpoint) { // send only if we know the endpoint
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0006, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0008, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0300, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, 0, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
wait_ms += 1000; // wait 1 second between devices
}
}
}
//
// Send messages to query the state of each Hue emulated light
//
int32_t Z_Query_Bulbs(uint8_t value) {
// Scan all devices and send deferred requests to know the state of bulbs
uint32_t wait_ms = 1000; // start with 1.0 s delay
const uint32_t inter_message_ms = 100; // wait 100ms between messages
for (uint32_t i = 0; i < zigbee_devices.devicesSize(); i++) {
const Z_Device &device = zigbee_devices.devicesAt(i);
if (0 <= device.bulbtype) {
uint16_t cluster;
uint8_t endpoint = zigbee_devices.findFirstEndpoint(device.shortaddr);
cluster = 0x0006;
if (endpoint) { // send only if we know the endpoint
zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
}
cluster = 0x0008;
if (endpoint) { // send only if we know the endpoint
zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
}
cluster = 0x0300;
if (endpoint) { // send only if we know the endpoint
zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
}
}
Z_Query_Bulb(device.shortaddr, wait_ms);
}
return 0; // continue
}
//
// Zigbee initialization is complete, let the party begin
//
int32_t Z_State_Ready(uint8_t value) {
zigbee.init_phase = false; // initialization phase complete
return 0; // continue

View File

@ -34,8 +34,8 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|"
D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|"
D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|"
D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|"
D_CMND_ZIGBEE_LIGHT
D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|"
D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE
;
void (* const ZigbeeCommand[])(void) PROGMEM = {
@ -43,89 +43,14 @@ void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZbStatus, &CmndZbReset, &CmndZbSend,
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
&CmndZbForget, &CmndZbSave, &CmndZbName,
&CmndZbBind, &CmndZbPing, &CmndZbModelId,
&CmndZbLight,
&CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId,
&CmndZbLight, &CmndZbRestore, &CmndZbBindState,
};
int32_t ZigbeeProcessInput(class SBuffer &buf) {
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
// apply the receive filter, acts as 'startsWith()'
bool recv_filter_match = true;
bool recv_prefix_match = false; // do the first 2 bytes match the response
if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) {
if (zigbee.recv_filter_len >= 2) {
recv_prefix_match = false;
if ( (pgm_read_byte(&zigbee.recv_filter[0]) == buf.get8(0)) &&
(pgm_read_byte(&zigbee.recv_filter[1]) == buf.get8(1)) ) {
recv_prefix_match = true;
}
}
for (uint32_t i = 0; i < zigbee.recv_filter_len; i++) {
if (pgm_read_byte(&zigbee.recv_filter[i]) != buf.get8(i)) {
recv_filter_match = false;
break;
}
}
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZbProcessInput: recv_prefix_match = %d, recv_filter_match = %d"), recv_prefix_match, recv_filter_match);
}
// if there is a recv_callback, call it now
int32_t res = -1; // default to ok
// res = 0 - proceed to next state
// res > 0 - proceed to the specified state
// res = -1 - silently ignore the message
// res <= -2 - move to error state
// pre-compute the suggested value
if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) {
if (!recv_prefix_match) {
res = -1; // ignore
} else { // recv_prefix_match
if (recv_filter_match) {
res = 0; // ok
} else {
if (zigbee.recv_until) {
res = -1; // ignore until full match
} else {
res = -2; // error, because message is expected but wrong value
}
}
}
} else { // we don't have any filter, ignore message by default
res = -1;
}
if (recv_prefix_match) {
if (zigbee.recv_func) {
res = (*zigbee.recv_func)(res, buf);
}
}
if (-1 == res) {
// if frame was ignored up to now
if (zigbee.recv_unexpected) {
res = (*zigbee.recv_unexpected)(res, buf);
}
}
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZbProcessInput: res = %d"), res);
// change state accordingly
if (0 == res) {
// if ok, continue execution
zigbee.state_waiting = false;
} else if (res > 0) {
ZigbeeGotoLabel(res); // if >0 then go to specified label
} else if (-1 == res) {
// -1 means ignore message
// just do nothing
} else {
// any other negative value means error
ZigbeeGotoLabel(zigbee.on_error_goto);
}
}
void ZigbeeInput(void)
//
// Called at event loop, checks for incoming data from the CC2530
//
void ZigbeeInputLoop(void)
{
static uint32_t zigbee_polling_window = 0;
static uint8_t fcs = ZIGBEE_SOF;
@ -217,6 +142,7 @@ void ZigbeeInput(void)
/********************************************************************************************/
// Initialize internal structures
void ZigbeeInit(void)
{
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem1 = %d"), ESP.getFreeHeap());
@ -260,10 +186,10 @@ uint32_t strToUInt(const JsonVariant &val) {
return 0; // couldn't parse anything
}
// Do a factory reset of the CC2530
const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM =
{ Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x01 /* STARTOPT_CLEAR_CONFIG */};
//"2605030101"; // Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 len, 0x01 STARTOPT_CLEAR_CONFIG
// Do a factory reset of the CC2530
void CmndZbReset(void) {
if (ZigbeeSerial) {
switch (XdrvMailbox.payload) {
@ -279,6 +205,10 @@ void CmndZbReset(void) {
}
}
//
// Same code for `ZbZNPSend` and `ZbZNPReceive`
// building the complete message (intro, length)
//
void CmndZbZNPSendOrReceive(bool send)
{
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
@ -298,8 +228,10 @@ void CmndZbZNPSendOrReceive(bool send)
codes += 2;
}
if (send) {
// Command was `ZbZNPSend`
ZigbeeZNPSend(buf.getBuffer(), buf.len());
} else {
// Command was `ZbZNPReceive`
ZigbeeProcessInput(buf);
}
}
@ -347,12 +279,27 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
}
void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) {
//
// Internal function, send the low-level frame
// Input:
// - shortaddr: 16-bits short address, or 0x0000 if group address
// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr
// - clusterIf: 16-bits cluster number
// - endpoint: 8-bits target endpoint (source is always 0x01), unused for group addresses. Should not be 0x00 except when sending to group address.
// - cmdId: 8-bits ZCL command number
// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL
// - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr
// - len: length of the 'msg' payload
// - needResponse: boolean, true = we ask the target to respond, false = the target should not respond
// - transacId: 8-bits, transation id of message (should be incremented at each message), used both for Zigbee message number and ZCL message number
// Returns: None
//
void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, uint16_t manuf, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) {
SBuffer buf(32+len);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST_EXT); // 02
if (groupaddr) {
if (0x0000 == shortaddr) { // if no shortaddr we assume group address
buf.add8(Z_Addr_Group); // 01
buf.add64(groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded
buf.add8(0xFF); // dest endpoint is not used for group addresses
@ -368,8 +315,11 @@ void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterI
buf.add8(0x30); // 30 options
buf.add8(0x1E); // 1E radius
buf.add16(3 + len);
buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00)); // Frame Control Field
buf.add16(3 + len + (manuf ? 2 : 0));
buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field
if (manuf) {
buf.add16(manuf); // add Manuf Id if not null
}
buf.add8(transacId); // Transaction Sequance Number
buf.add8(cmdId);
if (len > 0) {
@ -379,8 +329,23 @@ void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterI
ZigbeeZNPSend(buf.getBuffer(), buf.len());
}
// Send a command specified as an HEX string for the workload
void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, bool clusterSpecific,
/********************************************************************************************/
//
// High-level function
// Send a command specified as an HEX string for the workload.
// The target endpoint is computed if zero, i.e. sent to the first known endpoint of the device.
// If cluster-specific, a timer may be set calling `zigbeeSetCommandTimer()`, for ex to coalesce attributes or Aqara presence sensor
//
// Inputs:
// - shortaddr: 16-bits short address, or 0x0000 if group address
// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr
// - endpoint: 8-bits target endpoint (source is always 0x01), if 0x00, it will be guessed from ZbStatus information (basically the first endpoint of the device)
// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL
// - clusterIf: 16-bits cluster number
// - param: pointer to HEX string for payload, should not be nullptr
// Returns: None
//
void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, bool clusterSpecific, uint16_t manuf,
uint16_t cluster, uint8_t cmd, const char *param) {
size_t size = param ? strlen(param) : 0;
SBuffer buf((size+2)/2); // actual bytes buffer for data
@ -395,46 +360,50 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
if ((0 == endpoint) && (shortaddr)) {
// endpoint is not specified, let's try to find it from shortAddr, unless it's a group address
endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
shortaddr, groupaddr, cluster, endpoint, cmd, param);
if ((0 == endpoint) && (shortaddr)) {
if ((0 == endpoint) && (shortaddr)) { // endpoint null is ok for group address
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
return;
} // endpoint null is ok for group address
}
// everything is good, we can send the command
ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len(), true, zigbee_devices.getNextSeqNumber(shortaddr));
ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, cmd, clusterSpecific, manuf, buf.getBuffer(), buf.len(), true, zigbee_devices.getNextSeqNumber(shortaddr));
// now set the timer, if any, to read back the state later
if (clusterSpecific) {
zigbeeSetCommandTimer(shortaddr, groupaddr, cluster, endpoint);
}
}
//
// Command `ZbSend`
//
void CmndZbSend(void) {
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":null} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":false} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} }
// ZigbeeSend { "devicse":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":null} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":false} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} }
// ZbSend { "devicse":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} }
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data);
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params
static char delim[] = ", "; // delimiters for parameters
uint16_t device = 0x0000; // 0xFFFF is broadcast, so considered valid
uint16_t groupaddr = 0x0000; // ignore group address if 0x0000
uint16_t device = 0x0000; // 0x0000 is local, so considered invalid
uint16_t groupaddr = 0x0000; // group address
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
uint16_t manuf = 0x0000; // Manuf Id in ZCL frame
// Command elements
uint16_t cluster = 0;
uint8_t cmd = 0;
@ -443,19 +412,25 @@ void CmndZbSend(void) {
bool clusterSpecific = true;
// parse JSON
const JsonVariant &val_group = getCaseInsensitive(json, PSTR("Group"));
if (nullptr != &val_group) { groupaddr = strToUInt(val_group); }
if (0x0000 == groupaddr) { // if no group address, we need a device address
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (0x0000 == device) { // if not found, check if we have a group
const JsonVariant &val_group = getCaseInsensitive(json, PSTR("Group"));
if (nullptr != &val_group) {
groupaddr = strToUInt(val_group);
} else { // no device nor group
ResponseCmndChar_P(PSTR("Unknown device"));
return;
}
if ((nullptr == &val_device) || (0x0000 == device)) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
}
const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint"));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
const JsonVariant &val_manuf = getCaseInsensitive(json, PSTR("Manuf"));
if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); }
const JsonVariant &val_cmd = getCaseInsensitive(json, PSTR("Send"));
if (nullptr != &val_cmd) {
// probe the type of the argument
@ -463,16 +438,16 @@ void CmndZbSend(void) {
// If String, it's a low level command
if (val_cmd.is<JsonObject>()) {
// we have a high-level command
JsonObject &cmd_obj = val_cmd.as<JsonObject&>();
const JsonObject &cmd_obj = val_cmd.as<const JsonObject&>();
int32_t cmd_size = cmd_obj.size();
if (cmd_size > 1) {
Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size);
return;
} else if (1 == cmd_size) {
// We have exactly 1 command, parse it
JsonObject::iterator it = cmd_obj.begin(); // just get the first key/value
JsonObject::const_iterator it = cmd_obj.begin(); // just get the first key/value
String key = it->key;
JsonVariant& value = it->value;
const JsonVariant& value = it->value;
uint32_t x = 0, y = 0, z = 0;
uint16_t cmd_var;
@ -526,7 +501,7 @@ void CmndZbSend(void) {
} else {
// we have zero command, pass through until last error for missing command
}
} else if (val_cmd.is<char*>()) {
} else if (val_cmd.is<const char*>()) {
// low-level command
cmd_str = val_cmd.as<String>();
// Now parse the string to extract cluster, command, and payload
@ -558,22 +533,25 @@ void CmndZbSend(void) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%s\""),
device, groupaddr, endpoint, cluster, cmd, cmd_s);
zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, cluster, cmd, cmd_s);
zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, manuf, cluster, cmd, cmd_s);
ResponseCmndDone();
} else {
Response_P(PSTR("Missing zigbee 'Send'"));
return;
}
}
void CmndZbBind(void) {
// ZbBind { "device":"0x1234", "endpoint":1, "cluster":6 }
//
// Command `ZbBind`
//
void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
// ZbBind {"Device":"<device>", "Endpoint":<endpoint>, "Cluster":<cluster>, "ToDevice":"<to_device>", "ToEndpoint":<to_endpoint>, "ToGroup":<to_group> }
// ZbUnbind {"Device":"<device>", "Endpoint":<endpoint>, "Cluster":<cluster>, "ToDevice":"<to_device>", "ToEndpoint":<to_endpoint>, "ToGroup":<to_group> }
// local endpoint is always 1, IEEE addresses are calculated
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data);
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params
@ -634,7 +612,11 @@ void CmndZbBind(void) {
SBuffer buf(34);
buf.add8(Z_SREQ | Z_ZDO);
buf.add8(ZDO_BIND_REQ);
if (unbind) {
buf.add8(ZDO_UNBIND_REQ);
} else {
buf.add8(ZDO_BIND_REQ);
}
buf.add16(srcDevice);
buf.add64(srcLongAddr);
buf.add8(endpoint);
@ -653,11 +635,48 @@ void CmndZbBind(void) {
ResponseCmndDone();
}
//
// Command ZbBind
//
void CmndZbBind(void) {
ZbBindUnbind(false);
}
//
// Command ZbBind
//
void CmndZbUnbind(void) {
ZbBindUnbind(true);
}
//
// Command `ZbBindState`
//
void CmndZbBindState(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
SBuffer buf(10);
buf.add8(Z_SREQ | Z_ZDO); // 25
buf.add8(ZDO_MGMT_BIND_REQ); // 33
buf.add16(shortaddr); // shortaddr
buf.add8(0); // StartIndex = 0
ZigbeeZNPSend(buf.getBuffer(), buf.len());
ResponseCmndDone();
}
// Probe a specific device to get its endpoints and supported clusters
void CmndZbProbe(void) {
CmndZbProbeOrPing(true);
}
//
// Common code for `ZbProbe` and `ZbPing`
//
void CmndZbProbeOrPing(boolean probe) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
@ -677,12 +696,15 @@ void CmndZbPing(void) {
CmndZbProbeOrPing(false);
}
//
// Command `ZbName`
// Specify, read or erase a Friendly Name
//
void CmndZbName(void) {
// Syntax is:
// ZigbeeName <device_id>,<friendlyname> - assign a friendly name
// ZigbeeName <device_id> - display the current friendly name
// ZigbeeName <device_id>, - remove friendly name
// ZbName <device_id>,<friendlyname> - assign a friendly name
// ZbName <device_id> - display the current friendly name
// ZbName <device_id>, - remove friendly name
//
// Where <device_id> can be: short_addr, long_addr, device_index, friendly_name
@ -706,12 +728,15 @@ void CmndZbName(void) {
}
}
//
// Command `ZbName`
// Specify, read or erase a ModelId, only for debug purposes
//
void CmndZbModelId(void) {
// Syntax is:
// ZigbeeName <device_id>,<friendlyname> - assign a friendly name
// ZigbeeName <device_id> - display the current friendly name
// ZigbeeName <device_id>, - remove friendly name
// ZbName <device_id>,<friendlyname> - assign a friendly name
// ZbName <device_id> - display the current friendly name
// ZbName <device_id>, - remove friendly name
//
// Where <device_id> can be: short_addr, long_addr, device_index, friendly_name
@ -735,6 +760,8 @@ void CmndZbModelId(void) {
}
}
//
// Command `ZbLight`
// Specify, read or erase a Light type for Hue/Alexa integration
void CmndZbLight(void) {
// Syntax is:
@ -756,6 +783,8 @@ void CmndZbLight(void) {
if (p) {
int8_t bulbtype = strtol(p, nullptr, 10);
if (bulbtype > 5) { bulbtype = 5; }
if (bulbtype < -1) { bulbtype = -1; }
zigbee_devices.setHueBulbtype(shortaddr, bulbtype);
}
String dump = zigbee_devices.dumpLightState(shortaddr);
@ -766,7 +795,10 @@ void CmndZbLight(void) {
ResponseCmndDone();
}
//
// Command `ZbForget`
// Remove an old Zigbee device from the list of known devices, use ZigbeeStatus to know all registered devices
//
void CmndZbForget(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
@ -781,23 +813,81 @@ void CmndZbForget(void) {
}
}
//
// Command `ZbSave`
// Save Zigbee information to flash
//
void CmndZbSave(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
saveZigbeeDevices();
ResponseCmndDone();
}
// Send an attribute read command to a device, specifying cluster and list of attributes
void CmndZbRead(void) {
// ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5}
// ZigbeeRead {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Attr":"0x0005"}
// ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":[5,6,7,4]}
// Restore a device configuration previously exported via `ZbStatus2``
// Format:
// Either the entire `ZbStatus3` export, or an array or just the device configuration.
// If array, if can contain multiple devices
// ZbRestore {"ZbStatus3":[{"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}]}
// ZbRestore [{"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}]
// ZbRestore {"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
void CmndZbRestore(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data);
const JsonVariant json_parsed = jsonBuf.parse((const char*) XdrvMailbox.data); // const to force a copy of parameter
const JsonVariant * json = &json_parsed; // root of restore, to be changed if needed
bool success = false;
// check if parsing succeeded
if (json_parsed.is<JsonObject>()) {
success = json_parsed.as<const JsonObject&>().success();
} else if (json_parsed.is<JsonArray>()) {
success = json_parsed.as<const JsonArray&>().success();
}
if (!success) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// Check is root contains `ZbStatus<x>` key, if so change the root
const JsonVariant * zbstatus = &startsWithCaseInsensitive(*json, PSTR("ZbStatus"));
if (nullptr != zbstatus) {
json = zbstatus;
}
// check if the root is an array
if (json->is<JsonArray>()) {
const JsonArray& arr = json->as<const JsonArray&>();
for (auto elt : arr) {
// call restore on each item
int32_t res = zigbee_devices.deviceRestore(elt);
if (res < 0) {
ResponseCmndChar_P(PSTR("Restore failed"));
return;
}
}
} else if (json->is<JsonObject>()) {
int32_t res = zigbee_devices.deviceRestore(*json);
if (res < 0) {
ResponseCmndChar_P(PSTR("Restore failed"));
return;
}
// call restore on a single object
} else {
ResponseCmndChar_P(PSTR("Missing parameters"));
return;
}
ResponseCmndDone();
}
//
// Command `ZbRead`
// Send an attribute read command to a device, specifying cluster and list of attributes
//
void CmndZbRead(void) {
// ZbRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5}
// ZbRead {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Attr":"0x0005"}
// ZbRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":[5,6,7,4]}
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params
@ -805,30 +895,37 @@ void CmndZbRead(void) {
uint16_t groupaddr = 0x0000; // if 0x0000 ignore group adress
uint16_t cluster = 0x0000; // default to general cluster
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
uint16_t manuf = 0x0000; // Manuf Id in ZCL frame
size_t attrs_len = 0;
uint8_t* attrs = nullptr; // empty string is valid
const JsonVariant &val_group = getCaseInsensitive(json, PSTR("Group"));
if (nullptr != &val_group) { groupaddr = strToUInt(val_group); }
if (0x0000 == groupaddr) { // if no group address, we need a device address
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (0x0000 == device) { // if not found, check if we have a group
const JsonVariant &val_group = getCaseInsensitive(json, PSTR("Group"));
if (nullptr != &val_group) {
groupaddr = strToUInt(val_group);
} else { // no device nor group
ResponseCmndChar_P(PSTR("Unknown device"));
return;
}
if ((nullptr == &val_device) || (0x0000 == device)) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
}
const JsonVariant &val_cluster = getCaseInsensitive(json, PSTR("Cluster"));
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint"));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
const JsonVariant &val_manuf = getCaseInsensitive(json, PSTR("Manuf"));
if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); }
const JsonVariant &val_attr = getCaseInsensitive(json, PSTR("Read"));
if (nullptr != &val_attr) {
uint16_t val = strToUInt(val_attr);
if (val_attr.is<JsonArray>()) {
JsonArray& attr_arr = val_attr;
const JsonArray& attr_arr = val_attr.as<const JsonArray&>();
attrs_len = attr_arr.size() * 2;
attrs = new uint8_t[attrs_len];
@ -850,12 +947,12 @@ void CmndZbRead(void) {
endpoint = zigbee_devices.findFirstEndpoint(device);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
}
if (groupaddr) {
if (0x0000 == device) {
endpoint = 0xFF; // endpoint not used for group addresses
}
if ((0 != endpoint) && (attrs_len > 0)) {
ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device));
ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, manuf, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device));
ResponseCmndDone();
} else {
ResponseCmndChar_P(PSTR("Missing parameters"));
@ -864,7 +961,10 @@ void CmndZbRead(void) {
if (attrs) { delete[] attrs; }
}
//
// Command `ZbPermitJoin`
// Allow or Deny pairing of new Zigbee devices
//
void CmndZbPermitJoin(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint32_t payload = XdrvMailbox.payload;
@ -890,6 +990,9 @@ void CmndZbPermitJoin(void) {
ResponseCmndDone();
}
//
// Command `ZbStatus`
//
void CmndZbStatus(void) {
if (ZigbeeSerial) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
@ -920,7 +1023,7 @@ bool Xdrv23(uint8_t function)
}
break;
case FUNC_LOOP:
if (ZigbeeSerial) { ZigbeeInput(); }
if (ZigbeeSerial) { ZigbeeInputLoop(); }
if (zigbee.state_machine) {
ZigbeeStateMachine_Run();
}

View File

@ -685,7 +685,7 @@ void ShutterButtonHandler(void)
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
if ((i==shutter_index) || (Settings.shutter_button[button_index] & (0x01<<30))) {
snprintf_P(scommand, sizeof(scommand),PSTR("ShutterPosition%d"), i+1);
GetGroupTopic_P(stopic, scommand);
GetGroupTopic_P(stopic, scommand, SET_MQTT_GRP_TOPIC);
Response_P("%d", position);
MqttPublish(stopic, false);
}

View File

@ -77,6 +77,16 @@ void Pcf8574Init(void)
uint8_t pcf8574_address = PCF8574_ADDR1;
while ((Pcf8574.max_devices < MAX_PCF8574) && (pcf8574_address < PCF8574_ADDR2 +8)) {
#ifdef USE_MCP230xx_ADDR
if (USE_MCP230xx_ADDR == pcf8574_address) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Address 0x%02x reserved for MCP320xx skipped"), pcf8574_address);
pcf8574_address++;
if ((PCF8574_ADDR1 +7) == pcf8574_address) { // Support I2C addresses 0x20 to 0x26 and 0x39 to 0x3F
pcf8574_address = PCF8574_ADDR2 +1;
}
}
#endif
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Probing addr: 0x%x for PCF8574"), pcf8574_address);
if (I2cSetDevice(pcf8574_address)) {
@ -93,12 +103,6 @@ void Pcf8574Init(void)
}
pcf8574_address++;
#ifdef USE_MCP230xx_ADDR
if (USE_MCP230xx_ADDR == pcf8574_address) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Addr: 0x%x reserved for MCP320xx, skipping PCF8574 probe"), pcf8574_address);
pcf8574_address++;
}
#endif
if ((PCF8574_ADDR1 +7) == pcf8574_address) { // Support I2C addresses 0x20 to 0x26 and 0x39 to 0x3F
pcf8574_address = PCF8574_ADDR2 +1;
}
@ -142,7 +146,7 @@ const char HTTP_BTN_MENU_PCF8574[] PROGMEM =
const char HTTP_FORM_I2C_PCF8574_1[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_PCF8574_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='" WEB_HANDLE_PCF8574 "'>"
"<p><input id='b1' name='b1' type='checkbox'%s><b>" D_INVERT_PORTS "</b></p><hr/>";
"<p><label><input id='b1' name='b1' type='checkbox'%s><b>" D_INVERT_PORTS "</b></label></p><hr/>";
const char HTTP_FORM_I2C_PCF8574_2[] PROGMEM =
"<tr><td><b>" D_DEVICE " %d " D_PORT " %d</b></td><td style='width:100px'><select id='i2cs%d' name='i2cs%d'>"

View File

@ -50,37 +50,42 @@ struct remote_pwm_dimmer {
};
#endif // USE_PWM_DIMMER_REMOTE
uint32_t led_timeout_time = 0;
uint32_t turn_off_brightness_leds_time = 0;
uint32_t button_press_time;
uint32_t last_button_press_time;
uint32_t button_hold_time[3];
uint8_t restore_powered_off_led = 0;
uint8_t led_timeout_seconds = 0;
uint8_t restore_powered_off_led_counter = 0;
uint8_t power_button_index = 0;
uint8_t down_button_index = 1;
uint8_t up_button_index = 2;
uint8_t buttons_pressed = 0;
uint8_t tap_count = 0;
bool ignore_power_button_hold;
bool ignore_power_button_release;
bool down_button_tapped = false;
bool button_was_held = false;
bool power_button_increases_bri = true;
bool invert_power_button_bri_direction = false;
bool restore_brightness_leds = false;
bool button_hold_sent[3];
bool button_pressed[3] = { false, false, false };
bool button_hold_sent[3];
bool button_hold_processed[3];
#ifdef USE_PWM_DIMMER_REMOTE
struct remote_pwm_dimmer * remote_pwm_dimmers;
struct remote_pwm_dimmer * active_remote_pwm_dimmer;
bool active_device_is_local;
#endif // USE_PWM_DIMMER_REMOTE
void PWMModulePreInit()
void PWMModulePreInit(void)
{
Settings.seriallog_level = 0;
Settings.flag.mqtt_serial = 0; // Disable serial logging
Settings.ledstate = 0; // Disable LED usage
// If the module was just changed to PWM Dimmer, set the defaults.
if (Settings.last_module != Settings.module) {
Settings.flag.pwm_control = true; // SetOption15 - Switch between commands PWM or COLOR/DIMMER/CT/CHANNEL
Settings.param[P_HOLD_TIME] = 5; // SetOption32 - Button held for factor times longer
Settings.bri_power_on = Settings.bri_preset_low = Settings.bri_preset_high = 0;
Settings.last_module = Settings.module;
}
// Make sure the brightness level settings are sensible.
if (!Settings.bri_power_on) Settings.bri_power_on = 128;
if (!Settings.bri_preset_low) Settings.bri_preset_low = 10;
if (Settings.bri_preset_high < Settings.bri_preset_low) Settings.bri_preset_high = 255;
@ -142,7 +147,7 @@ void PWMDimmerSetBrightnessLeds(int32_t operation)
}
// If enabled, set the LED timeout.
if (!operation) led_timeout_time = (current_bri && Settings.flag4.led_timeout ? millis() + 5000 : 0);
if (!operation) led_timeout_seconds = (current_bri && Settings.flag4.led_timeout ? 5 : 0);
}
}
@ -164,9 +169,9 @@ void PWMDimmerSetPower(void)
}
#ifdef USE_DEVICE_GROUPS
void PWMDimmerHandleDeviceGroupItem()
void PWMDimmerHandleDeviceGroupItem(void)
{
uint8_t value = XdrvMailbox.payload;
uint32_t value = XdrvMailbox.payload;
#ifdef USE_PWM_DIMMER_REMOTE
uint8_t device_group_index = XdrvMailbox.index >> 16 & 0xff;
bool device_is_local = device_groups[device_group_index].local;
@ -178,6 +183,12 @@ void PWMDimmerHandleDeviceGroupItem()
case DGR_ITEM_LIGHT_BRI:
if (!device_is_local) remote_pwm_dimmer->bri = value;
break;
case DGR_ITEM_POWER:
if (!device_is_local) {
remote_pwm_dimmer->power = value;
remote_pwm_dimmer->power_button_increases_bri = (remote_pwm_dimmer->bri < 128);
}
break;
case DGR_ITEM_LIGHT_FIXED_COLOR:
if (!device_is_local) remote_pwm_dimmer->fixed_color_index = value;
break;
@ -217,7 +228,7 @@ void PWMDimmerHandleDeviceGroupItem()
}
#endif // USE_DEVICE_GROUPS
void PWMDimmerHandleButton()
void PWMDimmerHandleButton(void)
{
/*
* Power Button Up/Down Buttons State Remote Mode Action
@ -251,16 +262,16 @@ void PWMDimmerHandleButton()
// If the button is not pressed and was not just released (the most common case), ...
if (XdrvMailbox.payload && !button_pressed[XdrvMailbox.index]) {
// If no buttons have been pressed for 250ms, reset the button press counts.
if (button_press_time && !buttons_pressed && millis() - button_press_time > 400) {
button_was_held = false;
// If no buttons have been pressed for 400ms, reset the button press counts.
if (last_button_press_time && !buttons_pressed && millis() - last_button_press_time > 400) {
last_button_press_time = 0;
tap_count = 0;
}
return;
}
bool state_updated = false;
int8_t bri_offset = 0;
uint8_t power_on_bri = 0;
uint8_t dgr_item = 0;
uint8_t dgr_value;
@ -268,265 +279,192 @@ void PWMDimmerHandleButton()
uint32_t button_index = XdrvMailbox.index;
uint32_t now = millis();
// Set a bool indicating if the power is on.
// Initialize some variables.
#ifdef USE_PWM_DIMMER_REMOTE
bool power_is_on = (!active_device_is_local ? active_remote_pwm_dimmer->power : power);
bool is_power_button = (button_index == power_button_index);
#else // USE_PWM_DIMMER_REMOTE
bool power_is_on = power;
bool is_power_button = !button_index;
#endif // USE_PWM_DIMMER_REMOTE
bool is_down_button = (button_index == down_button_index);
// If the button is pressed, ...
if (!XdrvMailbox.payload) {
int8_t bri_direction = 0;
// If the button was just pressed, flag the button as pressed, clear the hold sent flag and
// increment the buttons pressed count.
if (!button_pressed[button_index]) {
button_press_time = now;
last_button_press_time = now;
button_pressed[button_index] = true;
button_hold_time[button_index] = now + Settings.param[P_HOLD_TIME] * 100;
button_hold_sent[button_index] = false;
buttons_pressed++;
#ifdef USE_PWM_DIMMER_REMOTE
#ifdef USE_PWM_DIMMER_REMOTE
// If there are no other buttons pressed right now and remote mode is enabled, make the device
// associated with this button the device we're going to control.
if (buttons_pressed == 1 && Settings.flag4.remote_device_mode) {
power_button_index = button_index;
up_button_index = (button_index == 2 ? 1 : 2);
down_button_index = (button_index ? 0 : 1);
active_device_is_local = device_groups[power_button_index].local;
if (!active_device_is_local) active_remote_pwm_dimmer = &remote_pwm_dimmers[power_button_index - 1];
}
if (button_index == power_button_index) {
#else // USE_PWM_DIMMER_REMOTE
// If this is about the power button, initialize some variables.
if (!button_index) {
#endif // USE_PWM_DIMMER_REMOTE
button_hold_time[button_index] = now + 500;
ignore_power_button_hold = false;
ignore_power_button_release = false;
return;
}
// If the button is being held, ...
if (button_hold_time[button_index] < now) {
// If the power button is not also pressed and the button has been held for over 10 seconds,
// execute the WiFiConfig 2 command.
if (!button_pressed[power_button_index] && now - button_hold_time[button_index] > 10000) {
button_hold_time[button_index] = now + 90000;
char scmnd[20];
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_WIFICONFIG " 2"));
ExecuteCommand(scmnd, SRC_BUTTON);
return;
}
// If this is not about the power button, load the new hold time. Note that the hold time for
// the power button is longer than the hold time for the other buttons.
button_hold_time[button_index] = now + (tap_count ? 0 : 250);
}
// If the button is being held, send a button hold.
else if (button_hold_time[button_index] < now) {
// Send a button hold if we haven't already. If it is handled (by the button topic or by a
// rule), ignore the this button until it's released.
if (!button_hold_sent[button_index]) {
button_hold_sent[button_index] = true;
SendKey(KEY_BUTTON, button_index + 1, POWER_HOLD);
button_hold_processed[button_index] = (!is_power_button && tap_count ? false : SendKey(KEY_BUTTON, button_index + 1, POWER_HOLD));
}
}
if (!button_hold_processed[button_index]) {
// If this is about the power button, ...
// If this is about the power button, ...
if (is_power_button) {
// If no other buttons are pressed, ...
if (buttons_pressed == 1) {
// If the power is on, adjust the brightness. Set the direction based on the current
// direction for the device and then invert the direction when the power button is
// released. The new brightness will be calculated below.
if (power_is_on) {
#ifdef USE_PWM_DIMMER_REMOTE
if (button_index == power_button_index) {
bri_offset = (!active_device_is_local ? (active_remote_pwm_dimmer->power_button_increases_bri ? 1 : -1) : (power_button_increases_bri ? 1 : -1));
#else // USE_PWM_DIMMER_REMOTE
if (!button_index) {
bri_offset = (power_button_increases_bri ? 1 : -1);
#endif // USE_PWM_DIMMER_REMOTE
// If the power button has been held with no other buttons pressed, ...
if (!ignore_power_button_hold && button_hold_time[button_index] < now) {
ignore_power_button_release = true;
// If the power is on, adjust the brightness. Set the direction based on the current
// direction for the device and then invert the direction when the power button is released.
// The new brightness will be calculated below.
if (power_is_on) {
#ifdef USE_PWM_DIMMER_REMOTE
bri_direction = (!active_device_is_local ? (active_remote_pwm_dimmer->power_button_increases_bri ? 1 : -1) : (power_button_increases_bri ? 1 : -1));
#else // USE_PWM_DIMMER_REMOTE
bri_direction = (power_button_increases_bri ? 1 : -1);
#endif // USE_PWM_DIMMER_REMOTE
invert_power_button_bri_direction = true;
}
// If the power is not on, turn it on using an initial brightness of bri_preset_low, set the
// power button hold dimmer direction to true so holding the power switch increases the
// brightness and the power button hold time to delay before we start increasing the
// brightness.
else {
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local) {
power_on_bri = active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->bri_preset_low;
active_remote_pwm_dimmer->power_button_increases_bri = true;
button_hold_time[button_index] = now + 1000;
}
else {
#endif // USE_PWM_DIMMER_REMOTE
power_on_bri = Settings.bri_preset_low;
power_button_increases_bri = true;
button_hold_time[button_index] = now + 500;
#ifdef USE_PWM_DIMMER_REMOTE
}
#endif // USE_PWM_DIMMER_REMOTE
}
}
}
// If this is about the down or up buttons, ...
else {
bool is_down_button = (button_index == down_button_index);
// If the power button is also pressed, set flags to ignore the power button being held and
// the next power button release.
if (button_pressed[power_button_index]) {
ignore_power_button_release = ignore_power_button_hold = true;
}
// If the button is being held, ...
if (button_hold_time[button_index] < now) {
uint8_t mqtt_trigger = 0;
// If the up or down button was tapped while holding the power button before this, handle
// the operation.
if (tap_count) {
// Send a device group update to select the previous/next fixed color.
if (down_button_tapped) {
#ifdef USE_DEVICE_GROUPS
uint8_t uint8_value;
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local)
uint8_value = active_remote_pwm_dimmer->fixed_color_index;
else
#endif // USE_PWM_DIMMER_REMOTE
uint8_value = Light.fixed_color_index;
if (is_down_button) {
if (uint8_value)
uint8_value--;
else
uint8_value = MAX_FIXED_COLOR;
invert_power_button_bri_direction = true;
}
// If the power is not on, turn it on using an initial brightness of bri_preset_low and
// set the power button hold time to delay before we start increasing the brightness.
else {
if (uint8_value < MAX_FIXED_COLOR)
uint8_value++;
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local)
power_on_bri = active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->bri_preset_low;
else
uint8_value = 0;
#endif // USE_PWM_DIMMER_REMOTE
power_on_bri = Settings.bri_preset_low;
button_hold_time[button_index] = now + 500;
}
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local)
active_remote_pwm_dimmer->fixed_color_index = uint8_value;
else
#endif // USE_PWM_DIMMER_REMOTE
Light.fixed_color_index = uint8_value;
dgr_item = DGR_ITEM_LIGHT_FIXED_COLOR;
dgr_value = uint8_value;
dgr_more_to_come = true;
#endif // USE_DEVICE_GROUPS
;
}
// Publish MQTT Event Trigger#.
else {
mqtt_trigger = (is_down_button ? 3 : 4);
}
}
// If the power button is not also pressed and the button has been held for over 10 seconds,
// execute the WiFiConfig 2 command.
else if (!button_pressed[power_button_index] && now - button_hold_time[button_index] > 10000) {
button_hold_time[button_index] = now + 90000;
char scmnd[20];
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_WIFICONFIG " 2"));
ExecuteCommand(scmnd, SRC_BUTTON);
return;
}
// If the power is not on, publish MQTT Event Trigger#.
else if (!power_is_on) {
mqtt_trigger = (is_down_button ? 1 : 2);
}
button_was_held = true;
button_hold_time[button_index] = now + 500;
// If we need to publish an MQTT trigger, do it.
if (mqtt_trigger) {
char topic[TOPSZ];
sprintf_P(mqtt_data, PSTR("Trigger%u"), mqtt_trigger);
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local) {
snprintf_P(topic, sizeof(topic), PSTR("cmnd/%s/Event"), device_groups[power_button_index].group_name);
MqttPublish(topic);
}
else
#endif // USE_PWM_DIMMER_REMOTE
MqttPublishPrefixTopic_P(CMND, PSTR("Event"));
}
}
// If the power is on and the up or down button was not tapped while holding the power button
// before this, adjust the brightness. Set the direction based on which button is pressed. The
// new brightness will be calculated below.
if (power_is_on && !tap_count) {
bri_direction = (is_down_button ? -1 : 1);
}
}
// If we need to adjust the brightness, do it.
if (bri_direction) {
int32_t bri;
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local)
bri = active_remote_pwm_dimmer->bri;
else
#endif // USE_PWM_DIMMER_REMOTE
bri = light_state.getBri();
int32_t new_bri;
int32_t offset = (Settings.light_correction ? 4 : bri / 16 + 1);
if (bri_direction > 0) {
new_bri = bri + offset;
if (new_bri > 255) new_bri = 255;
}
else {
new_bri = bri - offset;
if (new_bri < 1) new_bri = 1;
}
if (new_bri != bri) {
#ifdef USE_DEVICE_GROUPS
SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_ITEM_LIGHT_BRI, new_bri);
#endif // USE_DEVICE_GROUPS
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local)
active_remote_pwm_dimmer->bri_power_on = active_remote_pwm_dimmer->bri = new_bri;
// If this is about the down or up buttons, ...
else {
#endif // USE_PWM_DIMMER_REMOTE
skip_light_fade = true;
light_state.setBri(new_bri);
LightAnimate();
skip_light_fade = false;
Settings.bri_power_on = new_bri;
// If the power is on and the up or down button was not tapped while holding the power
// button before this, adjust the brightness. Set the direction based on which button is
// pressed. The new brightness will be calculated below.
if (power_is_on && !tap_count) {
bri_offset = (is_down_button ? -1 : 1);
}
else {
uint8_t mqtt_trigger = 0;
// If the up or down button was tapped while holding the power button before this,
// handle the operation.
if (tap_count) {
// Send a device group update to select the previous/next fixed color.
if (down_button_tapped) {
#ifdef USE_DEVICE_GROUPS
uint8_t uint8_value;
#ifdef USE_PWM_DIMMER_REMOTE
}
if (!active_device_is_local)
uint8_value = active_remote_pwm_dimmer->fixed_color_index;
else
#endif // USE_PWM_DIMMER_REMOTE
}
else {
PWMDimmerSetBrightnessLeds(0);
uint8_value = Light.fixed_color_index;
if (is_down_button) {
if (uint8_value)
uint8_value--;
else
uint8_value = MAX_FIXED_COLOR;
}
else {
if (uint8_value < MAX_FIXED_COLOR)
uint8_value++;
else
uint8_value = 0;
}
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local)
active_remote_pwm_dimmer->fixed_color_index = uint8_value;
else
#endif // USE_PWM_DIMMER_REMOTE
Light.fixed_color_index = uint8_value;
dgr_item = DGR_ITEM_LIGHT_FIXED_COLOR;
dgr_value = uint8_value;
dgr_more_to_come = true;
#endif // USE_DEVICE_GROUPS
;
}
// Publish MQTT Event Trigger#.
else {
mqtt_trigger = (is_down_button ? 3 : 4);
}
}
// If the power is not on, publish MQTT Event Trigger#.
else if (!power_is_on) {
mqtt_trigger = (is_down_button ? 1 : 2);
}
// If we need to publish an MQTT trigger, do it.
if (mqtt_trigger) {
char topic[TOPSZ];
sprintf_P(mqtt_data, PSTR("Trigger%u"), mqtt_trigger);
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local) {
snprintf_P(topic, sizeof(topic), PSTR("cmnd/%s/Event"), device_groups[power_button_index].group_name);
MqttPublish(topic);
}
else
#endif // USE_PWM_DIMMER_REMOTE
MqttPublishPrefixTopic_P(CMND, PSTR("Event"));
}
button_hold_time[button_index] = now + 500;
}
}
}
}
}
// If the button was just released, ...
else {
// if (now - button_press_time > Settings.button_debounce) {
bool button_was_held = button_hold_sent[button_index];
// If the button was held, send a button off; otherwise, send a button toggle.
SendKey(KEY_BUTTON, button_index + 1, (button_hold_sent[button_index] ? POWER_OFF : POWER_TOGGLE));
// If the button was not held, send a button toggle. If the button was held but not processes by
// support_button or support_buttondoesn't process the toggle (is not handled by a rule), ...
if (!(button_hold_sent[button_index] ? button_hold_processed[button_index] : SendKey(KEY_BUTTON, button_index + 1, POWER_TOGGLE))) {
// If this is about the power button, ...
#ifdef USE_PWM_DIMMER_REMOTE
if (button_index == power_button_index) {
#else // USE_PWM_DIMMER_REMOTE
if (!button_index) {
#endif // USE_PWM_DIMMER_REMOTE
if (is_power_button) {
// If we're ignoring the next power button released, ...
if (ignore_power_button_release) {
ignore_power_button_release = false;
// If the power button was held, ...
if (button_was_held) {
// If the power button was held with no other buttons pressed, we changed the brightness
// so invert the bri direction for the next time and send a final update.
@ -544,7 +482,7 @@ void PWMDimmerHandleButton()
#endif // USE_PWM_DIMMER_REMOTE
}
// If the up or down button was tapped while the power button was pressed, ...
// If the up or down button was tapped while the power button was held, ...
else if (tap_count) {
// If the button was tapped but not held, handle the operation based on which button was
@ -579,93 +517,116 @@ void PWMDimmerHandleButton()
}
}
// If we're not ignoring the power button until it's released, toggle the power.
// If the power button was not held, toggle the power.
else {
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local) {
if (!active_device_is_local)
power_on_bri = active_remote_pwm_dimmer->bri_power_on;
if (active_remote_pwm_dimmer->bri > 251)
active_remote_pwm_dimmer->power_button_increases_bri = false;
else if (active_remote_pwm_dimmer->bri < 4)
active_remote_pwm_dimmer->power_button_increases_bri = true;
}
else {
else
#endif // USE_PWM_DIMMER_REMOTE
power_on_bri = Settings.bri_power_on;
if (power_on_bri > 251)
power_button_increases_bri = false;
else if (power_on_bri < 4)
power_button_increases_bri = true;
#ifdef USE_PWM_DIMMER_REMOTE
}
#endif // USE_PWM_DIMMER_REMOTE
}
}
// If this is about the up or down buttons, ...
else {
bool is_down_button = (button_index == down_button_index);
if (restore_brightness_leds) {
restore_brightness_leds = false;
PWMDimmerSetBrightnessLeds(Settings.flag4.led_timeout ? -1 : 0);
}
// If the power is on and the up or down button was not tapped while holding the power
// button before this, we changed the brightness and sent updates with the more-to-come
// message type. Send a final update.
if (power_is_on && !tap_count) {
dgr_item = 255;
state_updated = true;
// If the power button is also pressed, set the power button hold dimmer direction so
// holding the power switch adjusts the brightness away from the brightness we just set.
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local && button_pressed[power_button_index]) active_remote_pwm_dimmer->power_button_increases_bri = is_down_button;
#endif // USE_PWM_DIMMER_REMOTE
// If the button was not held and the power button is also pressed, increment the count of
// how many times the button has been tapped.
if (!button_was_held && button_pressed[power_button_index]) {
down_button_tapped = is_down_button;
tap_count++;
}
// If the button was not held, ...
if (!button_was_held) {
// If the button wasn't tapped while the power button was pressed, ...
if (!tap_count) {
// If the power button is also pressed, increment the count of how many times a button has
// been tapped.
if (button_pressed[power_button_index]) {
down_button_tapped = (button_index == down_button_index);
tap_count++;
// If the power is on, ...
if (power_is_on) {
// If the button was not held, adjust the brightness. Set the direction based on which
// button is pressed. The new brightness will be calculated below.
if (button_hold_time[button_index] >= now) {
bri_offset = (is_down_button ? -10 : 10);
dgr_item = 255;
}
// If the button was held and the hold was not processed by a rule, we changed the
// brightness and sent updates with the more-to-come message type while the button was
// held. Send a final update.
else if (!button_hold_processed[button_index]) {
dgr_item = 255;
state_updated = true;
}
}
// If the power is off, turn it on using a temporary brightness of bri_preset_low if the
// down button is pressed or bri_preset_low if the up button is pressed.
else {
#ifdef USE_PWM_DIMMER_REMOTE
else if ((!active_device_is_local ? !active_remote_pwm_dimmer->power : !power)) {
#else // USE_PWM_DIMMER_REMOTE
else if (!power) {
#endif // USE_PWM_DIMMER_REMOTE
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local) {
if (!active_device_is_local)
power_on_bri = active_remote_pwm_dimmer->bri = (is_down_button ? active_remote_pwm_dimmer->bri_preset_low : active_remote_pwm_dimmer->bri_preset_high);
active_remote_pwm_dimmer->power_button_increases_bri = is_down_button;
}
else {
else
#endif // USE_PWM_DIMMER_REMOTE
power_on_bri = (is_down_button ? Settings.bri_preset_low : Settings.bri_preset_high);
power_button_increases_bri = is_down_button;
#ifdef USE_PWM_DIMMER_REMOTE
}
#endif // USE_PWM_DIMMER_REMOTE
button_hold_time[button_index] = now + 500;
}
}
}
// }
}
// Flag the button as released.
button_pressed[button_index] = false;
buttons_pressed--;
}
if (power_on_bri) {
// If we need to adjust the brightness, do it.
if (bri_offset) {
int32_t bri;
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local)
bri = active_remote_pwm_dimmer->bri;
else
#endif // USE_PWM_DIMMER_REMOTE
bri = light_state.getBri();
int32_t new_bri;
bri_offset *= (Settings.light_correction ? 4 : bri / 16 + 1);
new_bri = bri + bri_offset;
if (bri_offset > 0) {
if (new_bri > 255) new_bri = 255;
}
else {
if (new_bri < 1) new_bri = 1;
}
if (new_bri != bri) {
#ifdef USE_DEVICE_GROUPS
SendDeviceGroupMessage(power_button_index, (dgr_item ? DGR_MSGTYP_UPDATE : DGR_MSGTYP_UPDATE_MORE_TO_COME), DGR_ITEM_LIGHT_BRI, new_bri);
#endif // USE_DEVICE_GROUPS
#ifdef USE_PWM_DIMMER_REMOTE
if (!active_device_is_local)
active_remote_pwm_dimmer->bri_power_on = active_remote_pwm_dimmer->bri = new_bri;
else {
#endif // USE_PWM_DIMMER_REMOTE
skip_light_fade = true;
light_state.setBri(new_bri);
LightAnimate();
skip_light_fade = false;
Settings.bri_power_on = new_bri;
#ifdef USE_PWM_DIMMER_REMOTE
}
#endif // USE_PWM_DIMMER_REMOTE
}
else {
PWMDimmerSetBrightnessLeds(0);
}
}
// If we're supposed to toggle the power on, do it.
else if (power_on_bri) {
power_t new_power;
#ifdef USE_DEVICE_GROUPS
#ifdef USE_PWM_DIMMER_REMOTE
@ -686,16 +647,19 @@ void PWMDimmerHandleButton()
#endif // USE_DEVICE_GROUPS
#ifdef USE_PWM_DIMMER_REMOTE
if (active_device_is_local) {
if (!active_device_is_local)
active_remote_pwm_dimmer->power_button_increases_bri = (power_on_bri < 128);
else {
#endif // USE_PWM_DIMMER_REMOTE
ExecuteCommandPower(1, POWER_TOGGLE, SRC_RETRY);
light_state.setBri(power_on_bri);
ExecuteCommandPower(1, POWER_TOGGLE, SRC_RETRY);
#ifdef USE_PWM_DIMMER_REMOTE
}
#endif // USE_PWM_DIMMER_REMOTE
}
// If we're not toggling the power and we made changes, send a group update.
// If we're not changing the brightness or toggling the power and we made changes, send a group
// update.
else if (dgr_item) {
#ifdef USE_DEVICE_GROUPS
if (dgr_item == 255) dgr_item = 0;
@ -776,8 +740,7 @@ bool Xdrv35(uint8_t function)
switch (function) {
case FUNC_EVERY_SECOND:
// Turn off the brightness LED's if it's time.
if (led_timeout_time && led_timeout_time < millis()) {
led_timeout_time = 0;
if (led_timeout_seconds && !led_timeout_seconds--) {
PWMDimmerSetBrightnessLeds(-1);
}
@ -786,10 +749,10 @@ bool Xdrv35(uint8_t function)
// restored. If the state is blinking now, set a flag so we know that we need to restore it
// when it stops blinking.
if (global_state.data)
restore_powered_off_led = 5;
else if (restore_powered_off_led) {
restore_powered_off_led_counter = 5;
else if (restore_powered_off_led_counter) {
PWMDimmerSetPoweredOffLed();
restore_powered_off_led--;
restore_powered_off_led_counter--;
}
break;
@ -811,9 +774,13 @@ bool Xdrv35(uint8_t function)
case FUNC_SET_DEVICE_POWER:
// If we're turning the power on, turn the relay and the brightness LEDs on and turn the
// powered-off LED off.
if (XdrvMailbox.index)
if (XdrvMailbox.index) {
PWMDimmerSetPower();
// Set the power button hold dimmer direction based on the current brightness.
power_button_increases_bri = (light_state.getBri() < 128);
}
// If we're turning the power off, return true so SetDevicePower doesn't turn the relay off.
// It will be turned off in LightApplyFade when the fade is done.
else

View File

@ -27,16 +27,20 @@
#define D_PRFX_COUNTER "Counter"
#define D_CMND_COUNTERTYPE "Type"
#define D_CMND_COUNTERDEBOUNCE "Debounce"
#define D_CMND_COUNTERDEBOUNCELOW "DebounceLow"
#define D_CMND_COUNTERDEBOUNCEHIGH "DebounceHigh"
const char kCounterCommands[] PROGMEM = D_PRFX_COUNTER "|" // Prefix
"|" D_CMND_COUNTERTYPE "|" D_CMND_COUNTERDEBOUNCE ;
"|" D_CMND_COUNTERTYPE "|" D_CMND_COUNTERDEBOUNCE "|" D_CMND_COUNTERDEBOUNCELOW "|" D_CMND_COUNTERDEBOUNCEHIGH ;
void (* const CounterCommand[])(void) PROGMEM = {
&CmndCounter, &CmndCounterType, &CmndCounterDebounce };
&CmndCounter, &CmndCounterType, &CmndCounterDebounce, &CmndCounterDebounceLow, &CmndCounterDebounceHigh };
struct COUNTER {
uint32_t timer[MAX_COUNTERS]; // Last counter time in micro seconds
uint32_t timer_low_high[MAX_COUNTERS]; // Last low/high counter time in micro seconds
uint8_t no_pullup = 0; // Counter input pullup flag (1 = No pullup)
uint8_t pin_state = 0; // LSB0..3 Last state of counter pin; LSB7==0 IRQ is FALLING, LSB7==1 IRQ is CHANGE
bool any_counter = false;
} Counter;
@ -51,7 +55,30 @@ void CounterUpdate4(void) ICACHE_RAM_ATTR;
void CounterUpdate(uint8_t index)
{
uint32_t time = micros();
uint32_t debounce_time = time - Counter.timer[index];
uint32_t debounce_time;
if (Counter.pin_state) {
// handle low and high debounce times when configured
if (digitalRead(pin[GPIO_CNTR1 +index]) == bitRead(Counter.pin_state, index)) {
// new pin state to be ignored because debounce time was not met during last IRQ
return;
}
debounce_time = time - Counter.timer_low_high[index];
if bitRead(Counter.pin_state, index) {
// last valid pin state was high, current pin state is low
if (debounce_time <= Settings.pulse_counter_debounce_high * 1000) return;
} else {
// last valid pin state was low, current pin state is high
if (debounce_time <= Settings.pulse_counter_debounce_low * 1000) return;
}
// passed debounce check, save pin state and timing
Counter.timer_low_high[index] = time;
Counter.pin_state ^= (1<<index);
// do not count on rising edge
if bitRead(Counter.pin_state, index) return;
}
debounce_time = time - Counter.timer[index];
if (debounce_time > Settings.pulse_counter_debounce * 1000) {
Counter.timer[index] = time;
if (bitRead(Settings.pulse_counter_type, index)) {
@ -103,7 +130,13 @@ void CounterInit(void)
if (pin[GPIO_CNTR1 +i] < 99) {
Counter.any_counter = true;
pinMode(pin[GPIO_CNTR1 +i], bitRead(Counter.no_pullup, i) ? INPUT : INPUT_PULLUP);
attachInterrupt(pin[GPIO_CNTR1 +i], counter_callbacks[i], FALLING);
if ((0 == Settings.pulse_counter_debounce_low) && (0 == Settings.pulse_counter_debounce_high)) {
Counter.pin_state = 0;
attachInterrupt(pin[GPIO_CNTR1 +i], counter_callbacks[i], FALLING);
} else {
Counter.pin_state = 0x8f;
attachInterrupt(pin[GPIO_CNTR1 +i], counter_callbacks[i], CHANGE);
}
}
}
}
@ -213,6 +246,24 @@ void CmndCounterDebounce(void)
ResponseCmndNumber(Settings.pulse_counter_debounce);
}
void CmndCounterDebounceLow(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) {
Settings.pulse_counter_debounce_low = XdrvMailbox.payload;
CounterInit();
}
ResponseCmndNumber(Settings.pulse_counter_debounce_low);
}
void CmndCounterDebounceHigh(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) {
Settings.pulse_counter_debounce_high = XdrvMailbox.payload;
CounterInit();
}
ResponseCmndNumber(Settings.pulse_counter_debounce_high);
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/

View File

@ -25,30 +25,65 @@
* I2C Address: 0x23 or 0x5C
\*********************************************************************************************/
#define XSNS_10 10
#define XI2C_11 11 // See I2CDEVICES.md
#define XSNS_10 10
#define XI2C_11 11 // See I2CDEVICES.md
#define BH1750_ADDR1 0x23
#define BH1750_ADDR2 0x5C
#define BH1750_ADDR1 0x23
#define BH1750_ADDR2 0x5C
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_HIGH_RES_MODE2 0x11 // Start measurement at 0.5 lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1 lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_LOW_RES_MODE 0x13 // Start measurement at 4 lx resolution. Measurement time is approx 16ms.
uint8_t bh1750_address;
uint8_t bh1750_addresses[] = { BH1750_ADDR1, BH1750_ADDR2 };
uint8_t bh1750_type = 0;
uint8_t bh1750_valid = 0;
uint16_t bh1750_illuminance = 0;
char bh1750_types[] = "BH1750";
#define BH1750_MEASUREMENT_TIME_HIGH 0x40 // Measurement Time register high 3 bits
#define BH1750_MEASUREMENT_TIME_LOW 0x60 // Measurement Time register low 5 bits
struct BH1750DATA {
uint8_t address;
uint8_t addresses[2] = { BH1750_ADDR1, BH1750_ADDR2 };
uint8_t resolution[3] = { BH1750_CONTINUOUS_HIGH_RES_MODE, BH1750_CONTINUOUS_HIGH_RES_MODE2, BH1750_CONTINUOUS_LOW_RES_MODE };
uint8_t type = 0;
uint8_t valid = 0;
uint8_t mtreg = 69; // Default Measurement Time
uint16_t illuminance = 0;
char types[7] = "BH1750";
} Bh1750;
/*********************************************************************************************/
bool Bh1750SetResolution(void)
{
Wire.beginTransmission(Bh1750.address);
Wire.write(Bh1750.resolution[Settings.SensorBits1.bh1750_resolution]);
return (!Wire.endTransmission());
}
bool Bh1750SetMTreg(void)
{
Wire.beginTransmission(Bh1750.address);
uint8_t data = BH1750_MEASUREMENT_TIME_HIGH | ((Bh1750.mtreg >> 5) & 0x07);
Wire.write(data);
if (Wire.endTransmission()) { return false; }
Wire.beginTransmission(Bh1750.address);
data = BH1750_MEASUREMENT_TIME_LOW | (Bh1750.mtreg & 0x1F);
Wire.write(data);
if (Wire.endTransmission()) { return false; }
return Bh1750SetResolution();
}
bool Bh1750Read(void)
{
if (bh1750_valid) { bh1750_valid--; }
if (Bh1750.valid) { Bh1750.valid--; }
if (2 != Wire.requestFrom(bh1750_address, (uint8_t)2)) { return false; }
uint8_t msb = Wire.read();
uint8_t lsb = Wire.read();
bh1750_illuminance = ((msb << 8) | lsb) / 1.2;
bh1750_valid = SENSOR_MAX_MISS;
if (2 != Wire.requestFrom(Bh1750.address, (uint8_t)2)) { return false; }
float illuminance = (Wire.read() << 8) | Wire.read();
illuminance /= (1.2 * (69 / (float)Bh1750.mtreg));
if (1 == Settings.SensorBits1.bh1750_resolution) {
illuminance /= 2;
}
Bh1750.illuminance = illuminance;
Bh1750.valid = SENSOR_MAX_MISS;
return true;
}
@ -56,14 +91,13 @@ bool Bh1750Read(void)
void Bh1750Detect(void)
{
for (uint32_t i = 0; i < sizeof(bh1750_addresses); i++) {
bh1750_address = bh1750_addresses[i];
if (I2cActive(bh1750_address)) { continue; }
Wire.beginTransmission(bh1750_address);
Wire.write(BH1750_CONTINUOUS_HIGH_RES_MODE);
if (!Wire.endTransmission()) {
I2cSetActiveFound(bh1750_address, bh1750_types);
bh1750_type = 1;
for (uint32_t i = 0; i < sizeof(Bh1750.addresses); i++) {
Bh1750.address = Bh1750.addresses[i];
if (I2cActive(Bh1750.address)) { continue; }
if (Bh1750SetMTreg()) {
I2cSetActiveFound(Bh1750.address, Bh1750.types);
Bh1750.type = 1;
break;
}
}
@ -73,23 +107,49 @@ void Bh1750EverySecond(void)
{
// 1mS
if (!Bh1750Read()) {
AddLogMissed(bh1750_types, bh1750_valid);
AddLogMissed(Bh1750.types, Bh1750.valid);
}
}
/*********************************************************************************************\
* Command Sensor10
*
* 0 - High resolution mode (default)
* 1 - High resolution mode 2
* 2 - Low resolution mode
* 31..254 - Measurement Time value (not persistent, default is 69)
\*********************************************************************************************/
bool Bh1750CommandSensor(void)
{
if (XdrvMailbox.data_len) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
Settings.SensorBits1.bh1750_resolution = XdrvMailbox.payload;
Bh1750SetResolution();
}
else if ((XdrvMailbox.payload > 30) && (XdrvMailbox.payload < 255)) {
Bh1750.mtreg = XdrvMailbox.payload;
Bh1750SetMTreg();
}
}
Response_P(PSTR("{\"" D_CMND_SENSOR "10\":{\"Resolution\":%d,\"MTime\":%d}}"), Settings.SensorBits1.bh1750_resolution, Bh1750.mtreg);
return true;
}
void Bh1750Show(bool json)
{
if (bh1750_valid) {
if (Bh1750.valid) {
if (json) {
ResponseAppend_P(JSON_SNS_ILLUMINANCE, bh1750_types, bh1750_illuminance);
ResponseAppend_P(JSON_SNS_ILLUMINANCE, Bh1750.types, Bh1750.illuminance);
#ifdef USE_DOMOTICZ
if (0 == tele_period) {
DomoticzSensor(DZ_ILLUMINANCE, bh1750_illuminance);
DomoticzSensor(DZ_ILLUMINANCE, Bh1750.illuminance);
}
#endif // USE_DOMOTICZ
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, bh1750_types, bh1750_illuminance);
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, Bh1750.types, Bh1750.illuminance);
#endif // USE_WEBSERVER
}
}
@ -108,11 +168,16 @@ bool Xsns10(uint8_t function)
if (FUNC_INIT == function) {
Bh1750Detect();
}
else if (bh1750_type) {
else if (Bh1750.type) {
switch (function) {
case FUNC_EVERY_SECOND:
Bh1750EverySecond();
break;
case FUNC_COMMAND_SENSOR:
if (XSNS_10 == XdrvMailbox.index) {
result = Bh1750CommandSensor();
}
break;
case FUNC_JSON_APPEND:
Bh1750Show(1);
break;

View File

@ -230,7 +230,9 @@ void MhzEverySecond(void)
mhz_type = (s) ? 1 : 2;
if (MhzCheckAndApplyFilter(ppm, s)) {
mhz_retry = MHZ19_RETRY_COUNT;
#ifdef USE_LIGHT
LightSetSignal(CO2_LOW, CO2_HIGH, mhz_last_ppm);
#endif // USE_LIGHT
if (0 == s || 64 == s) { // Reading is stable.
if (mhz_abc_must_apply) {

View File

@ -84,7 +84,9 @@ void Senseair250ms(void) // Every 250 mSec
break;
case 2: // 0x03 (3) READ_CO2 - fe 04 02 06 2c af 59
senseair_co2 = value;
#ifdef USE_LIGHT
LightSetSignal(CO2_LOW, CO2_HIGH, senseair_co2);
#endif // USE_LIGHT
break;
case 3: // 0x04 (4) READ_TEMPERATURE - S8: fe 84 02 f2 f1 - Illegal Data Address
senseair_temperature = ConvertTemp((float)value / 100);

View File

@ -215,7 +215,9 @@ void AzEverySecond(void)
}
response_substr[j] = 0; // add null terminator
az_co2 = atoi((char*)response_substr);
#ifdef USE_LIGHT
LightSetSignal(CO2_LOW, CO2_HIGH, az_co2);
#endif // USE_LIGHT
i += 3; // advance to second delimiter
if(az_response[i] != ':') {
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error second delimiter"));

View File

@ -901,6 +901,11 @@ uint8_t dchars[16];
}
}
#ifdef ED300L
uint8_t sml_status[MAX_METERS];
uint8_t g_mindex;
#endif
// skip sml entries
uint8_t *skip_sml(uint8_t *cp,int16_t *res) {
uint8_t len,len1,type;
@ -933,6 +938,14 @@ double dval;
// scan for values
// check status
#ifdef ED300L
unsigned char *cpx=cp-5;
// decode OBIS 0180 amd extract direction info
if (*cp==0x64 && *cpx==0 && *(cpx+1)==0x01 && *(cpx+2)==0x08 && *(cpx+3)==0) {
sml_status[g_mindex]=*(cp+3);
}
#endif
cp=skip_sml(cp,&result);
// check time
cp=skip_sml(cp,&result);
@ -941,6 +954,7 @@ double dval;
// check scaler
cp=skip_sml(cp,&result);
scaler=result;
// get value
type=*cp&0x70;
len=*cp&0x0f;
@ -1033,6 +1047,15 @@ double dval;
} else if (scaler==3) {
dval*=1000;
}
#ifdef ED300L
// decode current power OBIS 00 0F 07 00
if (*cpx==0x00 && *(cpx+1)==0x0f && *(cpx+2)==0x07 && *(cpx+3)==0) {
if (sml_status[g_mindex]&0x20) {
// and invert sign on solar feed
dval*=-1;
}
}
#endif
return dval;
}
@ -1465,6 +1488,9 @@ void SML_Decode(uint8_t index) {
if (found) {
// matches, get value
mp++;
#ifdef ED300L
g_mindex=mindex;
#endif
if (*mp=='#') {
// get string value
mp++;
@ -2061,6 +2087,17 @@ uint32_t SML_SetBaud(uint32_t meter, uint32_t br) {
return 1;
}
uint32_t SML_Status(uint32_t meter) {
if (meter<1 || meter>meters_used) return 0;
meter--;
#ifdef ED300L
return sml_status[meter];
#else
return 0;
#endif
}
uint32_t SML_Write(uint32_t meter,char *hstr) {
if (meter<1 || meter>meters_used) return 0;
meter--;

File diff suppressed because it is too large Load Diff