mirror of
https://github.com/arendst/Tasmota.git
synced 2025-04-25 15:27:17 +00:00
Merge branch 'dev-arendst' of https://github.com/he-so/Tasmota into dev-arendst
This commit is contained in:
commit
b00f9d4179
45
Device_Groups.md
Normal file
45
Device_Groups.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Device Groups
|
||||||
|
|
||||||
|
The device groups module provides a framework to allow multiple devices to be in a group with values such as power, light color/temperature/brightness, PWM values, sensor values, etc. shared with other devices in the group. For example, with multiple light modules in a device group, the light settings can be changed on one module and the settings will automatically be changed on the other light modules. Dimmer switch modules could be in a device group with light modules and the dimmer switch could control the power, brightness and colors of all the lights in the group. Multiple dimmer switches could be in a device group to form a 3-way/4-way dimmer switch.
|
||||||
|
|
||||||
|
UDP multicasts, followed by UDP unicasts if necessary, are used to send updates to all devices so updates are fast. There is no need for an MQTT server but all the devices in a group must be on the same IP network.
|
||||||
|
|
||||||
|
To include device groups support in the build, define USE_DEVICE_GROUPS in your user_config_override. This adds 3.5K to the code size. All devices in a group must be running firmware with device group support and have device groups enabled.
|
||||||
|
|
||||||
|
To enable device groups, set Option85 to 1.
|
||||||
|
|
||||||
|
|
||||||
|
## Device Groups Operation
|
||||||
|
|
||||||
|
The device group name is the MQTT group topic set with the GroupTopic command. All devices in the same IP network with the same group topic are in the same group. Some modules may define additional device groups. For example, if Remote Device Mode is enabled, the PWM Dimmer module defines three devices groups.
|
||||||
|
|
||||||
|
The items that are sent to the group and the items that are received from the group are selected with the DevGroupShare command. By default all items are sent and received from the group. An example of when the DevGroupShare command would be used is when you have a group of lights that you control with a dimmer switch and home automation software. You want the dimmer switch to be able to control all items. The home automation software controls each light individually. When it controls the whole group, it actually sends command to each light in the group. If you use the home automation software to turn an individual light on or off or change it’s brightness, color or scheme, you do not want the change to be replicated to the other lights. In this case, you would set the incoming and outgoing item masks to 0xffffffff (all items) on the dimmer switch (DevGroupShare 0xffffffff,0xffffffff) and set the incoming item mask to 0xffffffff and outgoing item mask to 0 on all the lights (DevGroupShare 0xffffffff,0).
|
||||||
|
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Command</strong>
|
||||||
|
</td>
|
||||||
|
<td><strong>Parameters</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>DevGroupShare
|
||||||
|
</td>
|
||||||
|
<td><in>,<out> = set incoming and outgoing shared item mask (default = 0xffffffff,0xffffffff)
|
||||||
|
<p>
|
||||||
|
1 = Power, 2 = Light brightness, 4 = Light fade/speed, 8 = Light scheme, 16 = Light color, 32 = Minimum brightness
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>GroupTopic<x>
|
||||||
|
</td>
|
||||||
|
<td>1 = reset device group <x> MQTT group topic to firmware default (MQTT_GRPTOPIC) and restart
|
||||||
|
<p>
|
||||||
|
<value> = set device group <x> MQTT group topic (32 chars max) and restart
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
@ -105,3 +105,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
|
|||||||
- Add another new DHT driver based on ESPEasy. The old driver can still be used using define USE_DHT_OLD. The previous new driver can be used with define USE_DHT_V2 (#7717)
|
- Add another new DHT driver based on ESPEasy. The old driver can still be used using define USE_DHT_OLD. The previous new driver can be used with define USE_DHT_V2 (#7717)
|
||||||
- Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596)
|
- Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596)
|
||||||
- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764)
|
- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764)
|
||||||
|
- Add Zigbee enhanced commands decoding, added ``ZbPing``
|
||||||
|
- Add commands ``SetOption85 0/1`` and ``DevGroupShare`` supporting UDP Group command using ``GroupTopic`` without MQTT by Paul Diem (#7790)
|
||||||
|
@ -56,6 +56,7 @@ default_envs =
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
board = esp01_1m
|
board = esp01_1m
|
||||||
board_build.flash_mode = dout
|
board_build.flash_mode = dout
|
||||||
|
board_build.ldscript = eagle.flash.1m.ld
|
||||||
|
|
||||||
platform = ${core_active.platform}
|
platform = ${core_active.platform}
|
||||||
platform_packages = ${core_active.platform_packages}
|
platform_packages = ${core_active.platform_packages}
|
||||||
|
@ -135,10 +135,9 @@ build_flags = ${esp82xx_defaults.build_flags}
|
|||||||
|
|
||||||
[core_2_6_3]
|
[core_2_6_3]
|
||||||
; *** Esp8266 core for Arduino version 2.6.3
|
; *** Esp8266 core for Arduino version 2.6.3
|
||||||
platform = espressif8266@2.3.2
|
platform = espressif8266@2.3.3
|
||||||
platform_packages =
|
platform_packages =
|
||||||
build_flags = ${esp82xx_defaults.build_flags}
|
build_flags = ${esp82xx_defaults.build_flags}
|
||||||
-Wl,-Teagle.flash.1m.ld
|
|
||||||
-DBEARSSL_SSL_BASIC
|
-DBEARSSL_SSL_BASIC
|
||||||
; NONOSDK221
|
; NONOSDK221
|
||||||
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221
|
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221
|
||||||
@ -180,12 +179,10 @@ build_flags = ${esp82xx_defaults.build_flags}
|
|||||||
|
|
||||||
[tasmota_core_stage]
|
[tasmota_core_stage]
|
||||||
; *** Esp8266 core for Arduino version stable beta
|
; *** Esp8266 core for Arduino version stable beta
|
||||||
platform = espressif8266@2.3.2
|
platform = espressif8266@2.3.3
|
||||||
platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#6be561617f645f6a2ae82b8211f6af8c43e834cf
|
platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#abdd2bdbb6a5caf31807d82ebd7b447947a9c360
|
||||||
build_flags = ${esp82xx_defaults.build_flags}
|
build_flags = ${esp82xx_defaults.build_flags}
|
||||||
-Wl,-Teagle.flash.1m.ld
|
|
||||||
-DBEARSSL_SSL_BASIC
|
-DBEARSSL_SSL_BASIC
|
||||||
-DNOPRINTFLOAT
|
|
||||||
; NONOSDK221
|
; NONOSDK221
|
||||||
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221
|
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221
|
||||||
; NONOSDK22x_190313
|
; NONOSDK22x_190313
|
||||||
@ -226,12 +223,10 @@ build_flags = ${esp82xx_defaults.build_flags}
|
|||||||
|
|
||||||
[core_stage]
|
[core_stage]
|
||||||
; *** Esp8266 core for Arduino version latest beta
|
; *** Esp8266 core for Arduino version latest beta
|
||||||
platform = espressif8266@2.3.2
|
platform = espressif8266@2.3.3
|
||||||
platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git
|
platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git
|
||||||
board_build.ldscript = eagle.flash.1m.ld
|
|
||||||
build_flags = ${esp82xx_defaults.build_flags}
|
build_flags = ${esp82xx_defaults.build_flags}
|
||||||
-DBEARSSL_SSL_BASIC
|
-DBEARSSL_SSL_BASIC
|
||||||
-DNOPRINTFLOAT
|
|
||||||
; NONOSDK221
|
; NONOSDK221
|
||||||
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221
|
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221
|
||||||
; NONOSDK22x_190313
|
; NONOSDK22x_190313
|
||||||
|
@ -3,6 +3,7 @@ platform = ${common.platform}
|
|||||||
platform_packages = ${common.platform_packages}
|
platform_packages = ${common.platform_packages}
|
||||||
framework = ${common.framework}
|
framework = ${common.framework}
|
||||||
board = ${common.board}
|
board = ${common.board}
|
||||||
|
board_build.ldscript = ${common.board_build.ldscript}
|
||||||
board_build.flash_mode = ${common.board_build.flash_mode}
|
board_build.flash_mode = ${common.board_build.flash_mode}
|
||||||
board_build.f_cpu = ${common.board_build.f_cpu}
|
board_build.f_cpu = ${common.board_build.f_cpu}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
### 8.1.0.9 20200220
|
### 8.1.0.9 20200220
|
||||||
|
|
||||||
- Revert most wifi connectivity changes introduced in 8.1.0.5 (#7746, #7602, #7621)
|
- Revert most wifi connectivity changes introduced in 8.1.0.5 (#7746, #7602, #7621)
|
||||||
|
- Fix Zigbee auto-increment transaction number (#7757)
|
||||||
- Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596)
|
- Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596)
|
||||||
- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764)
|
- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764)
|
||||||
|
- Add Zigbee enhanced commands decoding, added ``ZbPing``
|
||||||
|
- Add commands ``SetOption85 0/1`` and ``DevGroupShare`` supporting UDP Group command using ``GroupTopic`` without MQTT by Paul Diem (#7790)
|
||||||
|
|
||||||
### 8.1.0.8 20200212
|
### 8.1.0.8 20200212
|
||||||
|
|
||||||
|
@ -289,6 +289,7 @@
|
|||||||
#define D_CMND_WIFIPOWER "WifiPower"
|
#define D_CMND_WIFIPOWER "WifiPower"
|
||||||
#define D_CMND_I2CSCAN "I2CScan"
|
#define D_CMND_I2CSCAN "I2CScan"
|
||||||
#define D_CMND_I2CDRIVER "I2CDriver"
|
#define D_CMND_I2CDRIVER "I2CDriver"
|
||||||
|
#define D_CMND_DEVGROUP_SHARE "DevGroupShare"
|
||||||
#define D_CMND_SERIALSEND "SerialSend"
|
#define D_CMND_SERIALSEND "SerialSend"
|
||||||
#define D_CMND_SERIALDELIMITER "SerialDelimiter"
|
#define D_CMND_SERIALDELIMITER "SerialDelimiter"
|
||||||
#define D_CMND_BAUDRATE "Baudrate"
|
#define D_CMND_BAUDRATE "Baudrate"
|
||||||
@ -489,12 +490,15 @@
|
|||||||
#define D_CMND_ZIGBEE_FORGET "Forget"
|
#define D_CMND_ZIGBEE_FORGET "Forget"
|
||||||
#define D_CMND_ZIGBEE_SAVE "Save"
|
#define D_CMND_ZIGBEE_SAVE "Save"
|
||||||
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
|
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
|
||||||
|
#define D_CMND_ZIGBEE_ENDPOINT "Endpoint"
|
||||||
#define D_CMND_ZIGBEE_READ "Read"
|
#define D_CMND_ZIGBEE_READ "Read"
|
||||||
#define D_CMND_ZIGBEE_SEND "Send"
|
#define D_CMND_ZIGBEE_SEND "Send"
|
||||||
#define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent"
|
#define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent"
|
||||||
#define D_JSON_ZIGBEE_RECEIVED "ZbReceived"
|
#define D_JSON_ZIGBEE_RECEIVED "ZbReceived"
|
||||||
#define D_JSON_ZIGBEE_RECEIVED_LEGACY "ZigbeeReceived"
|
#define D_JSON_ZIGBEE_RECEIVED_LEGACY "ZigbeeReceived"
|
||||||
#define D_CMND_ZIGBEE_BIND "Bind"
|
#define D_CMND_ZIGBEE_BIND "Bind"
|
||||||
|
#define D_CMND_ZIGBEE_PING "Ping"
|
||||||
|
#define D_JSON_ZIGBEE_PING "ZbPing"
|
||||||
|
|
||||||
// Commands xdrv_25_A4988_Stepper.ino
|
// Commands xdrv_25_A4988_Stepper.ino
|
||||||
#define D_CMND_MOTOR "MOTOR"
|
#define D_CMND_MOTOR "MOTOR"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Посока на вятъра"
|
#define D_TX20_WIND_DIRECTION "Посока на вятъра"
|
||||||
|
#define D_TX20_WIND_DEGREES "Степени на вятъра"
|
||||||
#define D_TX20_WIND_SPEED "Скорост на вятъра"
|
#define D_TX20_WIND_SPEED "Скорост на вятъра"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Средна скорост на вятъра"
|
#define D_TX20_WIND_SPEED_AVG "Средна скорост на вятъра"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Мини. скорост на вятъра"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Макс. скорост на вятъра"
|
#define D_TX20_WIND_SPEED_MAX "Макс. скорост на вятъра"
|
||||||
#define D_TX20_NORTH "С"
|
#define D_TX20_NORTH "С"
|
||||||
#define D_TX20_EAST "И"
|
#define D_TX20_EAST "И"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Směr větru"
|
#define D_TX20_WIND_DIRECTION "Směr větru"
|
||||||
|
#define D_TX20_WIND_DEGREES "Úhel větru"
|
||||||
#define D_TX20_WIND_SPEED "Rychlost větru"
|
#define D_TX20_WIND_SPEED "Rychlost větru"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Průměrná rychlost větru"
|
#define D_TX20_WIND_SPEED_AVG "Průměrná rychlost větru"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Minimální rychlost větru"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Maximální rychlost větru"
|
#define D_TX20_WIND_SPEED_MAX "Maximální rychlost větru"
|
||||||
#define D_TX20_NORTH "S"
|
#define D_TX20_NORTH "S"
|
||||||
#define D_TX20_EAST "V"
|
#define D_TX20_EAST "V"
|
||||||
|
@ -503,10 +503,12 @@
|
|||||||
#define D_CALIBRATION "Kalibrierung"
|
#define D_CALIBRATION "Kalibrierung"
|
||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Wind Richtung"
|
#define D_TX20_WIND_DIRECTION "Windrichtung"
|
||||||
|
#define D_TX20_WIND_DEGREES "Windrichtung Grad"
|
||||||
#define D_TX20_WIND_SPEED "Windgeschwindigkeit"
|
#define D_TX20_WIND_SPEED "Windgeschwindigkeit"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Ø Windgeschwindigkeit"
|
#define D_TX20_WIND_SPEED_AVG "Windgeschwindigkeit Ø"
|
||||||
#define D_TX20_WIND_SPEED_MAX "max Windgeschwindigkeit"
|
#define D_TX20_WIND_SPEED_MIN "Windgeschwindigkeit Min"
|
||||||
|
#define D_TX20_WIND_SPEED_MAX "Windgeschwindigkeit Max"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "O"
|
#define D_TX20_EAST "O"
|
||||||
#define D_TX20_SOUTH "S"
|
#define D_TX20_SOUTH "S"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Κατεύθυνση ανέμου"
|
#define D_TX20_WIND_DIRECTION "Κατεύθυνση ανέμου"
|
||||||
|
#define D_TX20_WIND_DEGREES "Βαθμός ανέμου"
|
||||||
#define D_TX20_WIND_SPEED "Ταχύτητα ανέμου"
|
#define D_TX20_WIND_SPEED "Ταχύτητα ανέμου"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Μέση ταχύτητα ανέμου"
|
#define D_TX20_WIND_SPEED_AVG "Μέση ταχύτητα ανέμου"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Ελάχιστη ταχύτητα ανέμου"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Μέγιστη ταχύτητα ανέμου"
|
#define D_TX20_WIND_SPEED_MAX "Μέγιστη ταχύτητα ανέμου"
|
||||||
#define D_TX20_NORTH "Β"
|
#define D_TX20_NORTH "Β"
|
||||||
#define D_TX20_EAST "Α"
|
#define D_TX20_EAST "Α"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Wind Direction"
|
#define D_TX20_WIND_DIRECTION "Wind Direction"
|
||||||
|
#define D_TX20_WIND_DEGREES "Wind Degrees"
|
||||||
#define D_TX20_WIND_SPEED "Wind Speed"
|
#define D_TX20_WIND_SPEED "Wind Speed"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Wind Speed Avg"
|
#define D_TX20_WIND_SPEED_AVG "Wind Speed Avg"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Wind Speed Min"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Wind Speed Max"
|
#define D_TX20_WIND_SPEED_MAX "Wind Speed Max"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Dirección del Viento"
|
#define D_TX20_WIND_DIRECTION "Dirección del Viento"
|
||||||
|
#define D_TX20_WIND_DEGREES "Ángulo del Viento"
|
||||||
#define D_TX20_WIND_SPEED "Vel. del Viento"
|
#define D_TX20_WIND_SPEED "Vel. del Viento"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Vel. Prom. del Viento"
|
#define D_TX20_WIND_SPEED_AVG "Vel. Prom. del Viento"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Vel. Min. del Viento"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Vel. Max. del Viento"
|
#define D_TX20_WIND_SPEED_MAX "Vel. Max. del Viento"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_TX20.ino
|
//xsns_35_TX20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Direction du vent"
|
#define D_TX20_WIND_DIRECTION "Direction du vent"
|
||||||
|
#define D_TX20_WIND_DEGREES "Degré de vent"
|
||||||
#define D_TX20_WIND_SPEED "Vitesse du vent"
|
#define D_TX20_WIND_SPEED "Vitesse du vent"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Vitesse Moy."
|
#define D_TX20_WIND_SPEED_AVG "Vitesse Moy."
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Vitesse Min"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Vitesse Max"
|
#define D_TX20_WIND_SPEED_MAX "Vitesse Max"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "כיוון הרוח"
|
#define D_TX20_WIND_DIRECTION "כיוון הרוח"
|
||||||
|
#define D_TX20_WIND_DEGREES "זווית הרוח"
|
||||||
#define D_TX20_WIND_SPEED "מהירות הרוח"
|
#define D_TX20_WIND_SPEED "מהירות הרוח"
|
||||||
#define D_TX20_WIND_SPEED_AVG "מהירות הרוח ממוצעת"
|
#define D_TX20_WIND_SPEED_AVG "מהירות הרוח ממוצעת"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "מהירות הרוח היא מינימלית"
|
||||||
#define D_TX20_WIND_SPEED_MAX "מהירות הרוח מקסימלית"
|
#define D_TX20_WIND_SPEED_MAX "מהירות הרוח מקסימלית"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Szélirány"
|
#define D_TX20_WIND_DIRECTION "Szélirány"
|
||||||
|
#define D_TX20_WIND_DEGREES "Szél mértéke"
|
||||||
#define D_TX20_WIND_SPEED "Szélsebesség"
|
#define D_TX20_WIND_SPEED "Szélsebesség"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Átlag szélsebesség"
|
#define D_TX20_WIND_SPEED_AVG "Átlag szélsebesség"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Min. szélsebesség"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Max. szélsebesség"
|
#define D_TX20_WIND_SPEED_MAX "Max. szélsebesség"
|
||||||
#define D_TX20_NORTH "É"
|
#define D_TX20_NORTH "É"
|
||||||
#define D_TX20_EAST "K"
|
#define D_TX20_EAST "K"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Direzione Vento"
|
#define D_TX20_WIND_DIRECTION "Direzione Vento"
|
||||||
|
#define D_TX20_WIND_DEGREES "Angolo Vento"
|
||||||
#define D_TX20_WIND_SPEED "Velocità Vento"
|
#define D_TX20_WIND_SPEED "Velocità Vento"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Velocità Media Vento"
|
#define D_TX20_WIND_SPEED_AVG "Velocità Media Vento"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Velocità Minima Vento"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Velocità Massima Vento"
|
#define D_TX20_WIND_SPEED_MAX "Velocità Massima Vento"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,9 +504,11 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "풍향"
|
#define D_TX20_WIND_DIRECTION "풍향"
|
||||||
|
#define D_TX20_WIND_DEGREES "바람 정도"
|
||||||
#define D_TX20_WIND_SPEED "풍속"
|
#define D_TX20_WIND_SPEED "풍속"
|
||||||
#define D_TX20_WIND_SPEED_AVG "평균 풍속"
|
#define D_TX20_WIND_SPEED_AVG "평균 풍속"
|
||||||
#define D_TX20_WIND_SPEED_MAX "최대 풍속"
|
#define D_TX20_WIND_SPEED_MIN "풍속 최소"
|
||||||
|
#define D_TX20_WIND_SPEED_MAX "풍속 최대"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
#define D_TX20_SOUTH "S"
|
#define D_TX20_SOUTH "S"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Windrichting"
|
#define D_TX20_WIND_DIRECTION "Windrichting"
|
||||||
|
#define D_TX20_WIND_DEGREES "Wind graad"
|
||||||
#define D_TX20_WIND_SPEED "Windsnelheid"
|
#define D_TX20_WIND_SPEED "Windsnelheid"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Windsnelheid gemiddeld"
|
#define D_TX20_WIND_SPEED_AVG "Windsnelheid gemiddeld"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Windsnelhied minimum"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Windsnelhied maximaal"
|
#define D_TX20_WIND_SPEED_MAX "Windsnelhied maximaal"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Kierunek"
|
#define D_TX20_WIND_DIRECTION "Kierunek"
|
||||||
|
#define D_TX20_WIND_DEGREES "Grad"
|
||||||
#define D_TX20_WIND_SPEED "Prędkość"
|
#define D_TX20_WIND_SPEED "Prędkość"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Średnia prędkość"
|
#define D_TX20_WIND_SPEED_AVG "Średnia prędkość"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Minimalna prędkość"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Maksymalna prędkość"
|
#define D_TX20_WIND_SPEED_MAX "Maksymalna prędkość"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Direção do vento"
|
#define D_TX20_WIND_DIRECTION "Direção do vento"
|
||||||
|
#define D_TX20_WIND_DEGREES "Ângulo do vento"
|
||||||
#define D_TX20_WIND_SPEED "Velocidade do vento"
|
#define D_TX20_WIND_SPEED "Velocidade do vento"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Velocidade média do vento"
|
#define D_TX20_WIND_SPEED_AVG "Velocidade média do vento"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Velocidade do vento Mínima"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Velocidade do vento Máxima"
|
#define D_TX20_WIND_SPEED_MAX "Velocidade do vento Máxima"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "L"
|
#define D_TX20_EAST "L"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Direção do vento"
|
#define D_TX20_WIND_DIRECTION "Direção do vento"
|
||||||
|
#define D_TX20_WIND_DEGREES "Ângulo do vento"
|
||||||
#define D_TX20_WIND_SPEED "Velocidade do vento"
|
#define D_TX20_WIND_SPEED "Velocidade do vento"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Velocidade média do vento"
|
#define D_TX20_WIND_SPEED_AVG "Velocidade média do vento"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Velocidade mínima do vento"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Velocidade máxima do vento"
|
#define D_TX20_WIND_SPEED_MAX "Velocidade máxima do vento"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Wind Direction"
|
#define D_TX20_WIND_DIRECTION "Wind Direction"
|
||||||
|
#define D_TX20_WIND_DEGREES "Wind Degrees"
|
||||||
#define D_TX20_WIND_SPEED "Wind Speed"
|
#define D_TX20_WIND_SPEED "Wind Speed"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Wind Speed Avg"
|
#define D_TX20_WIND_SPEED_AVG "Wind Speed Avg"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Wind Speed Min"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Wind Speed Max"
|
#define D_TX20_WIND_SPEED_MAX "Wind Speed Max"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Smer vetra"
|
#define D_TX20_WIND_DIRECTION "Smer vetra"
|
||||||
|
#define D_TX20_WIND_DEGREES "Uhol vetra"
|
||||||
#define D_TX20_WIND_SPEED "Rýchlosť vetra"
|
#define D_TX20_WIND_SPEED "Rýchlosť vetra"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Priemerná rýchlosť vetra"
|
#define D_TX20_WIND_SPEED_AVG "Priemerná rýchlosť vetra"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Minimálna rýchlosť vetra"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Maximálna rýchlosť vetra"
|
#define D_TX20_WIND_SPEED_MAX "Maximálna rýchlosť vetra"
|
||||||
#define D_TX20_NORTH "S"
|
#define D_TX20_NORTH "S"
|
||||||
#define D_TX20_EAST "V"
|
#define D_TX20_EAST "V"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Vindriktning"
|
#define D_TX20_WIND_DIRECTION "Vindriktning"
|
||||||
|
#define D_TX20_WIND_DEGREES "Vindvinkel"
|
||||||
#define D_TX20_WIND_SPEED "Vindstyrka"
|
#define D_TX20_WIND_SPEED "Vindstyrka"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Vindstyrka medel"
|
#define D_TX20_WIND_SPEED_AVG "Vindstyrka medel"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Vindstyrka min"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Vindstyrka max"
|
#define D_TX20_WIND_SPEED_MAX "Vindstyrka max"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "Ö"
|
#define D_TX20_EAST "Ö"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Wind Direction"
|
#define D_TX20_WIND_DIRECTION "Wind Direction"
|
||||||
|
#define D_TX20_WIND_DEGREES "Wind Degrees"
|
||||||
#define D_TX20_WIND_SPEED "Wind Speed"
|
#define D_TX20_WIND_SPEED "Wind Speed"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Wind Speed Avg"
|
#define D_TX20_WIND_SPEED_AVG "Wind Speed Avg"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Wind Speed Min"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Wind Speed Max"
|
#define D_TX20_WIND_SPEED_MAX "Wind Speed Max"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Напрям вітру"
|
#define D_TX20_WIND_DIRECTION "Напрям вітру"
|
||||||
|
#define D_TX20_WIND_DEGREES "Кут вітру"
|
||||||
#define D_TX20_WIND_SPEED "Швидкість вітру"
|
#define D_TX20_WIND_SPEED "Швидкість вітру"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Середня швидкість вітру"
|
#define D_TX20_WIND_SPEED_AVG "Середня швидкість вітру"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Мінімальна швидкість вітру"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Максимальна швидкість вітру"
|
#define D_TX20_WIND_SPEED_MAX "Максимальна швидкість вітру"
|
||||||
#define D_TX20_NORTH "Пн"
|
#define D_TX20_NORTH "Пн"
|
||||||
#define D_TX20_EAST "Сх"
|
#define D_TX20_EAST "Сх"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "风向"
|
#define D_TX20_WIND_DIRECTION "风向"
|
||||||
|
#define D_TX20_WIND_DEGREES "风度"
|
||||||
#define D_TX20_WIND_SPEED "风速"
|
#define D_TX20_WIND_SPEED "风速"
|
||||||
#define D_TX20_WIND_SPEED_AVG "平均风速"
|
#define D_TX20_WIND_SPEED_AVG "平均风速"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "最低风速"
|
||||||
#define D_TX20_WIND_SPEED_MAX "最高风速"
|
#define D_TX20_WIND_SPEED_MAX "最高风速"
|
||||||
#define D_TX20_NORTH "北"
|
#define D_TX20_NORTH "北"
|
||||||
#define D_TX20_EAST "东"
|
#define D_TX20_EAST "东"
|
||||||
|
@ -504,8 +504,10 @@
|
|||||||
|
|
||||||
//xsns_35_tx20.ino
|
//xsns_35_tx20.ino
|
||||||
#define D_TX20_WIND_DIRECTION "Wind Direction"
|
#define D_TX20_WIND_DIRECTION "Wind Direction"
|
||||||
|
#define D_TX20_WIND_DEGREES "Wind Degrees"
|
||||||
#define D_TX20_WIND_SPEED "Wind Speed"
|
#define D_TX20_WIND_SPEED "Wind Speed"
|
||||||
#define D_TX20_WIND_SPEED_AVG "Wind Speed Avg"
|
#define D_TX20_WIND_SPEED_AVG "Wind Speed Avg"
|
||||||
|
#define D_TX20_WIND_SPEED_MIN "Wind Speed Min"
|
||||||
#define D_TX20_WIND_SPEED_MAX "Wind Speed Max"
|
#define D_TX20_WIND_SPEED_MAX "Wind Speed Max"
|
||||||
#define D_TX20_NORTH "N"
|
#define D_TX20_NORTH "N"
|
||||||
#define D_TX20_EAST "E"
|
#define D_TX20_EAST "E"
|
||||||
|
@ -416,6 +416,7 @@
|
|||||||
// #define EXS_MCU_CMNDS // Add command to send MCU commands (+0k8 code)
|
// #define EXS_MCU_CMNDS // Add command to send MCU commands (+0k8 code)
|
||||||
//#define USE_HOTPLUG // Add support for sensor HotPlug
|
//#define USE_HOTPLUG // Add support for sensor HotPlug
|
||||||
//#define USE_KEELOQ
|
//#define USE_KEELOQ
|
||||||
|
//#define USE_DEVICE_GROUPS // Add support for device groups (+5k6 code)
|
||||||
|
|
||||||
// -- Optional light modules ----------------------
|
// -- Optional light modules ----------------------
|
||||||
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
|
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
|
||||||
|
@ -104,7 +104,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
|
|||||||
uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa
|
uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa
|
||||||
uint32_t zigbee_use_names : 1; // bit 1 (v8.1.0.4) - SetOption83 - Use FriendlyNames instead of ShortAddresses when possible
|
uint32_t zigbee_use_names : 1; // bit 1 (v8.1.0.4) - SetOption83 - Use FriendlyNames instead of ShortAddresses when possible
|
||||||
uint32_t awsiot_shadow : 1; // bit 2 (v8.1.0.5) - SetOption84 - (AWS IoT) publish MQTT state to a device shadow
|
uint32_t awsiot_shadow : 1; // bit 2 (v8.1.0.5) - SetOption84 - (AWS IoT) publish MQTT state to a device shadow
|
||||||
uint32_t spare03 : 1;
|
uint32_t device_groups_enabled : 1; // bit 3 (v8.1.0.9) - SetOption85 - Enable Device Groups
|
||||||
uint32_t spare04 : 1;
|
uint32_t spare04 : 1;
|
||||||
uint32_t spare05 : 1;
|
uint32_t spare05 : 1;
|
||||||
uint32_t spare06 : 1;
|
uint32_t spare06 : 1;
|
||||||
@ -132,7 +132,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
|
|||||||
uint32_t spare28 : 1;
|
uint32_t spare28 : 1;
|
||||||
uint32_t spare29 : 1;
|
uint32_t spare29 : 1;
|
||||||
uint32_t spare30 : 1;
|
uint32_t spare30 : 1;
|
||||||
uint32_t spare31 : 1;
|
uint32_t force_sdk_erase : 1; // bit 31 (v8.1.0.9) - SetOption113 - Force erase of SDK wifi calibrate secore on restart
|
||||||
};
|
};
|
||||||
} SysBitfield4;
|
} SysBitfield4;
|
||||||
|
|
||||||
@ -466,13 +466,14 @@ struct SYSCFG {
|
|||||||
uint8_t sps30_inuse_hours; // F02
|
uint8_t sps30_inuse_hours; // F02
|
||||||
uint8_t hotplug_scan; // F03
|
uint8_t hotplug_scan; // F03
|
||||||
uint8_t reserved1; // F04 - reserved for s-hadinger
|
uint8_t reserved1; // F04 - reserved for s-hadinger
|
||||||
uint8_t free_f05[191]; // F05
|
uint8_t free_f05[183]; // F05
|
||||||
|
|
||||||
uint32_t keeloq_master_msb; // FC4
|
|
||||||
uint32_t keeloq_master_lsb; // FC8
|
|
||||||
uint32_t keeloq_serial; // FCD
|
|
||||||
uint32_t keeloq_count; // FD0
|
|
||||||
|
|
||||||
|
uint32_t keeloq_master_msb; // FBC
|
||||||
|
uint32_t keeloq_master_lsb; // FC0
|
||||||
|
uint32_t keeloq_serial; // FC4
|
||||||
|
uint32_t keeloq_count; // FC8
|
||||||
|
uint32_t device_group_share_in; // FCC - Bitmask of device group items imported
|
||||||
|
uint32_t device_group_share_out; // FD0 - Bitmask of device group items exported
|
||||||
uint32_t bootcount_reset_time; // FD4
|
uint32_t bootcount_reset_time; // FD4
|
||||||
int adc_param4; // FD8
|
int adc_param4; // FD8
|
||||||
uint32_t shutter_button[MAX_KEYS]; // FDC
|
uint32_t shutter_button[MAX_KEYS]; // FDC
|
||||||
|
@ -277,6 +277,23 @@ bool RtcRebootValid(void)
|
|||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* Config - Flash
|
* Config - Flash
|
||||||
|
*
|
||||||
|
* Tasmota 1M flash usage
|
||||||
|
* 0x00000000 - Unzipped binary bootloader
|
||||||
|
* 0x00001000 - Unzipped binary code start
|
||||||
|
* ::::
|
||||||
|
* 0x000xxxxx - Unzipped binary code end
|
||||||
|
* 0x000x1000 - First page used by Core OTA
|
||||||
|
* ::::
|
||||||
|
* 0x000F3000 - Tasmota Quick Power Cycle counter (SETTINGS_LOCATION - CFG_ROTATES) - First four bytes only
|
||||||
|
* 0x000F4000 - First Tasmota rotating settings page
|
||||||
|
* ::::
|
||||||
|
* 0x000FA000 - Last Tasmota rotating settings page = Last page used by Core OTA
|
||||||
|
* 0x000FB000 - Core SPIFFS end = Core EEPROM = Tasmota settings page during OTA and when no flash rotation is active (SETTINGS_LOCATION)
|
||||||
|
* 0x000FC000 - SDK - Uses first 128 bytes for phy init data mirrored by Core in RAM. See core_esp8266_phy.cpp phy_init_data[128] = Core user_rf_cal_sector
|
||||||
|
* 0x000FD000 - SDK - Uses scattered bytes from 0x340 (iTead use as settings storage from 0x000FD000)
|
||||||
|
* 0x000FE000 - SDK - Uses scattered bytes from 0x340 (iTead use as mirrored settings storage from 0x000FE000)
|
||||||
|
* 0x000FF000 - SDK - Uses at least first 32 bytes of this page - Tasmota Zigbee persistence from 0x000FF800 to 0x000FFFFF
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -728,6 +745,7 @@ void SettingsErase(uint8_t type)
|
|||||||
1 = Erase 16k SDK parameter area near end of flash as seen by SDK (0x0xFCxxx - 0x0xFFFFF) solving possible wifi errors
|
1 = Erase 16k SDK parameter area near end of flash as seen by SDK (0x0xFCxxx - 0x0xFFFFF) solving possible wifi errors
|
||||||
2 = Erase Tasmota parameter area (0x0xF3xxx - 0x0xFBFFF)
|
2 = Erase Tasmota parameter area (0x0xF3xxx - 0x0xFBFFF)
|
||||||
3 = Erase Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
|
3 = Erase Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
|
||||||
|
4 = Erase SDK parameter area used for wifi calibration (0x0FCxxx - 0x0FCFFF)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FIRMWARE_MINIMAL
|
#ifndef FIRMWARE_MINIMAL
|
||||||
@ -745,8 +763,18 @@ void SettingsErase(uint8_t type)
|
|||||||
_sectorStart = SETTINGS_LOCATION - CFG_ROTATES; // Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
|
_sectorStart = SETTINGS_LOCATION - CFG_ROTATES; // Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
|
||||||
_sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; // Flash size as seen by SDK
|
_sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; // Flash size as seen by SDK
|
||||||
}
|
}
|
||||||
|
else if (4 == type) {
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " %d " D_UNIT_SECTORS), _sectorEnd - _sectorStart);
|
// _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4; // SDK phy area and Core calibration sector (0x0FC000)
|
||||||
|
_sectorStart = SETTINGS_LOCATION +1; // SDK phy area and Core calibration sector (0x0FC000)
|
||||||
|
_sectorEnd = _sectorStart +1; // SDK end of phy area and Core calibration sector (0x0FCFFF)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
else if (5 == type) {
|
||||||
|
_sectorStart = (ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE) -4; // SDK phy area and Core calibration sector (0xxFC000)
|
||||||
|
_sectorEnd = _sectorStart +1; // SDK end of phy area and Core calibration sector (0xxFCFFF)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " from 0x%08X to 0x%08X"), _sectorStart * SPI_FLASH_SEC_SIZE, (_sectorEnd * SPI_FLASH_SEC_SIZE) -1);
|
||||||
|
|
||||||
// EspErase(_sectorStart, _sectorEnd); // Arduino core and SDK - erases flash as seen by SDK
|
// EspErase(_sectorStart, _sectorEnd); // Arduino core and SDK - erases flash as seen by SDK
|
||||||
EsptoolErase(_sectorStart, _sectorEnd); // Esptool - erases flash completely
|
EsptoolErase(_sectorStart, _sectorEnd); // Esptool - erases flash completely
|
||||||
@ -755,7 +783,7 @@ void SettingsErase(uint8_t type)
|
|||||||
|
|
||||||
void SettingsSdkErase(void)
|
void SettingsSdkErase(void)
|
||||||
{
|
{
|
||||||
WiFi.disconnect(true); // Delete SDK wifi config
|
WiFi.disconnect(false); // Delete SDK wifi config
|
||||||
SettingsErase(1);
|
SettingsErase(1);
|
||||||
delay(1000);
|
delay(1000);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,9 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
|
|||||||
#ifdef USE_I2C
|
#ifdef USE_I2C
|
||||||
D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|"
|
D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
D_CMND_DEVGROUP_SHARE "|"
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
D_CMND_SENSOR "|" D_CMND_DRIVER;
|
D_CMND_SENSOR "|" D_CMND_DRIVER;
|
||||||
|
|
||||||
void (* const TasmotaCommand[])(void) PROGMEM = {
|
void (* const TasmotaCommand[])(void) PROGMEM = {
|
||||||
@ -45,6 +48,9 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
|
|||||||
#ifdef USE_I2C
|
#ifdef USE_I2C
|
||||||
&CmndI2cScan, CmndI2cDriver,
|
&CmndI2cScan, CmndI2cDriver,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
&CmndDevGroupShare,
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
&CmndSensor, &CmndDriver };
|
&CmndSensor, &CmndDriver };
|
||||||
|
|
||||||
const char kWifiConfig[] PROGMEM =
|
const char kWifiConfig[] PROGMEM =
|
||||||
@ -1664,6 +1670,17 @@ void CmndI2cDriver(void)
|
|||||||
}
|
}
|
||||||
#endif // USE_I2C
|
#endif // USE_I2C
|
||||||
|
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
void CmndDevGroupShare(void)
|
||||||
|
{
|
||||||
|
uint32_t parm[2] = { Settings.device_group_share_in, Settings.device_group_share_out };
|
||||||
|
ParseParameters(2, parm);
|
||||||
|
Settings.device_group_share_in = parm[0];
|
||||||
|
Settings.device_group_share_out = parm[1];
|
||||||
|
Response_P(PSTR("{\"" D_CMND_DEVGROUP_SHARE "\":{\"In\":\"%X\",\"Out\":\"%X\"}}"), Settings.device_group_share_in, Settings.device_group_share_out);
|
||||||
|
}
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
|
|
||||||
void CmndSensor(void)
|
void CmndSensor(void)
|
||||||
{
|
{
|
||||||
XsnsCall(FUNC_COMMAND_SENSOR);
|
XsnsCall(FUNC_COMMAND_SENSOR);
|
||||||
|
713
tasmota/support_device_groups.ino
Normal file
713
tasmota/support_device_groups.ino
Normal file
@ -0,0 +1,713 @@
|
|||||||
|
/*
|
||||||
|
support_device_groups.ino - device groups support for Tasmota
|
||||||
|
|
||||||
|
Copyright (C) 2020 Paul C Diem
|
||||||
|
|
||||||
|
Device group allow multiple devices to be in a group with power, light
|
||||||
|
brightness, fade and speed settings and other module-specific settings
|
||||||
|
kept in sync across all devices in the group.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
|
||||||
|
//#define DEVICE_GROUPS_DEBUG
|
||||||
|
|
||||||
|
extern bool udp_connected;
|
||||||
|
|
||||||
|
struct device_group_member {
|
||||||
|
struct device_group_member * flink;
|
||||||
|
IPAddress ip_address;
|
||||||
|
uint32_t timeout_time;
|
||||||
|
uint16_t received_sequence;
|
||||||
|
uint16_t acked_sequence;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct device_group {
|
||||||
|
uint32_t next_ack_check_time;
|
||||||
|
uint16_t last_full_status_sequence;
|
||||||
|
uint16_t message_length;
|
||||||
|
uint8_t message_header_length;
|
||||||
|
uint8_t initial_status_requests_remaining;
|
||||||
|
bool local;
|
||||||
|
char group_name[TOPSZ];
|
||||||
|
char message[128];
|
||||||
|
uint8_t group_member_count;
|
||||||
|
struct device_group_member * device_group_members;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct device_group * device_groups;
|
||||||
|
uint16_t outgoing_sequence = 0;
|
||||||
|
bool device_groups_initialized = false;
|
||||||
|
bool device_groups_initialization_failed = false;
|
||||||
|
bool building_status_message = false;
|
||||||
|
bool processing_remote_device_message = false;
|
||||||
|
bool waiting_for_acks;
|
||||||
|
bool udp_was_connected = false;
|
||||||
|
|
||||||
|
void DeviceGroupsInit(void)
|
||||||
|
{
|
||||||
|
// Initialize the device information for each device group. The group name is the MQTT group topic.
|
||||||
|
device_groups = (struct device_group *)calloc(device_group_count, sizeof(struct device_group));
|
||||||
|
if (device_groups == nullptr) {
|
||||||
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating %u-element device group array"), device_group_count);
|
||||||
|
device_groups_initialization_failed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) {
|
||||||
|
struct device_group * device_group = &device_groups[device_group_index];
|
||||||
|
strcpy(device_group->group_name, SettingsText((device_group_index == 0 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + device_group_index - 1)));
|
||||||
|
device_group->message_header_length = sprintf_P(device_group->message, PSTR("%s%s HTTP/1.1\n\n"), kDeviceGroupMessage, device_group->group_name);
|
||||||
|
device_group->last_full_status_sequence = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_groups[0].local = true;
|
||||||
|
|
||||||
|
// If both in and out shared items masks are 0, assume they're unitialized and initialize them.
|
||||||
|
if (!Settings.device_group_share_in && !Settings.device_group_share_out) {
|
||||||
|
Settings.device_group_share_in = Settings.device_group_share_out = 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_groups_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
char * IPAddressToString(const IPAddress& ip_address)
|
||||||
|
{
|
||||||
|
static char buffer[16];
|
||||||
|
sprintf_P(buffer, PSTR("%u.%u.%u.%u"), ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if we're configured to share the specified item.
|
||||||
|
bool DeviceGroupItemShared(bool incoming, uint8_t item)
|
||||||
|
{
|
||||||
|
uint8_t mask = 0;
|
||||||
|
switch (item) {
|
||||||
|
case DGR_ITEM_LIGHT_BRI:
|
||||||
|
mask = DGR_SHARE_LIGHT_BRI;
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_POWER:
|
||||||
|
mask = DGR_SHARE_POWER;
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_LIGHT_SCHEME:
|
||||||
|
mask = DGR_SHARE_LIGHT_SCHEME;
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_LIGHT_FIXED_COLOR:
|
||||||
|
case DGR_ITEM_LIGHT_CHANNELS:
|
||||||
|
mask = DGR_SHARE_LIGHT_COLOR;
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_LIGHT_FADE:
|
||||||
|
case DGR_ITEM_LIGHT_SPEED:
|
||||||
|
mask = DGR_SHARE_LIGHT_FADE;
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_BRI_MIN:
|
||||||
|
mask = DGR_SHARE_BRI_MIN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (!mask || ((incoming ? Settings.device_group_share_in : Settings.device_group_share_out) & mask));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * label)
|
||||||
|
{
|
||||||
|
for (int attempt = 1; attempt <= 5; attempt++) {
|
||||||
|
if (PortUdp.beginPacket(ip, 1900)) {
|
||||||
|
PortUdp.write(packet, len);
|
||||||
|
if (PortUdp.endPacket()) return;
|
||||||
|
}
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error sending %s packet"), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...)
|
||||||
|
{
|
||||||
|
// If device groups are not enabled, ignore this request.
|
||||||
|
if (!Settings.flag4.device_groups_enabled) return;
|
||||||
|
|
||||||
|
// If UDP is not set up, ignore this request.
|
||||||
|
if (!udp_connected) return;
|
||||||
|
|
||||||
|
// If we're currently processing a remote device message, ignore this request.
|
||||||
|
if (processing_remote_device_message) return;
|
||||||
|
|
||||||
|
// Get a pointer to the device information for this device.
|
||||||
|
device_group * device_group = &device_groups[device_group_index];
|
||||||
|
|
||||||
|
// If we're still sending initial status requests, ignore this request.
|
||||||
|
if (device_group->initial_status_requests_remaining) return;
|
||||||
|
|
||||||
|
// A full status request is a request from a remote device for the status of every item we
|
||||||
|
// control. As long as we're building it, we may as well multicast the status update to all
|
||||||
|
// device group members.
|
||||||
|
char * message_ptr = &device_group->message[device_group->message_header_length];
|
||||||
|
if (message_type == DGR_MSGTYP_FULL_STATUS) {
|
||||||
|
|
||||||
|
// Set the flag indicating we're currently building a status message. SendDeviceGroupMessage
|
||||||
|
// will build but not send messages while this flag is set.
|
||||||
|
building_status_message = true;
|
||||||
|
|
||||||
|
// Call the drivers to build the status update.
|
||||||
|
if (!++outgoing_sequence) outgoing_sequence = 1;
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s full status packet"), device_group->group_name);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
device_group->message_length = 0;
|
||||||
|
SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power);
|
||||||
|
XdrvMailbox.command_code = DGR_ITEM_STATUS;
|
||||||
|
XdrvCall(FUNC_DEVICE_GROUP_REQUEST);
|
||||||
|
building_status_message = false;
|
||||||
|
|
||||||
|
// If we have nothing to share with the other members, restore the message sequence and return.
|
||||||
|
if (!device_group->message_length) {
|
||||||
|
if (!--outgoing_sequence) outgoing_sequence = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device_group->last_full_status_sequence = outgoing_sequence;
|
||||||
|
|
||||||
|
// Set the status update flag in the outgoing message.
|
||||||
|
*(message_ptr + 2) |= DGR_FLAG_FULL_STATUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
bool shared;
|
||||||
|
uint8_t item;
|
||||||
|
uint32_t value;
|
||||||
|
uint8_t * value_ptr;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s packet"), device_group->group_name);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
uint16_t original_sequence = outgoing_sequence;
|
||||||
|
if (!building_status_message && message_type != DGR_MSGTYP_PARTIAL_UPDATE && !++outgoing_sequence) outgoing_sequence = 1;
|
||||||
|
*message_ptr++ = outgoing_sequence & 0xff;
|
||||||
|
*message_ptr++ = outgoing_sequence >> 8;
|
||||||
|
|
||||||
|
value = 0;
|
||||||
|
if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME)
|
||||||
|
value |= DGR_FLAG_MORE_TO_COME;
|
||||||
|
else if (message_type == DGR_MSGTYP_UPDATE_DIRECT)
|
||||||
|
value |= DGR_FLAG_DIRECT;
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">sequence=%u, flags=%u"), outgoing_sequence, value);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
*message_ptr++ = value & 0xff;
|
||||||
|
*message_ptr++ = value >> 8;
|
||||||
|
char * first_item_ptr = message_ptr;
|
||||||
|
|
||||||
|
// If we're still building this update or all group members haven't acknowledged the previous
|
||||||
|
// update yet, update the message to include these new updates. First we need to rebuild the
|
||||||
|
// previous update message to remove any items and their values that are included in this new
|
||||||
|
// update.
|
||||||
|
if (device_group->message_length) {
|
||||||
|
uint8_t item_array[32];
|
||||||
|
int item_index = 0;
|
||||||
|
int kept_item_count = 0;
|
||||||
|
|
||||||
|
// Build an array of all the items in this new update.
|
||||||
|
va_start(ap, message_type);
|
||||||
|
while ((item = va_arg(ap, int))) {
|
||||||
|
item_array[item_index++] = item;
|
||||||
|
if (item <= DGR_ITEM_MAX_32BIT)
|
||||||
|
va_arg(ap, int);
|
||||||
|
else if (item <= DGR_ITEM_MAX_STRING)
|
||||||
|
va_arg(ap, char *);
|
||||||
|
else {
|
||||||
|
switch (item) {
|
||||||
|
case DGR_ITEM_LIGHT_CHANNELS:
|
||||||
|
va_arg(ap, uint8_t *) ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
item_array[item_index] = 0;
|
||||||
|
|
||||||
|
// Rebuild the previous update message, removing any items their values that are included in
|
||||||
|
// this new update.
|
||||||
|
char * previous_message_ptr = message_ptr;
|
||||||
|
while (item = *previous_message_ptr++) {
|
||||||
|
|
||||||
|
// Search for this item in the new update.
|
||||||
|
for (item_index = 0; item_array[item_index]; item_index++) {
|
||||||
|
if (item_array[item_index] == item) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this item was found in the new update skip over it and it's value.
|
||||||
|
if (item_array[item_index]) {
|
||||||
|
if (item <= DGR_ITEM_MAX_32BIT) {
|
||||||
|
previous_message_ptr++;
|
||||||
|
if (item > DGR_ITEM_MAX_8BIT) {
|
||||||
|
previous_message_ptr++;
|
||||||
|
if (item > DGR_ITEM_MAX_16BIT) {
|
||||||
|
previous_message_ptr++;
|
||||||
|
previous_message_ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item <= DGR_ITEM_MAX_STRING)
|
||||||
|
previous_message_ptr += *previous_message_ptr++;
|
||||||
|
else {
|
||||||
|
switch (item) {
|
||||||
|
case DGR_ITEM_LIGHT_CHANNELS:
|
||||||
|
previous_message_ptr += 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this item was not found in the new udpate, copy it to the new update message.
|
||||||
|
else {
|
||||||
|
*message_ptr++ = item;
|
||||||
|
if (item <= DGR_ITEM_MAX_32BIT) {
|
||||||
|
*message_ptr++ = *previous_message_ptr++;
|
||||||
|
if (item > DGR_ITEM_MAX_8BIT) {
|
||||||
|
*message_ptr++ = *previous_message_ptr++;
|
||||||
|
if (item > DGR_ITEM_MAX_16BIT) {
|
||||||
|
*message_ptr++ = *previous_message_ptr++;
|
||||||
|
*message_ptr++ = *previous_message_ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item <= DGR_ITEM_MAX_STRING) {
|
||||||
|
*message_ptr++ = value = *previous_message_ptr++;
|
||||||
|
memmove(message_ptr, previous_message_ptr, value);
|
||||||
|
previous_message_ptr += value;
|
||||||
|
message_ptr += value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (item) {
|
||||||
|
case DGR_ITEM_LIGHT_CHANNELS:
|
||||||
|
memmove(message_ptr, previous_message_ptr, 5);
|
||||||
|
previous_message_ptr += 5;
|
||||||
|
message_ptr += 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kept_item_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
|
// Itertate through the passed items adding them and their values to the message.
|
||||||
|
va_start(ap, message_type);
|
||||||
|
while ((item = va_arg(ap, int))) {
|
||||||
|
shared = DeviceGroupItemShared(false, item);
|
||||||
|
if (shared) *message_ptr++ = item;
|
||||||
|
if (item <= DGR_ITEM_MAX_32BIT) {
|
||||||
|
value = va_arg(ap, int);
|
||||||
|
if (shared) {
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u"), item, value);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
*message_ptr++ = value & 0xff;
|
||||||
|
if (item > DGR_ITEM_MAX_8BIT) {
|
||||||
|
value >>= 8;
|
||||||
|
*message_ptr++ = value & 0xff;
|
||||||
|
}
|
||||||
|
if (item > DGR_ITEM_MAX_16BIT) {
|
||||||
|
value >>= 8;
|
||||||
|
*message_ptr++ = value & 0xff;
|
||||||
|
*message_ptr++ = value >> 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item <= DGR_ITEM_MAX_STRING) {
|
||||||
|
value_ptr = va_arg(ap, uint8_t *);
|
||||||
|
if (shared) {
|
||||||
|
value = strlen((const char *)value_ptr);
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%s"), item, value_ptr);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
*message_ptr++ = value;
|
||||||
|
memcpy(message_ptr, value_ptr, value);
|
||||||
|
message_ptr += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (item) {
|
||||||
|
case DGR_ITEM_LIGHT_CHANNELS:
|
||||||
|
value_ptr = va_arg(ap, uint8_t *);
|
||||||
|
if (shared) {
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u,%u,%u,%u,%u"), item, *value_ptr, *(value_ptr + 1), *(value_ptr + 2), *(value_ptr + 3), *(value_ptr + 4));
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
memmove(message_ptr, value_ptr, 5);
|
||||||
|
message_ptr += 5;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
// If there weren't any items added to the message, restore the outgoing message sequence and
|
||||||
|
// return.
|
||||||
|
if (message_ptr == first_item_ptr) {
|
||||||
|
outgoing_sequence = original_sequence;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the EOL item code and calculate the message length.
|
||||||
|
*message_ptr++ = 0;
|
||||||
|
device_group->message_length = message_ptr - device_group->message;
|
||||||
|
|
||||||
|
// If there's going to be more items added to this message, return.
|
||||||
|
if (building_status_message || message_type == DGR_MSGTYP_PARTIAL_UPDATE) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multicast the packet.
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via multicast, sequence=%u"), device_group->message_length, device_group->group_name, device_group->message[device_group->message_header_length] | device_group->message[device_group->message_header_length + 1] << 8);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Multicast"));
|
||||||
|
device_group->next_ack_check_time = millis() + 100;
|
||||||
|
|
||||||
|
if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME) {
|
||||||
|
for (struct device_group_member * device_group_member = device_group->device_group_members; device_group_member != nullptr; device_group_member = device_group_member->flink) {
|
||||||
|
device_group_member->acked_sequence = outgoing_sequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
waiting_for_acks = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessDeviceGroupMessage(char * packet, int packet_length)
|
||||||
|
{
|
||||||
|
// Make the group name a null-terminated string.
|
||||||
|
char * message_group_name = packet + sizeof(DEVICE_GROUP_MESSAGE) - 1;
|
||||||
|
char * message_ptr = strchr(message_group_name, ' ');
|
||||||
|
if (message_ptr == nullptr) return;
|
||||||
|
*message_ptr = 0;
|
||||||
|
|
||||||
|
// Search for a device group with the target group name. If one isn't found, return.
|
||||||
|
struct device_group * device_group;
|
||||||
|
uint32_t device_group_index = 0;
|
||||||
|
for (;;) {
|
||||||
|
device_group = &device_groups[device_group_index];
|
||||||
|
if (!strcmp(message_group_name, device_group->group_name)) break;
|
||||||
|
if (++device_group_index >= device_group_count) return;
|
||||||
|
}
|
||||||
|
*message_ptr++ = ' ';
|
||||||
|
|
||||||
|
// Find the group member. If this is a new group member, add it.
|
||||||
|
IPAddress remote_ip = PortUdp.remoteIP();
|
||||||
|
struct device_group_member * * flink = &device_group->device_group_members;
|
||||||
|
struct device_group_member * device_group_member;
|
||||||
|
for (;;) {
|
||||||
|
device_group_member = *flink;
|
||||||
|
if (!device_group_member) {
|
||||||
|
device_group_member = (struct device_group_member *)calloc(1, sizeof(struct device_group_member));
|
||||||
|
if (device_group_member == nullptr) {
|
||||||
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating device group member block"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: adding member %s (%p)"), IPAddressToString(remote_ip), device_group_member);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
device_group_member->ip_address = remote_ip;
|
||||||
|
*flink = device_group_member;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (device_group_member->ip_address == remote_ip) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
flink = &device_group_member->flink;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the start of the actual message (after the http header).
|
||||||
|
message_ptr = strstr_P(message_ptr, PSTR("\n\n"));
|
||||||
|
if (message_ptr == nullptr) return;
|
||||||
|
message_ptr += 2;
|
||||||
|
|
||||||
|
bool light_fade;
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t item;
|
||||||
|
uint16_t message_sequence;
|
||||||
|
int32_t value;
|
||||||
|
|
||||||
|
// Get the message sequence and flags.
|
||||||
|
if (packet_length - (message_ptr - packet) < 4) goto badmsg; // Malformed message - must be at least 16-bit sequence, 16-bit flags left
|
||||||
|
message_sequence = *message_ptr++;
|
||||||
|
message_sequence |= *message_ptr++ << 8;
|
||||||
|
flags = *message_ptr++;
|
||||||
|
flags |= *message_ptr++ << 8;
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Received device group %s packet from %s: sequence=%u, flags=%u"), device_group->group_name, IPAddressToString(remote_ip), message_sequence, flags);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
|
||||||
|
// If this is an ack message, save the message sequence if it's newwer than the last ack we
|
||||||
|
// received from this member.
|
||||||
|
if (flags == DGR_FLAG_ACK) {
|
||||||
|
if (message_sequence > device_group_member->acked_sequence || device_group_member->acked_sequence - message_sequence < 64536) {
|
||||||
|
device_group_member->acked_sequence = message_sequence;
|
||||||
|
}
|
||||||
|
device_group_member->timeout_time = 0;
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<ack"));
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an ack message to the sender.
|
||||||
|
if (!(flags & DGR_FLAG_MORE_TO_COME)) {
|
||||||
|
*(message_ptr - 2) = DGR_FLAG_ACK;
|
||||||
|
*(message_ptr - 1) = 0;
|
||||||
|
SendDeviceGroupPacket(remote_ip, packet, message_ptr - packet, PSTR("Ack"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a status request message, then if the requestor didn't just ack our previous full
|
||||||
|
// status update, send a full status update.
|
||||||
|
if ((flags & DGR_FLAG_STATUS_REQUEST)) {
|
||||||
|
if ((flags & DGR_FLAG_RESET) || device_group_member->acked_sequence != device_group->last_full_status_sequence) {
|
||||||
|
_SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_FULL_STATUS);
|
||||||
|
}
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<status request"));
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already processed this or a later message from this group member, ignore this message.
|
||||||
|
if (message_sequence < device_group_member->received_sequence && device_group_member->received_sequence - message_sequence > 64536) {
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<old message"));
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device_group_member->received_sequence = message_sequence;
|
||||||
|
|
||||||
|
// Set the flag indicating we're currently processing a remote device message.
|
||||||
|
// SendDeviceGroupMessage will not send messages while this flag is set.
|
||||||
|
processing_remote_device_message = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
XdrvMailbox
|
||||||
|
bool grpflg
|
||||||
|
bool usridx
|
||||||
|
uint16_t command_code Item code
|
||||||
|
uint32_t index Flags
|
||||||
|
uint32_t data_len String item value length
|
||||||
|
int32_t payload Integer item value
|
||||||
|
char *topic
|
||||||
|
char *data Pointer to non-integer item value
|
||||||
|
char *command nullptr
|
||||||
|
*/
|
||||||
|
XdrvMailbox.command = nullptr; // Indicates the source is a device group update
|
||||||
|
XdrvMailbox.index = flags;
|
||||||
|
light_fade = Settings.light_fade;
|
||||||
|
if (flags & (DGR_FLAG_MORE_TO_COME | DGR_FLAG_DIRECT)) Settings.light_fade = false;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (packet_length - (message_ptr - packet) < 1) goto badmsg; // Malformed message
|
||||||
|
item = *message_ptr++;
|
||||||
|
if (!item) break; // Done
|
||||||
|
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
switch (item) {
|
||||||
|
case DGR_ITEM_LIGHT_FADE:
|
||||||
|
case DGR_ITEM_LIGHT_SPEED:
|
||||||
|
case DGR_ITEM_LIGHT_BRI:
|
||||||
|
case DGR_ITEM_LIGHT_SCHEME:
|
||||||
|
case DGR_ITEM_LIGHT_FIXED_COLOR:
|
||||||
|
case DGR_ITEM_BRI_MIN:
|
||||||
|
case DGR_ITEM_BRI_PRESET_LOW:
|
||||||
|
case DGR_ITEM_BRI_PRESET_HIGH:
|
||||||
|
case DGR_ITEM_BRI_POWER_ON:
|
||||||
|
case DGR_ITEM_POWER:
|
||||||
|
case DGR_ITEM_LIGHT_CHANNELS:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: ********** invalid item=%u received from device group %s member %s"), item, device_group->group_name, IPAddressToString(remote_ip));
|
||||||
|
}
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
|
||||||
|
XdrvMailbox.command_code = item;
|
||||||
|
if (item <= DGR_ITEM_LAST_32BIT) {
|
||||||
|
value = *message_ptr++;
|
||||||
|
if (item > DGR_ITEM_MAX_8BIT) {
|
||||||
|
value |= *message_ptr++ << 8;
|
||||||
|
if (item > DGR_ITEM_MAX_16BIT) {
|
||||||
|
value |= *message_ptr++ << 16;
|
||||||
|
value |= *message_ptr++ << 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item == DGR_ITEM_LIGHT_FADE) {
|
||||||
|
light_fade = value;
|
||||||
|
}
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<item=%u, value=%u"), item, value);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
XdrvMailbox.payload = value;
|
||||||
|
}
|
||||||
|
else if (item <= DGR_ITEM_MAX_STRING) {
|
||||||
|
value = *message_ptr++;
|
||||||
|
if (value >= packet_length - (message_ptr - packet)) goto badmsg; // Malformed message
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<item=%u, value=%.*s"), item, value, message_ptr);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
XdrvMailbox.data_len = value;
|
||||||
|
XdrvMailbox.data = message_ptr;
|
||||||
|
message_ptr += value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (item) {
|
||||||
|
case DGR_ITEM_LIGHT_CHANNELS:
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<item=%u, value=%u,%u,%u,%u,%u"), item, *message_ptr, *(message_ptr + 1), *(message_ptr + 2), *(message_ptr + 3), *(message_ptr + 4));
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
XdrvMailbox.data = message_ptr;
|
||||||
|
message_ptr += 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DeviceGroupItemShared(true, item)) {
|
||||||
|
if (item == DGR_ITEM_POWER) {
|
||||||
|
for (uint32_t i = 0; i < devices_present; i++) {
|
||||||
|
uint32_t mask = 1 << i;
|
||||||
|
bool on = (value & mask);
|
||||||
|
if (on != (power & mask)) ExecuteCommandPower(i + 1, (on ? POWER_ON : POWER_OFF), SRC_REMOTE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
XdrvCall(FUNC_DEVICE_GROUP_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XdrvMailbox.command_code = DGR_ITEM_EOL;
|
||||||
|
XdrvCall(FUNC_DEVICE_GROUP_REQUEST);
|
||||||
|
Settings.light_fade = light_fade;
|
||||||
|
|
||||||
|
processing_remote_device_message = false;
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<processed"));
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
return;
|
||||||
|
|
||||||
|
badmsg:
|
||||||
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: malformed message received from %s"), IPAddressToString(remote_ip));
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("packet_length=%u, offset=%u"), packet_length, message_ptr - packet);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
processing_remote_device_message = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceGroupsLoop(void)
|
||||||
|
{
|
||||||
|
if (!Settings.flag4.device_groups_enabled) return;
|
||||||
|
if (udp_connected) {
|
||||||
|
if (!udp_was_connected) {
|
||||||
|
udp_was_connected = true;
|
||||||
|
|
||||||
|
if (!device_groups_initialized) DeviceGroupsInit();
|
||||||
|
if (device_groups_initialization_failed) return;
|
||||||
|
|
||||||
|
for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) {
|
||||||
|
device_group * device_group = &device_groups[device_group_index];
|
||||||
|
char * message_ptr = &device_group->message[device_group->message_header_length];
|
||||||
|
if (!++outgoing_sequence) outgoing_sequence = 1;
|
||||||
|
*message_ptr++ = outgoing_sequence & 0xff;
|
||||||
|
*message_ptr++ = outgoing_sequence >> 8;
|
||||||
|
*message_ptr++ = DGR_FLAG_RESET | DGR_FLAG_STATUS_REQUEST;
|
||||||
|
*message_ptr++ = 0;
|
||||||
|
device_group->message_length = message_ptr - device_group->message;
|
||||||
|
device_group->initial_status_requests_remaining = 5;
|
||||||
|
device_group->next_ack_check_time = millis() + 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
waiting_for_acks = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device_groups_initialization_failed) return;
|
||||||
|
|
||||||
|
if (waiting_for_acks) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
waiting_for_acks = false;
|
||||||
|
for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) {
|
||||||
|
device_group * device_group = &device_groups[device_group_index];
|
||||||
|
if (device_group->next_ack_check_time) {
|
||||||
|
if (device_group->next_ack_check_time <= now) {
|
||||||
|
if (device_group->initial_status_requests_remaining) {
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending initial status request for group %s"), device_group->group_name);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
if (--device_group->initial_status_requests_remaining) {
|
||||||
|
SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Initial"));
|
||||||
|
device_group->message[device_group->message_header_length + 2] = DGR_FLAG_STATUS_REQUEST; // The reset flag is on only for the first packet - turn it off now
|
||||||
|
device_group->next_ack_check_time = now + 200;
|
||||||
|
waiting_for_acks = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
device_group->next_ack_check_time = 0;
|
||||||
|
_SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_FULL_STATUS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bool acked = true;
|
||||||
|
struct device_group_member ** flink = &device_group->device_group_members;
|
||||||
|
struct device_group_member * device_group_member;
|
||||||
|
while ((device_group_member = *flink)) {
|
||||||
|
if (device_group_member->acked_sequence != outgoing_sequence) {
|
||||||
|
|
||||||
|
if (device_group_member->timeout_time && device_group_member->timeout_time < now) {
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: removing member %s (%p)"), IPAddressToString(device_group_member->ip_address), device_group_member);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
*flink = device_group_member->flink;
|
||||||
|
free(device_group_member);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#ifdef DEVICE_GROUPS_DEBUG
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via unicast to %s, sequence %u, last message acked=%u"), device_group->message_length, device_group->group_name, IPAddressToString(device_group_member->ip_address), outgoing_sequence, device_group_member->acked_sequence);
|
||||||
|
#endif // DEVICE_GROUPS_DEBUG
|
||||||
|
SendDeviceGroupPacket(device_group_member->ip_address, device_group->message, device_group->message_length, PSTR("Unicast"));
|
||||||
|
if (!device_group_member->timeout_time) device_group_member->timeout_time = now + 15000;
|
||||||
|
acked = false;
|
||||||
|
flink = &device_group_member->flink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
flink = &device_group_member->flink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (acked) {
|
||||||
|
device_group->next_ack_check_time = 0;
|
||||||
|
device_group->message_length = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
device_group->next_ack_check_time = now + 500;
|
||||||
|
waiting_for_acks = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
waiting_for_acks = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
udp_was_connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
@ -541,6 +541,9 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source)
|
|||||||
case POWER_TOGGLE:
|
case POWER_TOGGLE:
|
||||||
power ^= mask;
|
power ^= mask;
|
||||||
}
|
}
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
if (SRC_REMOTE != source && SRC_RETRY != source) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, power);
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
SetDevicePower(power, source);
|
SetDevicePower(power, source);
|
||||||
#ifdef USE_DOMOTICZ
|
#ifdef USE_DOMOTICZ
|
||||||
DomoticzUpdatePowerState(device);
|
DomoticzUpdatePowerState(device);
|
||||||
|
@ -85,54 +85,62 @@ void PollUdp(void)
|
|||||||
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer);
|
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer);
|
||||||
|
|
||||||
// Simple Service Discovery Protocol (SSDP)
|
// Simple Service Discovery Protocol (SSDP)
|
||||||
|
if (Settings.flag2.emulation) {
|
||||||
#ifdef USE_SCRIPT_HUE
|
#ifdef USE_SCRIPT_HUE
|
||||||
if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
|
if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
|
||||||
#else
|
#else
|
||||||
if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
|
if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
|
||||||
#endif
|
#endif
|
||||||
udp_response_mutex = true;
|
udp_response_mutex = true;
|
||||||
|
|
||||||
udp_remote_ip = PortUdp.remoteIP();
|
udp_remote_ip = PortUdp.remoteIP();
|
||||||
udp_remote_port = PortUdp.remotePort();
|
udp_remote_port = PortUdp.remotePort();
|
||||||
|
|
||||||
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"),
|
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"),
|
||||||
// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer);
|
// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer);
|
||||||
|
|
||||||
uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec
|
uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec
|
||||||
|
|
||||||
LowerCase(packet_buffer, packet_buffer);
|
LowerCase(packet_buffer, packet_buffer);
|
||||||
RemoveSpace(packet_buffer);
|
RemoveSpace(packet_buffer);
|
||||||
|
|
||||||
#ifdef USE_EMULATION_WEMO
|
#ifdef USE_EMULATION_WEMO
|
||||||
if (EMUL_WEMO == Settings.flag2.emulation) {
|
if (EMUL_WEMO == Settings.flag2.emulation) {
|
||||||
if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's
|
if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's
|
||||||
TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1);
|
TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || // type2 Echo 2g (echo & echo plus)
|
||||||
|
(strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
|
||||||
|
(strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
|
||||||
|
TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || // type2 Echo 2g (echo & echo plus)
|
|
||||||
(strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
|
|
||||||
(strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
|
|
||||||
TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // USE_EMULATION_WEMO
|
#endif // USE_EMULATION_WEMO
|
||||||
|
|
||||||
#ifdef USE_EMULATION_HUE
|
#ifdef USE_EMULATION_HUE
|
||||||
if (EMUL_HUE == Settings.flag2.emulation) {
|
if (EMUL_HUE == Settings.flag2.emulation) {
|
||||||
if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) ||
|
if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) ||
|
||||||
(strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) ||
|
(strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) ||
|
||||||
(strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
|
(strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
|
||||||
(strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
|
(strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
|
||||||
TickerMSearch.attach_ms(response_delay, HueRespondToMSearch);
|
TickerMSearch.attach_ms(response_delay, HueRespondToMSearch);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif // USE_EMULATION_HUE
|
#endif // USE_EMULATION_HUE
|
||||||
|
|
||||||
udp_response_mutex = false;
|
udp_response_mutex = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
if (Settings.flag4.device_groups_enabled && !strncmp_P(packet_buffer, kDeviceGroupMessage, sizeof(DEVICE_GROUP_MESSAGE) - 1)) {
|
||||||
|
ProcessDeviceGroupMessage(packet_buffer, len);
|
||||||
|
}
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
}
|
}
|
||||||
optimistic_yield(100);
|
optimistic_yield(100);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
* Wifi
|
* Wifi
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
// Enable only one of two below debug options
|
||||||
|
//#define WIFI_RF_MODE_RF_CAL // Set RF_MODE to RF_CAL for restart and deepsleep during user_rf_pre_init
|
||||||
|
//#define WIFI_RF_PRE_INIT // Set RF_MODE to RF_CAL for restart, deepsleep and power on during user_rf_pre_init
|
||||||
|
|
||||||
#ifndef WIFI_RSSI_THRESHOLD
|
#ifndef WIFI_RSSI_THRESHOLD
|
||||||
#define WIFI_RSSI_THRESHOLD 10 // Difference in dB between current network and scanned network
|
#define WIFI_RSSI_THRESHOLD 10 // Difference in dB between current network and scanned network
|
||||||
#endif
|
#endif
|
||||||
@ -559,7 +563,11 @@ void WifiCheck(uint8_t param)
|
|||||||
StopWebserver();
|
StopWebserver();
|
||||||
}
|
}
|
||||||
#ifdef USE_EMULATION
|
#ifdef USE_EMULATION
|
||||||
if (Settings.flag2.emulation) { UdpConnect(); }
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { UdpConnect(); }
|
||||||
|
#else // USE_DEVICE_GROUPS
|
||||||
|
if (Settings.flag2.emulation) { UdpConnect(); }
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
#endif // USE_EMULATION
|
#endif // USE_EMULATION
|
||||||
#endif // USE_WEBSERVER
|
#endif // USE_WEBSERVER
|
||||||
|
|
||||||
@ -605,6 +613,32 @@ void WifiSetOutputPower(void)
|
|||||||
WiFi.setOutputPower((float)(Settings.wifi_output_power) / 10);
|
WiFi.setOutputPower((float)(Settings.wifi_output_power) / 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
See Esp.h, core_esp8266_phy.cpp and test_overrides.ino
|
||||||
|
RF_DEFAULT = 0, // RF_CAL or not after deep-sleep wake up, depends on init data byte 108.
|
||||||
|
RF_CAL = 1, // RF_CAL after deep-sleep wake up, there will be large current.
|
||||||
|
RF_NO_CAL = 2, // no RF_CAL after deep-sleep wake up, there will only be small current.
|
||||||
|
RF_DISABLED = 4 // disable RF after deep-sleep wake up, just like modem sleep, there will be the smallest current.
|
||||||
|
*/
|
||||||
|
#ifdef WIFI_RF_MODE_RF_CAL
|
||||||
|
#ifndef USE_DEEPSLEEP
|
||||||
|
RF_MODE(RF_CAL);
|
||||||
|
#endif // USE_DEEPSLEEP
|
||||||
|
#endif // WIFI_RF_MODE_RF_CAL
|
||||||
|
|
||||||
|
#ifdef WIFI_RF_PRE_INIT
|
||||||
|
bool rf_pre_init_flag = false;
|
||||||
|
RF_PRE_INIT()
|
||||||
|
{
|
||||||
|
#ifndef USE_DEEPSLEEP
|
||||||
|
system_deep_sleep_set_option(1); // The option is 1 by default.
|
||||||
|
system_phy_set_rfoption(RF_CAL);
|
||||||
|
#endif // USE_DEEPSLEEP
|
||||||
|
system_phy_set_powerup_option(3); // 3: RF initialization will do the whole RF calibration which will take about 200ms; this increases the current consumption.
|
||||||
|
rf_pre_init_flag = true;
|
||||||
|
}
|
||||||
|
#endif // WIFI_RF_PRE_INIT
|
||||||
|
|
||||||
void WifiConnect(void)
|
void WifiConnect(void)
|
||||||
{
|
{
|
||||||
WifiSetState(0);
|
WifiSetState(0);
|
||||||
@ -614,32 +648,47 @@ void WifiConnect(void)
|
|||||||
Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2);
|
Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2);
|
||||||
Wifi.retry = Wifi.retry_init;
|
Wifi.retry = Wifi.retry_init;
|
||||||
Wifi.counter = 1;
|
Wifi.counter = 1;
|
||||||
|
|
||||||
|
#ifdef WIFI_RF_PRE_INIT
|
||||||
|
if (rf_pre_init_flag) {
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "Pre-init done"));
|
||||||
|
}
|
||||||
|
#endif // WIFI_RF_PRE_INIT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable from 6.0.0a until 6.1.0a - disabled due to possible cause of bad wifi connect on core 2.3.0
|
void WifiShutdown(bool option = false)
|
||||||
// Re-enabled from 6.3.0.7 with ESP.restart replaced by ESP.reset
|
|
||||||
void WifiDisconnect(void)
|
|
||||||
{
|
|
||||||
// Courtesy of EspEasy
|
|
||||||
WiFi.persistent(true); // use SDK storage of SSID/WPA parameters
|
|
||||||
ETS_UART_INTR_DISABLE();
|
|
||||||
wifi_station_disconnect(); // this will store empty ssid/wpa into sdk storage
|
|
||||||
ETS_UART_INTR_ENABLE();
|
|
||||||
WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
void WifiShutdown(void)
|
|
||||||
{
|
{
|
||||||
|
// option = false - Legacy disconnect also used by DeepSleep
|
||||||
|
// option = true - Disconnect with SDK wifi calibrate sector erase
|
||||||
delay(100); // Allow time for message xfer - disabled v6.1.0b
|
delay(100); // Allow time for message xfer - disabled v6.1.0b
|
||||||
|
|
||||||
|
#ifdef USE_EMULATION
|
||||||
|
UdpDisconnect();
|
||||||
|
#endif // USE_EMULATION
|
||||||
|
|
||||||
if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
|
if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
|
||||||
MqttDisconnect();
|
MqttDisconnect();
|
||||||
}
|
}
|
||||||
WifiDisconnect();
|
|
||||||
|
if (option && Settings.flag4.force_sdk_erase) { // SetOption113 - Force erase of SDK wifi calibrate sector on restart
|
||||||
|
WiFi.disconnect(false); // Disconnect wifi
|
||||||
|
SettingsErase(4); // Delete SDK wifi config and calibrate data
|
||||||
|
} else {
|
||||||
|
// Enable from 6.0.0a until 6.1.0a - disabled due to possible cause of bad wifi connect on core 2.3.0
|
||||||
|
// Re-enabled from 6.3.0.7 with ESP.restart replaced by ESP.reset
|
||||||
|
// Courtesy of EspEasy
|
||||||
|
WiFi.persistent(true); // use SDK storage of SSID/WPA parameters
|
||||||
|
ETS_UART_INTR_DISABLE();
|
||||||
|
wifi_station_disconnect(); // this will store empty ssid/wpa into sdk storage
|
||||||
|
ETS_UART_INTR_ENABLE();
|
||||||
|
WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters
|
||||||
|
}
|
||||||
|
delay(100); // Flush anything in the network buffers.
|
||||||
}
|
}
|
||||||
|
|
||||||
void EspRestart(void)
|
void EspRestart(void)
|
||||||
{
|
{
|
||||||
WifiShutdown();
|
WifiShutdown(true);
|
||||||
CrashDumpClear(); // Clear the stack dump in RTC
|
CrashDumpClear(); // Clear the stack dump in RTC
|
||||||
// ESP.restart(); // This results in exception 3 on restarts on core 2.3.0
|
// ESP.restart(); // This results in exception 3 on restarts on core 2.3.0
|
||||||
ESP.reset();
|
ESP.reset();
|
||||||
|
@ -277,7 +277,8 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FU
|
|||||||
FUNC_SET_POWER, FUNC_SET_DEVICE_POWER, FUNC_SHOW_SENSOR, FUNC_ANY_KEY,
|
FUNC_SET_POWER, FUNC_SET_DEVICE_POWER, FUNC_SHOW_SENSOR, FUNC_ANY_KEY,
|
||||||
FUNC_ENERGY_EVERY_SECOND, FUNC_ENERGY_RESET,
|
FUNC_ENERGY_EVERY_SECOND, FUNC_ENERGY_RESET,
|
||||||
FUNC_RULES_PROCESS, FUNC_SERIAL, FUNC_FREE_MEM, FUNC_BUTTON_PRESSED,
|
FUNC_RULES_PROCESS, FUNC_SERIAL, FUNC_FREE_MEM, FUNC_BUTTON_PRESSED,
|
||||||
FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS, FUNC_SET_SCHEME, FUNC_HOTPLUG_SCAN};
|
FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS, FUNC_SET_SCHEME, FUNC_HOTPLUG_SCAN,
|
||||||
|
FUNC_DEVICE_GROUP_REQUEST };
|
||||||
|
|
||||||
enum AddressConfigSteps { ADDR_IDLE, ADDR_RECEIVE, ADDR_SEND };
|
enum AddressConfigSteps { ADDR_IDLE, ADDR_RECEIVE, ADDR_SEND };
|
||||||
|
|
||||||
@ -299,8 +300,32 @@ enum SettingsTextIndex { SET_OTAURL,
|
|||||||
SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8,
|
SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8,
|
||||||
SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8,
|
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_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_MAX };
|
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 };
|
||||||
|
|
||||||
|
enum DeviceGroupMessageFlag { DGR_FLAG_RESET = 1, DGR_FLAG_STATUS_REQUEST = 2, DGR_FLAG_FULL_STATUS = 4, DGR_FLAG_ACK = 8, DGR_FLAG_MORE_TO_COME = 16, DGR_FLAG_DIRECT = 32 };
|
||||||
|
|
||||||
|
enum DeviceGroupItem { DGR_ITEM_EOL, DGR_ITEM_STATUS,
|
||||||
|
DGR_ITEM_LIGHT_FADE, DGR_ITEM_LIGHT_SPEED, DGR_ITEM_LIGHT_BRI, DGR_ITEM_LIGHT_SCHEME, DGR_ITEM_LIGHT_FIXED_COLOR,
|
||||||
|
DGR_ITEM_BRI_MIN, DGR_ITEM_BRI_PRESET_LOW, DGR_ITEM_BRI_PRESET_HIGH, DGR_ITEM_BRI_POWER_ON,
|
||||||
|
// Add new 8-bit items before this line
|
||||||
|
DGR_ITEM_LAST_8BIT, DGR_ITEM_MAX_8BIT = 63,
|
||||||
|
DGR_ITEM_ACK,
|
||||||
|
DGR_ITEM_ANALOG1, DGR_ITEM_ANALOG2, DGR_ITEM_ANALOG3, DGR_ITEM_ANALOG4, DGR_ITEM_ANALOG5,
|
||||||
|
// Add new 16-bit items before this line
|
||||||
|
DGR_ITEM_LAST_16BIT, DGR_ITEM_MAX_16BIT = 127,
|
||||||
|
DGR_ITEM_POWER,
|
||||||
|
// Add new 32-bit items before this line
|
||||||
|
DGR_ITEM_LAST_32BIT, DGR_ITEM_MAX_32BIT = 191,
|
||||||
|
// Add new string items before this line
|
||||||
|
DGR_ITEM_LAST_STRING, DGR_ITEM_MAX_STRING = 223,
|
||||||
|
DGR_ITEM_LIGHT_CHANNELS };
|
||||||
|
|
||||||
|
enum DeviceGroupShareItem { DGR_SHARE_POWER = 1, DGR_SHARE_LIGHT_BRI = 2, DGR_SHARE_LIGHT_FADE = 4, DGR_SHARE_LIGHT_SCHEME = 8,
|
||||||
|
DGR_SHARE_LIGHT_COLOR = 16, DGR_SHARE_BRI_MIN = 32 };
|
||||||
|
|
||||||
enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,
|
enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,
|
||||||
SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER,
|
SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER,
|
||||||
SRC_MAX };
|
SRC_MAX };
|
||||||
|
@ -329,6 +329,9 @@ void loop(void)
|
|||||||
#ifdef ROTARY_V1
|
#ifdef ROTARY_V1
|
||||||
RotaryLoop();
|
RotaryLoop();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
DeviceGroupsLoop();
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
BacklogLoop();
|
BacklogLoop();
|
||||||
|
|
||||||
if (TimeReached(state_50msecond)) {
|
if (TimeReached(state_50msecond)) {
|
||||||
|
@ -55,6 +55,9 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
|
|||||||
#ifdef USE_EMULATION_WEMO
|
#ifdef USE_EMULATION_WEMO
|
||||||
#define USE_EMULATION
|
#define USE_EMULATION
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
#define USE_EMULATION
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_MQTT_TLS
|
#ifdef USE_MQTT_TLS
|
||||||
const uint16_t WEB_LOG_SIZE = 2000; // Max number of characters in weblog
|
const uint16_t WEB_LOG_SIZE = 2000; // Max number of characters in weblog
|
||||||
@ -246,6 +249,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
|
|||||||
#undef USE_EMULATION // Disable Belkin WeMo and Hue Bridge emulation for Alexa (-16k code, -2k mem)
|
#undef USE_EMULATION // Disable Belkin WeMo and Hue Bridge emulation for Alexa (-16k code, -2k mem)
|
||||||
#undef USE_EMULATION_HUE // Disable Hue Bridge emulation for Alexa (+14k code, +2k mem common)
|
#undef USE_EMULATION_HUE // Disable Hue Bridge emulation for Alexa (+14k code, +2k mem common)
|
||||||
#undef USE_EMULATION_WEMO // Disable Belkin WeMo emulation for Alexa (+6k code, +2k mem common)
|
#undef USE_EMULATION_WEMO // Disable Belkin WeMo emulation for Alexa (+6k code, +2k mem common)
|
||||||
|
#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
|
||||||
#undef DEBUG_THEO // Disable debug code
|
#undef DEBUG_THEO // Disable debug code
|
||||||
#undef USE_DEBUG_DRIVER // Disable debug code
|
#undef USE_DEBUG_DRIVER // Disable debug code
|
||||||
#endif // FIRMWARE_KNX_NO_EMULATION
|
#endif // FIRMWARE_KNX_NO_EMULATION
|
||||||
@ -283,6 +287,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
|
|||||||
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
|
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
|
||||||
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
|
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
|
||||||
#undef USE_HOTPLUG // Disable support for HotPlug
|
#undef USE_HOTPLUG // Disable support for HotPlug
|
||||||
|
#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
|
||||||
|
|
||||||
#undef USE_ENERGY_SENSOR // Disable energy sensors (-14k code)
|
#undef USE_ENERGY_SENSOR // Disable energy sensors (-14k code)
|
||||||
#undef USE_PZEM004T // Disable PZEM004T energy sensor
|
#undef USE_PZEM004T // Disable PZEM004T energy sensor
|
||||||
@ -359,6 +364,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
|
|||||||
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
|
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
|
||||||
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
|
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
|
||||||
#undef USE_HOTPLUG // Disable support for HotPlug
|
#undef USE_HOTPLUG // Disable support for HotPlug
|
||||||
|
#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
|
||||||
|
|
||||||
// -- Optional light modules ----------------------
|
// -- Optional light modules ----------------------
|
||||||
//#undef USE_LIGHT // Also disable all Dimmer/Light support
|
//#undef USE_LIGHT // Also disable all Dimmer/Light support
|
||||||
@ -580,6 +586,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
|
|||||||
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
|
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
|
||||||
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
|
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
|
||||||
#undef USE_HOTPLUG // Disable support for HotPlug
|
#undef USE_HOTPLUG // Disable support for HotPlug
|
||||||
|
#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
|
||||||
|
|
||||||
// -- Optional light modules ----------------------
|
// -- Optional light modules ----------------------
|
||||||
#undef USE_LIGHT // Disable support for lights
|
#undef USE_LIGHT // Disable support for lights
|
||||||
@ -687,11 +694,11 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MESSZ
|
#ifndef MESSZ
|
||||||
//#define MESSZ 1040 // Max number of characters in JSON message string (Hass discovery and nice MQTT_MAX_PACKET_SIZE = 1200)
|
//#define MESSZ 1040 // Max number of characters in JSON message string (Hass discovery and nice MQTT_MAX_PACKET_SIZE = 1200)
|
||||||
#define MESSZ (MQTT_MAX_PACKET_SIZE -TOPSZ -7) // Max number of characters in JSON message string
|
#define MESSZ (MQTT_MAX_PACKET_SIZE -TOPSZ -7) // Max number of characters in JSON message string
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//#include <core_version.h> // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0)
|
//#include <core_version.h> // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0)
|
||||||
#ifndef ARDUINO_ESP8266_RELEASE
|
#ifndef ARDUINO_ESP8266_RELEASE
|
||||||
#define ARDUINO_ESP8266_RELEASE "STAGE"
|
#define ARDUINO_ESP8266_RELEASE "STAGE"
|
||||||
#endif
|
#endif
|
||||||
@ -700,6 +707,14 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
|
|||||||
#undef USE_MQTT_TLS_CA_CERT
|
#undef USE_MQTT_TLS_CA_CERT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
#define SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, ...) _SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, __VA_ARGS__, 0)
|
||||||
|
#define SendLocalDeviceGroupMessage(REQUEST_TYPE, ...) _SendDeviceGroupMessage(0, REQUEST_TYPE, __VA_ARGS__, 0)
|
||||||
|
#define DEVICE_GROUP_MESSAGE "M-TASMOTA_DGR/"
|
||||||
|
const char kDeviceGroupMessage[] PROGMEM = DEVICE_GROUP_MESSAGE;
|
||||||
|
uint8_t device_group_count = 1;
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
|
|
||||||
#ifdef DEBUG_TASMOTA_CORE
|
#ifdef DEBUG_TASMOTA_CORE
|
||||||
#define DEBUG_CORE_LOG(...) AddLog_Debug(__VA_ARGS__)
|
#define DEBUG_CORE_LOG(...) AddLog_Debug(__VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
|
@ -2824,6 +2824,7 @@ void (* const WebCommand[])(void) PROGMEM = {
|
|||||||
#ifdef USE_EMULATION
|
#ifdef USE_EMULATION
|
||||||
void CmndEmulation(void)
|
void CmndEmulation(void)
|
||||||
{
|
{
|
||||||
|
#if defined(USE_EMULATION_WEMO) || defined(USE_EMULATION_HUE)
|
||||||
#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE)
|
#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE)
|
||||||
if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) {
|
if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) {
|
||||||
#else
|
#else
|
||||||
@ -2837,6 +2838,7 @@ void CmndEmulation(void)
|
|||||||
Settings.flag2.emulation = XdrvMailbox.payload;
|
Settings.flag2.emulation = XdrvMailbox.payload;
|
||||||
restart_flag = 2;
|
restart_flag = 2;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
ResponseCmndNumber(Settings.flag2.emulation);
|
ResponseCmndNumber(Settings.flag2.emulation);
|
||||||
}
|
}
|
||||||
#endif // USE_EMULATION
|
#endif // USE_EMULATION
|
||||||
@ -2970,7 +2972,11 @@ bool Xdrv01(uint8_t function)
|
|||||||
case FUNC_LOOP:
|
case FUNC_LOOP:
|
||||||
PollDnsWebserver();
|
PollDnsWebserver();
|
||||||
#ifdef USE_EMULATION
|
#ifdef USE_EMULATION
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { PollUdp(); }
|
||||||
|
#else // USE_DEVICE_GROUPS
|
||||||
if (Settings.flag2.emulation) { PollUdp(); }
|
if (Settings.flag2.emulation) { PollUdp(); }
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
#endif // USE_EMULATION
|
#endif // USE_EMULATION
|
||||||
break;
|
break;
|
||||||
case FUNC_COMMAND:
|
case FUNC_COMMAND:
|
||||||
|
@ -398,7 +398,7 @@ void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retain
|
|||||||
free(mqtt_save);
|
free(mqtt_save);
|
||||||
|
|
||||||
bool result = MqttClient.publish(romram, mqtt_data, false);
|
bool result = MqttClient.publish(romram, mqtt_data, false);
|
||||||
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram);
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram);
|
||||||
yield(); // #3313
|
yield(); // #3313
|
||||||
}
|
}
|
||||||
#endif // USE_MQTT_AWS_IOT
|
#endif // USE_MQTT_AWS_IOT
|
||||||
@ -871,10 +871,17 @@ void CmndPublish(void)
|
|||||||
|
|
||||||
void CmndGroupTopic(void)
|
void CmndGroupTopic(void)
|
||||||
{
|
{
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
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) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
MakeValidMqtt(0, XdrvMailbox.data);
|
MakeValidMqtt(0, XdrvMailbox.data);
|
||||||
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
|
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);
|
SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
restart_flag = 2;
|
restart_flag = 2;
|
||||||
}
|
}
|
||||||
ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC));
|
ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC));
|
||||||
|
@ -1767,6 +1767,10 @@ void LightAnimate(void)
|
|||||||
Light.update = true;
|
Light.update = true;
|
||||||
}
|
}
|
||||||
if (Light.update) {
|
if (Light.update) {
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
if (Light.power) LightSendDeviceGroupStatus();
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
|
|
||||||
uint16_t cur_col_10[LST_MAX]; // 10 bits resolution
|
uint16_t cur_col_10[LST_MAX]; // 10 bits resolution
|
||||||
Light.update = false;
|
Light.update = false;
|
||||||
|
|
||||||
@ -2077,6 +2081,86 @@ void calcGammaBulbs(uint16_t cur_col_10[5]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
void LightSendDeviceGroupStatus()
|
||||||
|
{
|
||||||
|
uint8_t channels[LST_MAX];
|
||||||
|
light_state.getChannels(channels);
|
||||||
|
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme, DGR_ITEM_LIGHT_CHANNELS, channels,
|
||||||
|
DGR_ITEM_LIGHT_BRI, (power ? light_state.getBri() : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightHandleDeviceGroupRequest()
|
||||||
|
{
|
||||||
|
static bool send_state = false;
|
||||||
|
uint32_t value = XdrvMailbox.payload;
|
||||||
|
switch (XdrvMailbox.command_code) {
|
||||||
|
case DGR_ITEM_EOL:
|
||||||
|
LightAnimate();
|
||||||
|
if (send_state && !(XdrvMailbox.index & DGR_FLAG_MORE_TO_COME)) {
|
||||||
|
light_controller.saveSettings();
|
||||||
|
if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
|
||||||
|
MqttPublishTeleState();
|
||||||
|
}
|
||||||
|
send_state = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_LIGHT_BRI:
|
||||||
|
if (light_state.getBri() != value) {
|
||||||
|
light_controller.changeBri(value);
|
||||||
|
send_state = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_LIGHT_SCHEME:
|
||||||
|
if (Settings.light_scheme != value) {
|
||||||
|
Settings.light_scheme = value;
|
||||||
|
send_state = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_LIGHT_CHANNELS:
|
||||||
|
light_controller.changeChannels((uint8_t *)XdrvMailbox.data);
|
||||||
|
send_state = true;
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_LIGHT_FIXED_COLOR:
|
||||||
|
{
|
||||||
|
power_t save_power = Light.power;
|
||||||
|
if (value) {
|
||||||
|
bool save_decimal_text = Settings.flag.decimal_text;
|
||||||
|
char str[16];
|
||||||
|
XdrvMailbox.index = 2;
|
||||||
|
XdrvMailbox.data_len = sprintf_P(str, PSTR("%u"), value);
|
||||||
|
XdrvMailbox.data = str;
|
||||||
|
CmndSupportColor();
|
||||||
|
Settings.flag.decimal_text = save_decimal_text;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Light.fixed_color_index = 0;
|
||||||
|
XdrvMailbox.index = 1;
|
||||||
|
XdrvMailbox.payload = light_state.BriToDimmer(light_state.getBri());
|
||||||
|
CmndWhite();
|
||||||
|
}
|
||||||
|
if (Light.power != save_power) {
|
||||||
|
XdrvMailbox.index = save_power;
|
||||||
|
LightSetPower();
|
||||||
|
}
|
||||||
|
send_state = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DGR_ITEM_LIGHT_SPEED:
|
||||||
|
if (Settings.light_speed != value && value > 0 && value <= 40) {
|
||||||
|
Settings.light_speed = value;
|
||||||
|
send_state = true;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* Commands
|
* Commands
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
@ -2339,6 +2423,9 @@ void CmndScheme(void)
|
|||||||
Light.wheel = parm[1];
|
Light.wheel = parm[1];
|
||||||
}
|
}
|
||||||
Settings.light_scheme = XdrvMailbox.payload;
|
Settings.light_scheme = XdrvMailbox.payload;
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme);
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
if (LS_WAKEUP == Settings.light_scheme) {
|
if (LS_WAKEUP == Settings.light_scheme) {
|
||||||
Light.wakeup_active = 3;
|
Light.wakeup_active = 3;
|
||||||
}
|
}
|
||||||
@ -2516,6 +2603,9 @@ void CmndFade(void)
|
|||||||
Settings.light_fade ^= 1;
|
Settings.light_fade ^= 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 2) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade);
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
if (!Settings.light_fade) { Light.fade_running = false; }
|
if (!Settings.light_fade) { Light.fade_running = false; }
|
||||||
ResponseCmndStateText(Settings.light_fade);
|
ResponseCmndStateText(Settings.light_fade);
|
||||||
}
|
}
|
||||||
@ -2536,6 +2626,9 @@ void CmndSpeed(void)
|
|||||||
}
|
}
|
||||||
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 40)) {
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 40)) {
|
||||||
Settings.light_speed = XdrvMailbox.payload;
|
Settings.light_speed = XdrvMailbox.payload;
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
}
|
}
|
||||||
ResponseCmndNumber(Settings.light_speed);
|
ResponseCmndNumber(Settings.light_speed);
|
||||||
}
|
}
|
||||||
@ -2588,6 +2681,11 @@ bool Xdrv04(uint8_t function)
|
|||||||
case FUNC_EVERY_50_MSECOND:
|
case FUNC_EVERY_50_MSECOND:
|
||||||
LightAnimate();
|
LightAnimate();
|
||||||
break;
|
break;
|
||||||
|
#ifdef USE_DEVICE_GROUPS
|
||||||
|
case FUNC_DEVICE_GROUP_REQUEST:
|
||||||
|
LightHandleDeviceGroupRequest();
|
||||||
|
break;
|
||||||
|
#endif // USE_DEVICE_GROUPS
|
||||||
case FUNC_SET_POWER:
|
case FUNC_SET_POWER:
|
||||||
LightSetPower();
|
LightSetPower();
|
||||||
break;
|
break;
|
||||||
|
@ -296,6 +296,7 @@ bool DomoticzMqttData(void)
|
|||||||
found = true;
|
found = true;
|
||||||
} else
|
} else
|
||||||
#endif // USE_SHUTTER
|
#endif // USE_SHUTTER
|
||||||
|
#ifdef USE_LIGHT
|
||||||
if (iscolordimmer && 10 == nvalue) { // Color_SetColor
|
if (iscolordimmer && 10 == nvalue) { // Color_SetColor
|
||||||
// https://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s#Set_a_light_to_a_certain_color_or_color_temperature
|
// https://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s#Set_a_light_to_a_certain_color_or_color_temperature
|
||||||
JsonObject& color = domoticz["Color"];
|
JsonObject& color = domoticz["Color"];
|
||||||
@ -333,8 +334,9 @@ bool DomoticzMqttData(void)
|
|||||||
snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_DIMMER));
|
snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_DIMMER));
|
||||||
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue);
|
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue);
|
||||||
found = true;
|
found = true;
|
||||||
}
|
} else
|
||||||
else if (1 == nvalue || 0 == nvalue) {
|
#endif // USE_LIGHT
|
||||||
|
if (1 == nvalue || 0 == nvalue) {
|
||||||
if (((power >> i) &1) == (power_t)nvalue) {
|
if (((power >> i) &1) == (power_t)nvalue) {
|
||||||
return true; // Stop loop
|
return true; // Stop loop
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
// contains some definitions for functions used before their declarations
|
// contains some definitions for functions used before their declarations
|
||||||
|
|
||||||
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1);
|
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId);
|
||||||
|
|
||||||
|
|
||||||
// Get an JSON attribute, with case insensitive key search
|
// Get an JSON attribute, with case insensitive key search
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
|
|
||||||
#ifdef USE_ZIGBEE
|
#ifdef USE_ZIGBEE
|
||||||
|
|
||||||
|
#ifndef ZIGBEERECEIVED
|
||||||
|
#define ZIGBEERECEIVED 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
@ -49,6 +53,8 @@ typedef struct Z_Device {
|
|||||||
// json buffer used for attribute reporting
|
// json buffer used for attribute reporting
|
||||||
DynamicJsonBuffer *json_buffer;
|
DynamicJsonBuffer *json_buffer;
|
||||||
JsonObject *json;
|
JsonObject *json;
|
||||||
|
// sequence number for Zigbee frames
|
||||||
|
uint8_t seqNumber;
|
||||||
} Z_Device;
|
} Z_Device;
|
||||||
|
|
||||||
// All devices are stored in a Vector
|
// All devices are stored in a Vector
|
||||||
@ -96,6 +102,9 @@ public:
|
|||||||
// device just seen on the network, update the lastSeen field
|
// device just seen on the network, update the lastSeen field
|
||||||
void updateLastSeen(uint16_t shortaddr);
|
void updateLastSeen(uint16_t shortaddr);
|
||||||
|
|
||||||
|
// get next sequence number for (increment at each all)
|
||||||
|
uint8_t getNextSeqNumber(uint16_t shortaddr);
|
||||||
|
|
||||||
// Dump json
|
// Dump json
|
||||||
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
|
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
|
||||||
|
|
||||||
@ -133,6 +142,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
std::vector<Z_Device> _devices = {};
|
std::vector<Z_Device> _devices = {};
|
||||||
uint32_t _saveTimer = 0;
|
uint32_t _saveTimer = 0;
|
||||||
|
uint8_t _seqNumber = 0; // global seqNumber if device is unknown
|
||||||
|
|
||||||
template < typename T>
|
template < typename T>
|
||||||
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
|
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
|
||||||
@ -226,7 +236,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
|
|||||||
std::vector<uint32_t>(),
|
std::vector<uint32_t>(),
|
||||||
0,0,0,0,
|
0,0,0,0,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr, nullptr };
|
nullptr, nullptr,
|
||||||
|
0, // seqNumber
|
||||||
|
};
|
||||||
device.json_buffer = new DynamicJsonBuffer();
|
device.json_buffer = new DynamicJsonBuffer();
|
||||||
_devices.push_back(device);
|
_devices.push_back(device);
|
||||||
dirty();
|
dirty();
|
||||||
@ -532,6 +544,19 @@ void Z_Devices::updateLastSeen(uint16_t shortaddr) {
|
|||||||
_updateLastSeen(device);
|
_updateLastSeen(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
if (short_found >= 0) {
|
||||||
|
Z_Device &device = getShortAddr(shortaddr);
|
||||||
|
device.seqNumber += 1;
|
||||||
|
return device.seqNumber;
|
||||||
|
} else {
|
||||||
|
_seqNumber += 1;
|
||||||
|
return _seqNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Per device timers
|
// Per device timers
|
||||||
//
|
//
|
||||||
// Reset the timer for a specific device
|
// Reset the timer for a specific device
|
||||||
@ -704,18 +729,22 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
|||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
|
||||||
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
|
#if ZIGBEERECEIVED
|
||||||
// DEPRECATED TODO
|
// DEPRECATED TODO
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
|
||||||
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
||||||
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
|
#if ZIGBEERECEIVED
|
||||||
// DEPRECATED TODO
|
// DEPRECATED TODO
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
||||||
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
// MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
||||||
// XdrvRulesProcess();
|
// XdrvRulesProcess();
|
||||||
|
@ -486,18 +486,8 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
|
|||||||
|
|
||||||
|
|
||||||
// Parse non-normalized attributes
|
// Parse non-normalized attributes
|
||||||
// The key is "s_" followed by 16 bits clusterId, "_" followed by 8 bits command id
|
|
||||||
void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
|
void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
|
||||||
uint32_t i = offset;
|
convertClusterSpecific(json, _cluster_id, _cmd_id, _frame_control.b.direction, _payload);
|
||||||
uint32_t len = _payload.len();
|
|
||||||
|
|
||||||
char attrid_str[12];
|
|
||||||
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X!%02X"), _cluster_id, _cmd_id);
|
|
||||||
|
|
||||||
char hex_char[_payload.len()*2+2];
|
|
||||||
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
|
||||||
|
|
||||||
json[attrid_str] = hex_char;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return value:
|
// return value:
|
||||||
|
@ -19,34 +19,81 @@
|
|||||||
|
|
||||||
#ifdef USE_ZIGBEE
|
#ifdef USE_ZIGBEE
|
||||||
|
|
||||||
//typedef int32_t (*Z_AttrConverter)(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param);
|
|
||||||
typedef struct Z_CommandConverter {
|
typedef struct Z_CommandConverter {
|
||||||
const char * tasmota_cmd;
|
const char * tasmota_cmd;
|
||||||
const char * zcl_cmd;
|
uint16_t cluster;
|
||||||
|
uint8_t cmd; // normally 8 bits, 0xFF means it's a parameter
|
||||||
|
uint8_t direction; // direction of the command. 0x01 client->server, 0x02 server->client, 0x03 both
|
||||||
|
const char * param;
|
||||||
} Z_CommandConverter;
|
} Z_CommandConverter;
|
||||||
|
|
||||||
|
typedef struct Z_XYZ_Var { // Holds values for vairables X, Y and Z
|
||||||
|
uint32_t x = 0;
|
||||||
|
uint32_t y = 0;
|
||||||
|
uint32_t z = 0;
|
||||||
|
uint8_t x_type = 0; // 0 = no value, 1 = 1 bytes, 2 = 2 bytes
|
||||||
|
uint8_t y_type = 0;
|
||||||
|
uint8_t z_type = 0;
|
||||||
|
} Z_XYZ_Var;
|
||||||
|
|
||||||
// list of post-processing directives
|
// list of post-processing directives
|
||||||
const Z_CommandConverter Z_Commands[] = {
|
const Z_CommandConverter Z_Commands[] = {
|
||||||
{ "Power", "0006!xx" }, // 0=Off, 1=On, 2=Toggle
|
// Group adress commands
|
||||||
{ "Dimmer", "0008!04/xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid)
|
{ "AddGroup", 0x0004, 0x00, 0x01, "xxxx00" }, // Add group id, group name is not supported
|
||||||
{ "Dimmer+", "0008!06/001902" }, // Step up by 10%, 0.2 secs
|
{ "ViewGroup", 0x0004, 0x01, 0x01, "xxxx" }, // Ask for the group name
|
||||||
{ "Dimmer-", "0008!06/011902" }, // Step down by 10%, 0.2 secs
|
{ "GetGroup", 0x0004, 0x02, 0x01, "01xxxx" }, // Get one group membership
|
||||||
{ "DimmerStop", "0008!03" }, // Stop any Dimmer animation
|
{ "GetAllGroups", 0x0004, 0x02, 0x01, "00" }, // Get all groups membership
|
||||||
{ "ResetAlarm", "0009!00/xxyyyy" }, // Reset alarm (alarm code + cluster identifier)
|
{ "RemoveGroup", 0x0004, 0x03, 0x01, "xxxx" }, // Remove one group
|
||||||
{ "ResetAllAlarms","0009!01" }, // Reset all alarms
|
{ "RemoveAllGroups",0x0004, 0x04, 0x01, "" }, // Remove all groups
|
||||||
{ "Hue", "0300!00/xx000A00" }, // Move to Hue, shortest time, 1s
|
// Light & Shutter commands
|
||||||
{ "Sat", "0300!03/xx0A00" }, // Move to Sat
|
{ "Power", 0x0006, 0xFF, 0x01, "" }, // 0=Off, 1=On, 2=Toggle
|
||||||
{ "HueSat", "0300!06/xxyy0A00" }, // Hue, Sat
|
{ "Dimmer", 0x0008, 0x04, 0x01, "xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid)
|
||||||
{ "Color", "0300!07/xxxxyyyy0A00" }, // x, y (uint16)
|
{ "Dimmer+", 0x0008, 0x06, 0x01, "001902" }, // Step up by 10%, 0.2 secs
|
||||||
{ "CT", "0300!0A/xxxx0A00" }, // Color Temperature Mireds (uint16)
|
{ "Dimmer-", 0x0008, 0x06, 0x01, "011902" }, // Step down by 10%, 0.2 secs
|
||||||
{ "Shutter", "0102!xx" },
|
{ "DimmerStop", 0x0008, 0x03, 0x01, "" }, // Stop any Dimmer animation
|
||||||
{ "ShutterOpen", "0102!00" },
|
{ "ResetAlarm", 0x0009, 0x00, 0x01, "xxyyyy" }, // Reset alarm (alarm code + cluster identifier)
|
||||||
{ "ShutterClose", "0102!01" },
|
{ "ResetAllAlarms", 0x0009, 0x01, 0x01, "" }, // Reset all alarms
|
||||||
{ "ShutterStop", "0102!02" },
|
{ "Hue", 0x0300, 0x00, 0x01, "xx000A00" }, // Move to Hue, shortest time, 1s
|
||||||
{ "ShutterLift", "0102!05xx" }, // Lift percentage, 0%=open, 100%=closed
|
{ "Sat", 0x0300, 0x03, 0x01, "xx0A00" }, // Move to Sat
|
||||||
{ "ShutterTilt", "0102!08xx" }, // Tilt percentage
|
{ "HueSat", 0x0300, 0x06, 0x01, "xxyy0A00" }, // Hue, Sat
|
||||||
|
{ "Color", 0x0300, 0x07, 0x01, "xxxxyyyy0A00" }, // x, y (uint16)
|
||||||
|
{ "CT", 0x0300, 0x0A, 0x01, "xxxx0A00" }, // Color Temperature Mireds (uint16)
|
||||||
|
{ "ShutterOpen", 0x0102, 0x00, 0x01, "" },
|
||||||
|
{ "ShutterClose", 0x0102, 0x01, 0x01, "" },
|
||||||
|
{ "ShutterStop", 0x0102, 0x02, 0x01, "" },
|
||||||
|
{ "ShutterLift", 0x0102, 0x05, 0x01, "xx" }, // Lift percentage, 0%=open, 100%=closed
|
||||||
|
{ "ShutterTilt", 0x0102, 0x08, 0x01, "xx" }, // Tilt percentage
|
||||||
|
{ "Shutter", 0x0102, 0xFF, 0x01, "" },
|
||||||
|
// Blitzwolf PIR
|
||||||
|
{ "Occupancy", 0xEF00, 0x01, 0x01, "xx"}, // Specific decoder for Blitzwolf PIR, empty name means special treatment
|
||||||
|
// Decoders only - normally not used to send, and names may be masked by previous definitions
|
||||||
|
{ "Dimmer", 0x0008, 0x00, 0x01, "xx" },
|
||||||
|
{ "DimmerMove", 0x0008, 0x01, 0x01, "xx0A" },
|
||||||
|
{ "DimmerStep", 0x0008, 0x02, 0x01, "xx190A00" },
|
||||||
|
{ "DimmerMove", 0x0008, 0x05, 0x01, "xx0A" },
|
||||||
|
{ "Dimmer+", 0x0008, 0x06, 0x01, "00" },
|
||||||
|
{ "Dimmer-", 0x0008, 0x06, 0x01, "01" },
|
||||||
|
{ "DimmerStop", 0x0008, 0x07, 0x01, "" },
|
||||||
|
{ "HueMove", 0x0300, 0x01, 0x01, "xx19" },
|
||||||
|
{ "HueStep", 0x0300, 0x02, 0x01, "xx190A00" },
|
||||||
|
{ "SatMove", 0x0300, 0x04, 0x01, "xx19" },
|
||||||
|
{ "SatStep", 0x0300, 0x05, 0x01, "xx190A" },
|
||||||
|
{ "ColorMove", 0x0300, 0x08, 0x01, "xxxxyyyy" },
|
||||||
|
{ "ColorStep", 0x0300, 0x09, 0x01, "xxxxyyyy0A00" },
|
||||||
|
// Tradfri
|
||||||
|
{ "ArrowClick", 0x0005, 0x07, 0x01, "xx" }, // xx == 0x01 = left, 0x00 = right
|
||||||
|
{ "ArrowHold", 0x0005, 0x08, 0x01, "xx" }, // xx == 0x01 = left, 0x00 = right
|
||||||
|
{ "ArrowRelease", 0x0005, 0x09, 0x01, "" },
|
||||||
|
// IAS - Intruder Alarm System + leak/fire detection
|
||||||
|
{ "ZoneStatusChange",0x0500, 0x00, 0x02, "xxxxyyzz" }, // xxxx = zone status, yy = extended status, zz = zone id, Delay is ignored
|
||||||
|
// responses for Group cluster commands
|
||||||
|
{ "AddGroupResp", 0x0004, 0x00, 0x02, "xxyyyy" }, // xx = status, yy = group id
|
||||||
|
{ "ViewGroupResp", 0x0004, 0x01, 0x02, "xxyyyy" }, // xx = status, yy = group id, name ignored
|
||||||
|
{ "GetGroupResp", 0x0004, 0x02, 0x02, "xxyyzzzz" }, // xx = capacity, yy = count, zzzz = first group id, following groups ignored
|
||||||
|
{ "RemoveGroup", 0x0004, 0x03, 0x02, "xxyyyy" }, // xx = status, yy = group id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian
|
#define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian
|
||||||
|
|
||||||
// Below are the attributes we wand to read from each cluster
|
// Below are the attributes we wand to read from each cluster
|
||||||
@ -55,6 +102,7 @@ const uint8_t CLUSTER_0008[] = { ZLE(0x0000) }; // CurrentLevel
|
|||||||
const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount
|
const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount
|
||||||
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007) }; // Hue, Sat, X, Y, CT
|
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007) }; // Hue, Sat, X, Y, CT
|
||||||
|
|
||||||
|
// This callback is registered after a cluster specific command and sends a read command for the same cluster
|
||||||
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
|
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
|
||||||
size_t attrs_len = 0;
|
size_t attrs_len = 0;
|
||||||
const uint8_t* attrs = nullptr;
|
const uint8_t* attrs = nullptr;
|
||||||
@ -78,11 +126,10 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoi
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (attrs) {
|
if (attrs) {
|
||||||
ZigbeeZCLSend(shortaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */);
|
ZigbeeZCLSend(shortaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// set a timer to read back the value in the future
|
// set a timer to read back the value in the future
|
||||||
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) {
|
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) {
|
||||||
uint32_t wait_ms = 0;
|
uint32_t wait_ms = 0;
|
||||||
@ -105,22 +152,182 @@ void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const __FlashStringHelper* zigbeeFindCommand(const char *command) {
|
// returns true if char is 'x', 'y' or 'z'
|
||||||
char parm_uc[16]; // used to convert JSON keys to uppercase
|
inline bool isXYZ(char c) {
|
||||||
|
return (c >= 'x') && (c <= 'z');
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the Hex value of a digit [0-9A-Fa-f]
|
||||||
|
// return: 0x00-0x0F
|
||||||
|
// or -1 if cannot be parsed
|
||||||
|
inline int8_t hexValue(char c) {
|
||||||
|
if ((c >= '0') && (c <= '9')) {
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
if ((c >= 'A') && (c <= 'F')) {
|
||||||
|
return 10 + c - 'A';
|
||||||
|
}
|
||||||
|
if ((c >= 'a') && (c <= 'f')) {
|
||||||
|
return 10 + c - 'a';
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a Big Endian suite of max_len digits, or stops when a non-hex digit is found
|
||||||
|
uint32_t parseHex_P(const char **data, size_t max_len = 8) {
|
||||||
|
uint32_t ret = 0;
|
||||||
|
for (uint32_t i = 0; i < max_len; i++) {
|
||||||
|
int8_t v = hexValue(pgm_read_byte(*data));
|
||||||
|
if (v < 0) { break; } // non hex digit, we stop parsing
|
||||||
|
ret = (ret << 4) | v;
|
||||||
|
*data += 1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a model like "xxyy00"
|
||||||
|
// and fill x, y and z values
|
||||||
|
// Little Endian encoding
|
||||||
|
// On exit, xyz is updated, and x_type, y_type, z_type contain the number of bytes read for each
|
||||||
|
void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz) {
|
||||||
|
const char *p = model; // pointer to the model character
|
||||||
|
uint32_t v = 0; // index in the payload bytes buffer
|
||||||
|
char c = pgm_read_byte(p); // cur char
|
||||||
|
while (c) {
|
||||||
|
char c1 = pgm_read_byte(p+1); // next char
|
||||||
|
if (!c1) { break; } // unexpected end of model
|
||||||
|
if (isXYZ(c) && (c == c1) && (v < payload.len())) { // if char is [x-z] and followed by same char
|
||||||
|
uint8_t val = payload.get8(v);
|
||||||
|
switch (c) {
|
||||||
|
case 'x':
|
||||||
|
xyz->x = xyz->x | (val << (xyz->x_type * 8));
|
||||||
|
xyz->x_type++;
|
||||||
|
break;
|
||||||
|
case 'y':
|
||||||
|
xyz->y = xyz->y | (val << (xyz->y_type * 8));
|
||||||
|
xyz->y_type++;
|
||||||
|
break;
|
||||||
|
case 'z':
|
||||||
|
xyz->z = xyz->z | (val << (xyz->z_type * 8));
|
||||||
|
xyz->z_type++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p += 2;
|
||||||
|
v++;
|
||||||
|
c = pgm_read_byte(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// works on big endiand hex only
|
||||||
|
// Returns if found:
|
||||||
|
// - cluster number
|
||||||
|
// - command number or 0xFF if command is part of the variable part
|
||||||
|
// - the payload in the form of a HEX string with x/y/z variables
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Parse a cluster specific command, and try to convert into human readable
|
||||||
|
void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, const SBuffer &payload) {
|
||||||
|
size_t hex_char_len = payload.len()*2+2;
|
||||||
|
char *hex_char = (char*) malloc(hex_char_len);
|
||||||
|
if (!hex_char) { return; }
|
||||||
|
ToHex_P((unsigned char*)payload.getBuffer(), payload.len(), hex_char, hex_char_len);
|
||||||
|
|
||||||
|
const __FlashStringHelper* command_name = nullptr;
|
||||||
|
Z_XYZ_Var xyz;
|
||||||
|
|
||||||
|
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>> len = %d - %02X%02X%02X"), payload.len(), payload.get8(0), payload.get8(1), payload.get8(2));
|
||||||
|
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
|
||||||
|
const Z_CommandConverter *conv = &Z_Commands[i];
|
||||||
|
if (conv->cluster == cluster) {
|
||||||
|
// cluster match
|
||||||
|
if ((0xFF == conv->cmd) || (cmd == conv->cmd)) {
|
||||||
|
// cmd match
|
||||||
|
if ((direction && (conv->direction & 0x02)) || (!direction && (conv->direction & 0x01))) {
|
||||||
|
// check if we have a match for params too
|
||||||
|
// Match if:
|
||||||
|
// - payload exactly matches conv->param (conv->param may be longer)
|
||||||
|
// - payload matches conv->param until 'x', 'y' or 'z'
|
||||||
|
const char * p = conv->param;
|
||||||
|
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++1 param = %s"), p);
|
||||||
|
bool match = true;
|
||||||
|
for (uint8_t i = 0; i < payload.len(); i++) {
|
||||||
|
const char c1 = pgm_read_byte(p);
|
||||||
|
const char c2 = pgm_read_byte(p+1);
|
||||||
|
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++2 c1 = %c, c2 = %c"), c1, c2);
|
||||||
|
if ((0x00 == c1) || isXYZ(c1)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const char * p2 = p;
|
||||||
|
uint32_t nextbyte = parseHex_P(&p2, 2);
|
||||||
|
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++3 parseHex_P = %02X"), nextbyte);
|
||||||
|
if (nextbyte != payload.get8(i)) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
command_name = (const __FlashStringHelper*) conv->tasmota_cmd;
|
||||||
|
parseXYZ(conv->param, payload, &xyz);
|
||||||
|
if (0xFF == conv->cmd) {
|
||||||
|
// shift all values
|
||||||
|
xyz.z = xyz.y;
|
||||||
|
xyz.z_type = xyz.y_type;
|
||||||
|
xyz.y = xyz.x;
|
||||||
|
xyz.y_type = xyz.x_type;
|
||||||
|
xyz.x = cmd;
|
||||||
|
xyz.x_type = 1; // 1 byte
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// always report attribute in raw format
|
||||||
|
// Format: "0001!06": "00" = "<cluster>!<cmd>": "<payload>" for commands to devices
|
||||||
|
// Format: "0004<00": "00" = "<cluster><<cmd>": "<payload>" for commands to devices
|
||||||
|
char attrid_str[12];
|
||||||
|
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd);
|
||||||
|
json[attrid_str] = hex_char;
|
||||||
|
free(hex_char);
|
||||||
|
|
||||||
|
if (command_name) {
|
||||||
|
if (0 == xyz.x_type) {
|
||||||
|
json[command_name] = true; // no parameter
|
||||||
|
} else if (0 == xyz.y_type) {
|
||||||
|
json[command_name] = xyz.x; // 1 parameter
|
||||||
|
} else {
|
||||||
|
// multiple answers, create an array
|
||||||
|
JsonArray &arr = json.createNestedArray(command_name);
|
||||||
|
arr.add(xyz.x);
|
||||||
|
arr.add(xyz.y);
|
||||||
|
if (xyz.z_type) {
|
||||||
|
arr.add(xyz.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the command details by command name
|
||||||
|
// If not found:
|
||||||
|
// - returns nullptr
|
||||||
|
const __FlashStringHelper* zigbeeFindCommand(const char *command, uint16_t *cluster, uint16_t *cmd) {
|
||||||
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
|
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
|
||||||
const Z_CommandConverter *conv = &Z_Commands[i];
|
const Z_CommandConverter *conv = &Z_Commands[i];
|
||||||
if (0 == strcasecmp_P(command, conv->tasmota_cmd)) {
|
if (0 == strcasecmp_P(command, conv->tasmota_cmd)) {
|
||||||
return (const __FlashStringHelper*) conv->zcl_cmd;
|
*cluster = conv->cluster;
|
||||||
|
*cmd = conv->cmd;
|
||||||
|
return (const __FlashStringHelper*) conv->param;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool isXYZ(char c) {
|
|
||||||
return (c >= 'x') && (c <= 'z');
|
|
||||||
}
|
|
||||||
|
|
||||||
// take the lower 4 bits and turn it to an hex char
|
// take the lower 4 bits and turn it to an hex char
|
||||||
inline char hexDigit(uint32_t h) {
|
inline char hexDigit(uint32_t h) {
|
||||||
uint32_t nybble = h & 0x0F;
|
uint32_t nybble = h & 0x0F;
|
||||||
|
@ -33,6 +33,7 @@ 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_ACTIVE_EP = 32; // Endpoints descriptor
|
||||||
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters)
|
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_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_VERSION = 50; // Status: CC2530 ZNP Version
|
||||||
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
|
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
|
||||||
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||||
|
@ -176,15 +176,24 @@ int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
|
|||||||
return -1;
|
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
|
// Send ACTIVE_EP_REQ to collect active endpoints for this address
|
||||||
void Z_SendActiveEpReq(uint16_t shortaddr) {
|
void Z_SendActiveEpReq(uint16_t shortaddr) {
|
||||||
uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ,
|
uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ,
|
||||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
||||||
|
|
||||||
uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
|
|
||||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
|
||||||
|
|
||||||
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
|
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
|
||||||
|
|
||||||
|
// uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
|
||||||
|
// Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
||||||
|
|
||||||
//ZigbeeZNPSend(NodeDescReq, sizeof(NodeDescReq)); Not sure this is useful
|
//ZigbeeZNPSend(NodeDescReq, sizeof(NodeDescReq)); Not sure this is useful
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,6 +344,40 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 numAssocDev = buf.get8(14);
|
||||||
|
|
||||||
|
if (0 == status) { // SUCCESS
|
||||||
|
zigbee_devices.updateDevice(nwkAddr, ieeeAddr);
|
||||||
|
char hex[20];
|
||||||
|
Uint64toHex(ieeeAddr, hex, 64);
|
||||||
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
||||||
|
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
||||||
|
"}}"),
|
||||||
|
ZIGBEE_STATUS_DEVICE_IEEE, hex, nwkAddr
|
||||||
|
);
|
||||||
|
|
||||||
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
||||||
|
XdrvRulesProcess();
|
||||||
|
// Ping response
|
||||||
|
const String * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
|
||||||
|
if (friendlyName) {
|
||||||
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
|
||||||
|
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), nwkAddr, friendlyName->c_str());
|
||||||
|
} else {
|
||||||
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"}}"), nwkAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
||||||
|
XdrvRulesProcess();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
|
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
|
||||||
Z_ShortAddress srcAddr = buf.get16(2);
|
Z_ShortAddress srcAddr = buf.get16(2);
|
||||||
Z_ShortAddress nwkAddr = buf.get16(4);
|
Z_ShortAddress nwkAddr = buf.get16(4);
|
||||||
@ -451,6 +494,8 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
|||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
|
||||||
|
|
||||||
zcl_received.postProcessAttributes(srcaddr, json);
|
zcl_received.postProcessAttributes(srcaddr, json);
|
||||||
|
// Add Endpoint
|
||||||
|
json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint;
|
||||||
// Add linkquality
|
// Add linkquality
|
||||||
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
|
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
|
||||||
|
|
||||||
@ -482,6 +527,7 @@ 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_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_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_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
|
||||||
|
|
||||||
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
||||||
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
|
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
|
||||||
@ -491,6 +537,7 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
|||||||
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
|
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
|
||||||
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
|
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
|
||||||
{ AREQ_ZDO_SIMPLEDESCRSP, &Z_ReceiveSimpleDesc },
|
{ AREQ_ZDO_SIMPLEDESCRSP, &Z_ReceiveSimpleDesc },
|
||||||
|
{ AREQ_ZDO_IEEE_ADDR_RSP, &Z_ReceiveIEEEAddr },
|
||||||
};
|
};
|
||||||
|
|
||||||
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
|
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
|
||||||
|
@ -33,19 +33,22 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
|
|||||||
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
|
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
|
||||||
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|"
|
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_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_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|"
|
||||||
|
D_CMND_ZIGBEE_PING ;
|
||||||
|
|
||||||
const char kZigbeeCommands[] PROGMEM = D_PRFX_ZIGBEE "|" // legacy prefix -- deprecated
|
const char kZigbeeCommands[] PROGMEM = D_PRFX_ZIGBEE "|" // legacy prefix -- deprecated
|
||||||
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
|
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
|
||||||
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|"
|
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_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_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|"
|
||||||
|
D_CMND_ZIGBEE_PING ;
|
||||||
|
|
||||||
void (* const ZigbeeCommand[])(void) PROGMEM = {
|
void (* const ZigbeeCommand[])(void) PROGMEM = {
|
||||||
&CmndZbZNPSend, &CmndZbPermitJoin,
|
&CmndZbZNPSend, &CmndZbPermitJoin,
|
||||||
&CmndZbStatus, &CmndZbReset, &CmndZbSend,
|
&CmndZbStatus, &CmndZbReset, &CmndZbSend,
|
||||||
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
|
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
|
||||||
&CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind
|
&CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind,
|
||||||
|
&CmndZbPing,
|
||||||
};
|
};
|
||||||
|
|
||||||
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
||||||
@ -344,7 +347,7 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
|
|||||||
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
|
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp, uint8_t transacId) {
|
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) {
|
||||||
SBuffer buf(25+len);
|
SBuffer buf(25+len);
|
||||||
buf.add8(Z_SREQ | Z_AF); // 24
|
buf.add8(Z_SREQ | Z_AF); // 24
|
||||||
buf.add8(AF_DATA_REQUEST); // 01
|
buf.add8(AF_DATA_REQUEST); // 01
|
||||||
@ -357,7 +360,7 @@ void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8
|
|||||||
buf.add8(0x1E); // 1E radius
|
buf.add8(0x1E); // 1E radius
|
||||||
|
|
||||||
buf.add8(3 + len);
|
buf.add8(3 + len);
|
||||||
buf.add8((disableDefResp ? 0x10 : 0x00) | (clusterSpecific ? 0x01 : 0x00)); // Frame Control Field
|
buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00)); // Frame Control Field
|
||||||
buf.add8(transacId); // Transaction Sequance Number
|
buf.add8(transacId); // Transaction Sequance Number
|
||||||
buf.add8(cmdId);
|
buf.add8(cmdId);
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
@ -367,61 +370,16 @@ void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8
|
|||||||
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int8_t hexValue(char c) {
|
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, bool clusterSpecific,
|
||||||
if ((c >= '0') && (c <= '9')) {
|
uint16_t cluster, uint8_t cmd, const char *param) {
|
||||||
return c - '0';
|
size_t size = param ? strlen(param) : 0;
|
||||||
}
|
|
||||||
if ((c >= 'A') && (c <= 'F')) {
|
|
||||||
return 10 + c - 'A';
|
|
||||||
}
|
|
||||||
if ((c >= 'a') && (c <= 'f')) {
|
|
||||||
return 10 + c - 'a';
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t parseHex(const char **data, size_t max_len = 8) {
|
|
||||||
uint32_t ret = 0;
|
|
||||||
for (uint32_t i = 0; i < max_len; i++) {
|
|
||||||
int8_t v = hexValue(**data);
|
|
||||||
if (v < 0) { break; } // non hex digit, we stop parsing
|
|
||||||
ret = (ret << 4) | v;
|
|
||||||
*data += 1;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
|
|
||||||
|
|
||||||
uint16_t cluster = 0x0000; // 0x0000 is a valid default value
|
|
||||||
uint8_t cmd = ZCL_READ_ATTRIBUTES; // default command is READ_ATTRIBUTES
|
|
||||||
bool clusterSpecific = false;
|
|
||||||
// Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC"
|
|
||||||
// where AA is the cluster number, BBBB the command number, CCCC... the payload
|
|
||||||
// First delimiter is '_' for a global command, or '!' for a cluster specific commanc
|
|
||||||
cluster = parseHex(&data, 4);
|
|
||||||
|
|
||||||
// delimiter
|
|
||||||
if (('_' == *data) || ('!' == *data)) {
|
|
||||||
if ('!' == *data) { clusterSpecific = true; }
|
|
||||||
data++;
|
|
||||||
} else {
|
|
||||||
ResponseCmndChar("Wrong delimiter for payload");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// parse cmd number
|
|
||||||
cmd = parseHex(&data, 2);
|
|
||||||
|
|
||||||
// move to end of payload
|
|
||||||
// delimiter is optional
|
|
||||||
if ('/' == *data) { data++; } // skip delimiter
|
|
||||||
|
|
||||||
size_t size = strlen(data);
|
|
||||||
SBuffer buf((size+2)/2); // actual bytes buffer for data
|
SBuffer buf((size+2)/2); // actual bytes buffer for data
|
||||||
|
|
||||||
while (*data) {
|
if (param) {
|
||||||
uint8_t code = parseHex(&data, 2);
|
while (*param) {
|
||||||
buf.add8(code);
|
uint8_t code = parseHex_P(¶m, 2);
|
||||||
|
buf.add8(code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 == endpoint) {
|
if (0 == endpoint) {
|
||||||
@ -430,7 +388,7 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
|
|||||||
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: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
|
||||||
dstAddr, cluster, endpoint, cmd, data);
|
dstAddr, cluster, endpoint, cmd, param);
|
||||||
|
|
||||||
if (0 == endpoint) {
|
if (0 == endpoint) {
|
||||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
|
||||||
@ -438,7 +396,7 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// everything is good, we can send the command
|
// everything is good, we can send the command
|
||||||
ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len());
|
ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len(), false, zigbee_devices.getNextSeqNumber(dstAddr));
|
||||||
// now set the timer, if any, to read back the state later
|
// now set the timer, if any, to read back the state later
|
||||||
if (clusterSpecific) {
|
if (clusterSpecific) {
|
||||||
zigbeeSetCommandTimer(dstAddr, cluster, endpoint);
|
zigbeeSetCommandTimer(dstAddr, cluster, endpoint);
|
||||||
@ -467,8 +425,12 @@ void CmndZbSend(void) {
|
|||||||
static char delim[] = ", "; // delimiters for parameters
|
static char delim[] = ", "; // delimiters for parameters
|
||||||
uint16_t device = 0xFFFF; // 0xFFFF is broadcast, so considered valid
|
uint16_t device = 0xFFFF; // 0xFFFF is broadcast, so considered valid
|
||||||
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
|
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
|
||||||
|
// Command elements
|
||||||
|
uint16_t cluster = 0;
|
||||||
|
uint8_t cmd = 0;
|
||||||
String cmd_str = ""; // the actual low-level command, either specified or computed
|
String cmd_str = ""; // the actual low-level command, either specified or computed
|
||||||
|
|
||||||
|
// parse JSON
|
||||||
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
|
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
|
||||||
if (nullptr != &val_device) {
|
if (nullptr != &val_device) {
|
||||||
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
|
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
|
||||||
@ -496,8 +458,9 @@ void CmndZbSend(void) {
|
|||||||
String key = it->key;
|
String key = it->key;
|
||||||
JsonVariant& value = it->value;
|
JsonVariant& value = it->value;
|
||||||
uint32_t x = 0, y = 0, z = 0;
|
uint32_t x = 0, y = 0, z = 0;
|
||||||
|
uint16_t cmd_var;
|
||||||
|
|
||||||
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str());
|
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &cluster, &cmd_var);
|
||||||
if (tasmota_cmd) {
|
if (tasmota_cmd) {
|
||||||
cmd_str = tasmota_cmd;
|
cmd_str = tasmota_cmd;
|
||||||
} else {
|
} else {
|
||||||
@ -533,9 +496,16 @@ void CmndZbSend(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str());
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str());
|
||||||
|
if (0xFF == cmd_var) { // if command number is a variable, replace it with x
|
||||||
|
cmd = x;
|
||||||
|
x = y; // and shift other variables
|
||||||
|
y = z;
|
||||||
|
} else {
|
||||||
|
cmd = cmd_var; // or simply copy the cmd number
|
||||||
|
}
|
||||||
cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters
|
cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str());
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str());
|
||||||
} else {
|
} else {
|
||||||
// we have zero command, pass through until last error for missing command
|
// we have zero command, pass through until last error for missing command
|
||||||
}
|
}
|
||||||
@ -546,9 +516,9 @@ void CmndZbSend(void) {
|
|||||||
// we have an unsupported command type, just ignore it and fallback to missing command
|
// we have an unsupported command type, just ignore it and fallback to missing command
|
||||||
}
|
}
|
||||||
|
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"),
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%04X!%02X/%s\"}"),
|
||||||
device, endpoint, cmd_str.c_str());
|
device, endpoint, cluster, cmd, cmd_str.c_str());
|
||||||
zigbeeZCLSendStr(device, endpoint, cmd_str.c_str());
|
zigbeeZCLSendStr(device, endpoint, true, cluster, cmd, cmd_str.c_str());
|
||||||
} else {
|
} else {
|
||||||
Response_P(PSTR("Missing zigbee 'Send'"));
|
Response_P(PSTR("Missing zigbee 'Send'"));
|
||||||
return;
|
return;
|
||||||
@ -614,16 +584,28 @@ void CmndZbBind(void) {
|
|||||||
|
|
||||||
// Probe a specific device to get its endpoints and supported clusters
|
// Probe a specific device to get its endpoints and supported clusters
|
||||||
void CmndZbProbe(void) {
|
void CmndZbProbe(void) {
|
||||||
|
CmndZbProbeOrPing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CmndZbProbeOrPing(boolean probe) {
|
||||||
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
||||||
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
|
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
|
||||||
if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; }
|
if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; }
|
||||||
if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; }
|
if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; }
|
||||||
|
|
||||||
// everything is good, we can send the command
|
// everything is good, we can send the command
|
||||||
Z_SendActiveEpReq(shortaddr);
|
Z_SendIEEEAddrReq(shortaddr);
|
||||||
|
if (probe) {
|
||||||
|
Z_SendActiveEpReq(shortaddr);
|
||||||
|
}
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ping a device, actually a simplified version of ZbProbe
|
||||||
|
void CmndZbPing(void) {
|
||||||
|
CmndZbProbeOrPing(false);
|
||||||
|
}
|
||||||
|
|
||||||
// Specify, read or erase a Friendly Name
|
// Specify, read or erase a Friendly Name
|
||||||
void CmndZbName(void) {
|
void CmndZbName(void) {
|
||||||
// Syntax is:
|
// Syntax is:
|
||||||
@ -728,8 +710,13 @@ void CmndZbRead(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (0 == endpoint) { // try to compute the endpoint
|
||||||
|
endpoint = zigbee_devices.findClusterEndpointIn(device, cluster);
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
if ((0 != endpoint) && (attrs_len > 0)) {
|
if ((0 != endpoint) && (attrs_len > 0)) {
|
||||||
ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */);
|
ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device));
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
} else {
|
} else {
|
||||||
ResponseCmndChar("Missing parameters");
|
ResponseCmndChar("Missing parameters");
|
||||||
|
@ -48,7 +48,7 @@ void (* const ShutterCommand[])(void) PROGMEM = {
|
|||||||
&CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay,
|
&CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay,
|
||||||
&CmndShutterFrequency, &CmndShutterButton, &CmndShutterLock, &CmndShutterEnableEndStopTime};
|
&CmndShutterFrequency, &CmndShutterButton, &CmndShutterLock, &CmndShutterEnableEndStopTime};
|
||||||
|
|
||||||
const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"Direction\":%d}";
|
const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"Direction\":%d,\"Target\":%d}";
|
||||||
const char JSON_SHUTTER_BUTTON[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Button%d\":%d}";
|
const char JSON_SHUTTER_BUTTON[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Button%d\":%d}";
|
||||||
|
|
||||||
#include <Ticker.h>
|
#include <Ticker.h>
|
||||||
@ -70,7 +70,7 @@ struct SHUTTER {
|
|||||||
int8_t direction[MAX_SHUTTERS]; // 1 == UP , 0 == stop; -1 == down
|
int8_t direction[MAX_SHUTTERS]; // 1 == UP , 0 == stop; -1 == down
|
||||||
uint8_t mode = 0; // operation mode definition. see enum type above SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE
|
uint8_t mode = 0; // operation mode definition. see enum type above SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE
|
||||||
int16_t motordelay[MAX_SHUTTERS]; // initial motorstarttime in 0.05sec.
|
int16_t motordelay[MAX_SHUTTERS]; // initial motorstarttime in 0.05sec.
|
||||||
int16_t pwm_frequency; // frequency of PWN for stepper motors
|
int16_t pwm_frequency[MAX_SHUTTERS]; // frequency of PWN for stepper motors
|
||||||
uint16_t max_pwm_frequency = 1000; // maximum of PWM frequency for openig the shutter. depend on the motor and drivers
|
uint16_t max_pwm_frequency = 1000; // maximum of PWM frequency for openig the shutter. depend on the motor and drivers
|
||||||
uint16_t max_close_pwm_frequency[MAX_SHUTTERS];// maximum of PWM frequency for closeing the shutter. depend on the motor and drivers
|
uint16_t max_close_pwm_frequency[MAX_SHUTTERS];// maximum of PWM frequency for closeing the shutter. depend on the motor and drivers
|
||||||
uint8_t skip_relay_change; // avoid overrun at endstops
|
uint8_t skip_relay_change; // avoid overrun at endstops
|
||||||
@ -82,17 +82,18 @@ void ShutterLogPos(uint32_t i)
|
|||||||
char stemp2[10];
|
char stemp2[10];
|
||||||
dtostrfd((float)Shutter.time[i] / steps_per_second, 2, stemp2);
|
dtostrfd((float)Shutter.time[i] / steps_per_second, 2, stemp2);
|
||||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter%d Real %d, Start %d, Stop %d, Dir %d, Delay %d, Rtc %s [s], Freq %d"),
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter%d Real %d, Start %d, Stop %d, Dir %d, Delay %d, Rtc %s [s], Freq %d"),
|
||||||
i+1, Shutter.real_position[i], Shutter.start_position[i], Shutter.target_position[i], Shutter.direction[i], Shutter.motordelay[i], stemp2, Shutter.pwm_frequency);
|
i+1, Shutter.real_position[i], Shutter.start_position[i], Shutter.target_position[i], Shutter.direction[i], Shutter.motordelay[i], stemp2, Shutter.pwm_frequency[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShutterRtc50mS(void)
|
void ShutterRtc50mS(void)
|
||||||
{
|
{
|
||||||
for (uint32_t i = 0; i < shutters_present; i++) {
|
for (uint8_t i = 0; i < shutters_present; i++) {
|
||||||
Shutter.time[i]++;
|
Shutter.time[i]++;
|
||||||
if (Shutter.accelerator[i]) {
|
if (Shutter.accelerator[i]) {
|
||||||
Shutter.pwm_frequency += Shutter.accelerator[i];
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: accelerator i=%d -> %d"),i, Shutter.accelerator[i]);
|
||||||
Shutter.pwm_frequency = tmax(0,tmin(Shutter.direction[i]==1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i],Shutter.pwm_frequency));
|
Shutter.pwm_frequency[i] += Shutter.accelerator[i];
|
||||||
analogWriteFreq(Shutter.pwm_frequency);
|
Shutter.pwm_frequency[i] = tmax(0,tmin(Shutter.direction[i]==1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i],Shutter.pwm_frequency[i]));
|
||||||
|
analogWriteFreq(Shutter.pwm_frequency[i]);
|
||||||
analogWrite(pin[GPIO_PWM1+i], 50);
|
analogWrite(pin[GPIO_PWM1+i], 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,8 +103,6 @@ void ShutterRtc50mS(void)
|
|||||||
|
|
||||||
int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index)
|
int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index)
|
||||||
{
|
{
|
||||||
if (0 == percent) return 0;
|
|
||||||
if (100 == percent) return Shutter.open_max[index];
|
|
||||||
if (Settings.shutter_set50percent[index] != 50) {
|
if (Settings.shutter_set50percent[index] != 50) {
|
||||||
return (percent <= 5) ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index];
|
return (percent <= 5) ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index];
|
||||||
} else {
|
} else {
|
||||||
@ -138,8 +137,6 @@ int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index)
|
|||||||
|
|
||||||
uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index)
|
uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index)
|
||||||
{
|
{
|
||||||
if (0 >= realpos) return 0;
|
|
||||||
if (Shutter.open_max[index] <= realpos) return 100;
|
|
||||||
if (Settings.shutter_set50percent[index] != 50) {
|
if (Settings.shutter_set50percent[index] != 50) {
|
||||||
return (Settings.shuttercoeff[2][index] * 5 > realpos) ? SHT_DIV_ROUND(realpos, Settings.shuttercoeff[2][index]) : SHT_DIV_ROUND(realpos-Settings.shuttercoeff[0][index], Settings.shuttercoeff[1][index]);
|
return (Settings.shuttercoeff[2][index] * 5 > realpos) ? SHT_DIV_ROUND(realpos, Settings.shuttercoeff[2][index]) : SHT_DIV_ROUND(realpos-Settings.shuttercoeff[0][index], Settings.shuttercoeff[1][index]);
|
||||||
} else {
|
} else {
|
||||||
@ -203,8 +200,9 @@ void ShutterInit(void)
|
|||||||
Shutter.mode = SHT_OFF_ON__OPEN_CLOSE;
|
Shutter.mode = SHT_OFF_ON__OPEN_CLOSE;
|
||||||
if ((pin[GPIO_PWM1+i] < 99) && (pin[GPIO_CNTR1+i] < 99)) {
|
if ((pin[GPIO_PWM1+i] < 99) && (pin[GPIO_CNTR1+i] < 99)) {
|
||||||
Shutter.mode = SHT_OFF_ON__OPEN_CLOSE_STEPPER;
|
Shutter.mode = SHT_OFF_ON__OPEN_CLOSE_STEPPER;
|
||||||
Shutter.pwm_frequency = 0;
|
Shutter.pwm_frequency[i] = 0;
|
||||||
analogWriteFreq(Shutter.pwm_frequency);
|
Shutter.accelerator[i] = 0;
|
||||||
|
analogWriteFreq(Shutter.pwm_frequency[i]);
|
||||||
analogWrite(pin[GPIO_PWM1+i], 50);
|
analogWrite(pin[GPIO_PWM1+i], 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,29 +254,24 @@ void ShutterInit(void)
|
|||||||
|
|
||||||
void ShutterReportPosition(bool always)
|
void ShutterReportPosition(bool always)
|
||||||
{
|
{
|
||||||
uint32_t shutter_moving = 0;
|
|
||||||
Response_P(PSTR("{"));
|
Response_P(PSTR("{"));
|
||||||
for (uint32_t i = 0; i < shutters_present; i++) {
|
for (uint32_t i = 0; i < shutters_present; i++) {
|
||||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d: Real Pos: %d"), i+1,Shutter.real_position[i]);
|
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d: Real Pos: %d"), i+1,Shutter.real_position[i]);
|
||||||
uint32_t position = ShutterRealToPercentPosition(Shutter.real_position[i], i);
|
uint32_t position = ShutterRealToPercentPosition(Shutter.real_position[i], i);
|
||||||
if (Shutter.direction[i] != 0) {
|
if (Shutter.direction[i] != 0) {
|
||||||
shutter_moving = 1;
|
rules_flag.shutter_moving = 1;
|
||||||
ShutterLogPos(i);
|
ShutterLogPos(i);
|
||||||
}
|
}
|
||||||
if (i) { ResponseAppend_P(PSTR(",")); }
|
if (i) { ResponseAppend_P(PSTR(",")); }
|
||||||
ResponseAppend_P(JSON_SHUTTER_POS, i+1, (Settings.shutter_options[i] & 1) ? 100-position : position, Shutter.direction[i]);
|
ResponseAppend_P(JSON_SHUTTER_POS, i+1, (Settings.shutter_options[i] & 1) ? 100-position : position, Shutter.direction[i], ShutterRealToPercentPosition(Shutter.target_position[i], i));
|
||||||
}
|
}
|
||||||
ResponseJsonEnd();
|
ResponseJsonEnd();
|
||||||
if (always || (1 == shutter_moving)) {
|
if (always || (rules_flag.shutter_moving)) {
|
||||||
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER));
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER));
|
||||||
|
XdrvRulesProcess();
|
||||||
}
|
}
|
||||||
if (rules_flag.shutter_moving > shutter_moving) {
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved);
|
||||||
rules_flag.shutter_moved = 1;
|
|
||||||
} else {
|
|
||||||
rules_flag.shutter_moved = 0;
|
|
||||||
}
|
|
||||||
rules_flag.shutter_moving = shutter_moving;
|
|
||||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShutterLimitRealAndTargetPositions(uint32_t i) {
|
void ShutterLimitRealAndTargetPositions(uint32_t i) {
|
||||||
@ -304,22 +297,22 @@ void ShutterUpdatePosition(void)
|
|||||||
|
|
||||||
int32_t max_frequency = Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i];
|
int32_t max_frequency = Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i];
|
||||||
int32_t max_freq_change_per_sec = Shutter.max_pwm_frequency*steps_per_second / (Shutter.motordelay[i]>0 ? Shutter.motordelay[i] : 1);
|
int32_t max_freq_change_per_sec = Shutter.max_pwm_frequency*steps_per_second / (Shutter.motordelay[i]>0 ? Shutter.motordelay[i] : 1);
|
||||||
int32_t min_runtime_ms = Shutter.pwm_frequency*1000 / max_freq_change_per_sec;
|
int32_t min_runtime_ms = Shutter.pwm_frequency[i]*1000 / max_freq_change_per_sec;
|
||||||
int32_t velocity = Shutter.direction[i] == 1 ? 100 : Shutter.close_velocity[i];
|
int32_t velocity = Shutter.direction[i] == 1 ? 100 : Shutter.close_velocity[i];
|
||||||
int32_t minstopway = min_runtime_ms * velocity / 100 * Shutter.pwm_frequency / max_frequency * Shutter.direction[i] ;
|
int32_t minstopway = min_runtime_ms * velocity / 100 * Shutter.pwm_frequency[i] / max_frequency * Shutter.direction[i] ;
|
||||||
|
|
||||||
int32_t next_possible_stop = Shutter.real_position[i] + minstopway ;
|
int32_t next_possible_stop = Shutter.real_position[i] + minstopway ;
|
||||||
stop_position_delta =200 * Shutter.pwm_frequency/max_frequency + Shutter.direction[i] * (next_possible_stop - Shutter.target_position[i]);
|
stop_position_delta =200 * Shutter.pwm_frequency[i]/max_frequency + Shutter.direction[i] * (next_possible_stop - Shutter.target_position[i]);
|
||||||
//Shutter.accelerator[i] = tmin(tmax(max_freq_change_per_sec*(100-(Shutter.direction[i]*(Shutter.target_position[i]-next_possible_stop) ))/2000 , max_freq_change_per_sec*9/200), max_freq_change_per_sec*11/200);
|
//Shutter.accelerator[i] = tmin(tmax(max_freq_change_per_sec*(100-(Shutter.direction[i]*(Shutter.target_position[i]-next_possible_stop) ))/2000 , max_freq_change_per_sec*9/200), max_freq_change_per_sec*11/200);
|
||||||
//int32_t act_freq_change = max_freq_change_per_sec/20;
|
//int32_t act_freq_change = max_freq_change_per_sec/20;
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: time: %d, velocity %d, minstopway %d,cur_freq %d, max_frequency %d, act_freq_change %d, min_runtime_ms %d, act.pos %d, next_stop %d, target: %d"),Shutter.time[i],velocity,minstopway,
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: time: %d, velocity %d, minstopway %d,cur_freq %d, max_frequency %d, act_freq_change %d, min_runtime_ms %d, act.pos %d, next_stop %d, target: %d"),Shutter.time[i],velocity,minstopway,
|
||||||
Shutter.pwm_frequency,max_frequency, Shutter.accelerator[i],min_runtime_ms,Shutter.real_position[i], next_possible_stop,Shutter.target_position[i]);
|
Shutter.pwm_frequency[i],max_frequency, Shutter.accelerator[i],min_runtime_ms,Shutter.real_position[i], next_possible_stop,Shutter.target_position[i]);
|
||||||
|
|
||||||
if (Shutter.accelerator[i] < 0 || next_possible_stop * Shutter.direction[i] > Shutter.target_position[i] * Shutter.direction[i] ) {
|
if (Shutter.accelerator[i] < 0 || next_possible_stop * Shutter.direction[i] > Shutter.target_position[i] * Shutter.direction[i] ) {
|
||||||
|
|
||||||
Shutter.accelerator[i] = - tmin(tmax(max_freq_change_per_sec*(100-(Shutter.direction[i]*(Shutter.target_position[i]-next_possible_stop) ))/2000 , max_freq_change_per_sec*9/200), max_freq_change_per_sec*12/200);
|
Shutter.accelerator[i] = - tmin(tmax(max_freq_change_per_sec*(100-(Shutter.direction[i]*(Shutter.target_position[i]-next_possible_stop) ))/2000 , max_freq_change_per_sec*9/200), max_freq_change_per_sec*12/200);
|
||||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Ramp down: acc: %d"), Shutter.accelerator[i]);
|
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Ramp down: acc: %d"), Shutter.accelerator[i]);
|
||||||
} else if ( Shutter.accelerator[i] > 0 && Shutter.pwm_frequency == max_frequency) {
|
} else if ( Shutter.accelerator[i] > 0 && Shutter.pwm_frequency[i] == max_frequency) {
|
||||||
Shutter.accelerator[i] = 0;
|
Shutter.accelerator[i] = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -343,13 +336,13 @@ void ShutterUpdatePosition(void)
|
|||||||
case SHT_OFF_ON__OPEN_CLOSE_STEPPER:
|
case SHT_OFF_ON__OPEN_CLOSE_STEPPER:
|
||||||
missing_steps = ((Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) - RtcSettings.pulse_counter[i];
|
missing_steps = ((Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) - RtcSettings.pulse_counter[i];
|
||||||
//prepare for stop PWM
|
//prepare for stop PWM
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Remain steps %d, counter %d, freq %d"), missing_steps, RtcSettings.pulse_counter[i] ,Shutter.pwm_frequency);
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Remain steps %d, counter %d, freq %d"), missing_steps, RtcSettings.pulse_counter[i] ,Shutter.pwm_frequency[i]);
|
||||||
Shutter.accelerator[i] = 0;
|
Shutter.accelerator[i] = 0;
|
||||||
Shutter.pwm_frequency = Shutter.pwm_frequency > 250 ? 250 : Shutter.pwm_frequency;
|
Shutter.pwm_frequency[i] = Shutter.pwm_frequency[i] > 250 ? 250 : Shutter.pwm_frequency[i];
|
||||||
analogWriteFreq(Shutter.pwm_frequency);
|
analogWriteFreq(Shutter.pwm_frequency[i]);
|
||||||
analogWrite(pin[GPIO_PWM1+i], 50);
|
analogWrite(pin[GPIO_PWM1+i], 50);
|
||||||
Shutter.pwm_frequency = 0;
|
Shutter.pwm_frequency[i] = 0;
|
||||||
analogWriteFreq(Shutter.pwm_frequency);
|
analogWriteFreq(Shutter.pwm_frequency[i]);
|
||||||
while (RtcSettings.pulse_counter[i] < (uint32_t)(Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) {
|
while (RtcSettings.pulse_counter[i] < (uint32_t)(Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) {
|
||||||
delay(1);
|
delay(1);
|
||||||
}
|
}
|
||||||
@ -391,6 +384,7 @@ void ShutterUpdatePosition(void)
|
|||||||
|
|
||||||
Shutter.direction[i] = 0;
|
Shutter.direction[i] = 0;
|
||||||
ShutterReportPosition(true);
|
ShutterReportPosition(true);
|
||||||
|
rules_flag.shutter_moved = 1;
|
||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,8 +407,8 @@ void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos)
|
|||||||
Shutter.skip_relay_change = 1;
|
Shutter.skip_relay_change = 1;
|
||||||
} else {
|
} else {
|
||||||
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) {
|
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) {
|
||||||
Shutter.pwm_frequency = 0;
|
Shutter.pwm_frequency[i] = 0;
|
||||||
analogWriteFreq(Shutter.pwm_frequency);
|
analogWriteFreq(Shutter.pwm_frequency[i]);
|
||||||
analogWrite(pin[GPIO_PWM1+i], 0);
|
analogWrite(pin[GPIO_PWM1+i], 0);
|
||||||
// can be operated without counter, but then not that acurate.
|
// can be operated without counter, but then not that acurate.
|
||||||
if (pin[GPIO_CNTR1+i] < 99) {
|
if (pin[GPIO_CNTR1+i] < 99) {
|
||||||
@ -439,10 +433,11 @@ void ShutterWaitForMotorStop(uint32_t i)
|
|||||||
if ((SHT_OFF_ON__OPEN_CLOSE == Shutter.mode) || (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode)) {
|
if ((SHT_OFF_ON__OPEN_CLOSE == Shutter.mode) || (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode)) {
|
||||||
if (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode) {
|
if (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode) {
|
||||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Frequency change %d"), Shutter.pwm_frequency);
|
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Frequency change %d"), Shutter.pwm_frequency);
|
||||||
while (Shutter.pwm_frequency > 0) {
|
while (Shutter.pwm_frequency[i] > 0) {
|
||||||
Shutter.accelerator[i] = 0;
|
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Frequency: %ld, delta: %d"), Shutter.pwm_frequency[i], (int32_t)((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) );
|
||||||
Shutter.pwm_frequency = tmax(Shutter.pwm_frequency-((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) , 0);
|
Shutter.pwm_frequency[i] = tmax(Shutter.pwm_frequency[i]-((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) , 0);
|
||||||
analogWriteFreq(Shutter.pwm_frequency);
|
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Frequency: %ld"), Shutter.pwm_frequency[i]);
|
||||||
|
analogWriteFreq(Shutter.pwm_frequency[i]);
|
||||||
analogWrite(pin[GPIO_PWM1+i], 50);
|
analogWrite(pin[GPIO_PWM1+i], 50);
|
||||||
delay(50);
|
delay(50);
|
||||||
}
|
}
|
||||||
@ -532,7 +527,6 @@ void ShutterButtonHandler(void)
|
|||||||
uint8_t shutter_index = Settings.shutter_button[button_index] & 0x03;
|
uint8_t shutter_index = Settings.shutter_button[button_index] & 0x03;
|
||||||
|
|
||||||
uint16_t loops_per_second = 1000 / Settings.button_debounce; // ButtonDebounce (50)
|
uint16_t loops_per_second = 1000 / Settings.button_debounce; // ButtonDebounce (50)
|
||||||
|
|
||||||
if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) {
|
if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) {
|
||||||
if (Settings.flag.button_single) { // SetOption13 (0) - Allow only single button press for immediate action
|
if (Settings.flag.button_single) { // SetOption13 (0) - Allow only single button press for immediate action
|
||||||
buttonState = SHT_PRESSED_MULTI;
|
buttonState = SHT_PRESSED_MULTI;
|
||||||
@ -542,9 +536,9 @@ void ShutterButtonHandler(void)
|
|||||||
buttonState = SHT_PRESSED_IMMEDIATE;
|
buttonState = SHT_PRESSED_IMMEDIATE;
|
||||||
press_index = 1;
|
press_index = 1;
|
||||||
Button.press_counter[button_index] = 99; // Remember to discard further action for press & hold within button timings
|
Button.press_counter[button_index] = 99; // Remember to discard further action for press & hold within button timings
|
||||||
} else
|
} else {
|
||||||
Button.press_counter[button_index] = (Button.window_timer[button_index]) ? Button.press_counter[button_index] +1 : 1;
|
Button.press_counter[button_index] = (Button.window_timer[button_index]) ? Button.press_counter[button_index] +1 : 1;
|
||||||
Button.window_timer[button_index] = loops_per_second / 2; // 0.5 second multi press window
|
}
|
||||||
}
|
}
|
||||||
blinks = 201;
|
blinks = 201;
|
||||||
}
|
}
|
||||||
@ -596,14 +590,18 @@ void ShutterButtonHandler(void)
|
|||||||
// check for simultaneous shutter button press
|
// check for simultaneous shutter button press
|
||||||
uint32 min_shutterbutton_press_counter = -1;
|
uint32 min_shutterbutton_press_counter = -1;
|
||||||
for (uint32_t i = 0; i < MAX_KEYS; i++) {
|
for (uint32_t i = 0; i < MAX_KEYS; i++) {
|
||||||
if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index) && (Button.press_counter[i] < min_shutterbutton_press_counter))
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Settings.shutter_button[i] %ld, shutter_index %d, Button.press_counter[i] %d, min_shutterbutton_press_counter %d"), Settings.shutter_button[i], shutter_index, Button.press_counter[i] , min_shutterbutton_press_counter);
|
||||||
|
if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index) && (Button.press_counter[i] < min_shutterbutton_press_counter)) {
|
||||||
min_shutterbutton_press_counter = Button.press_counter[i];
|
min_shutterbutton_press_counter = Button.press_counter[i];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (min_shutterbutton_press_counter == Button.press_counter[button_index]) {
|
if (min_shutterbutton_press_counter == Button.press_counter[button_index]) {
|
||||||
// simultaneous shutter button press detected
|
// simultaneous shutter button press detected
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT:simultanous presss deteced"));
|
||||||
press_index = Button.press_counter[button_index];
|
press_index = Button.press_counter[button_index];
|
||||||
for (uint32_t i = 0; i < MAX_KEYS; i++)
|
for (uint32_t i = 0; i < MAX_KEYS; i++)
|
||||||
if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index))
|
if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) != shutter_index))
|
||||||
Button.press_counter[i] = 99; // Remember to discard further action for press & hold within button timings
|
Button.press_counter[i] = 99; // Remember to discard further action for press & hold within button timings
|
||||||
buttonState = SHT_PRESSED_MULTI_SIMULTANEOUS;
|
buttonState = SHT_PRESSED_MULTI_SIMULTANEOUS;
|
||||||
}
|
}
|
||||||
@ -628,7 +626,6 @@ void ShutterButtonHandler(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (buttonState == SHT_PRESSED_EXT_HOLD_SIMULTANEOUS) {
|
} else if (buttonState == SHT_PRESSED_EXT_HOLD_SIMULTANEOUS) {
|
||||||
// simultaneous shutter button extend hold detected
|
|
||||||
if (!Settings.flag.button_restrict) { // no SetOption1 (0)
|
if (!Settings.flag.button_restrict) { // no SetOption1 (0)
|
||||||
char scmnd[20];
|
char scmnd[20];
|
||||||
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1"));
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1"));
|
||||||
@ -649,8 +646,7 @@ void ShutterButtonHandler(void)
|
|||||||
if (buttonState == SHT_PRESSED_IMMEDIATE) {
|
if (buttonState == SHT_PRESSED_IMMEDIATE) {
|
||||||
XdrvMailbox.payload = XdrvMailbox.index;
|
XdrvMailbox.payload = XdrvMailbox.index;
|
||||||
CmndShutterStop();
|
CmndShutterStop();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
uint8_t position = (Settings.shutter_button[button_index]>>(6*pos_press_index + 2)) & 0x03f;
|
uint8_t position = (Settings.shutter_button[button_index]>>(6*pos_press_index + 2)) & 0x03f;
|
||||||
if (position) {
|
if (position) {
|
||||||
if (Shutter.direction[shutter_index]) {
|
if (Shutter.direction[shutter_index]) {
|
||||||
@ -670,12 +666,12 @@ void ShutterButtonHandler(void)
|
|||||||
Response_P("%d", position);
|
Response_P("%d", position);
|
||||||
MqttPublish(stopic, false);
|
MqttPublish(stopic, false);
|
||||||
}
|
}
|
||||||
}
|
} // for (uint32_t)
|
||||||
}
|
} // if (Settings.shutter)
|
||||||
}
|
} // ende else
|
||||||
}
|
} // if (position)
|
||||||
}
|
} // end else
|
||||||
}
|
} // if if (Settings.shutter_startrelay[shutter_index]
|
||||||
}
|
}
|
||||||
Response_P(PSTR("{"));
|
Response_P(PSTR("{"));
|
||||||
ResponseAppend_P(JSON_SHUTTER_BUTTON, shutter_index+1, (buttonState <= SHT_PRESSED_IMMEDIATE) ? (button_index+1) : 0, press_index);
|
ResponseAppend_P(JSON_SHUTTER_BUTTON, shutter_index+1, (buttonState <= SHT_PRESSED_IMMEDIATE) ? (button_index+1) : 0, press_index);
|
||||||
@ -756,6 +752,7 @@ void CmndShutterPosition(void)
|
|||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Pos. in: payload %s (%d), payload %d, idx %d, src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, last_source );
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Pos. in: payload %s (%d), payload %d, idx %d, src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, last_source );
|
||||||
|
|
||||||
// value 0 with data_len > 0 can mean Open
|
// value 0 with data_len > 0 can mean Open
|
||||||
|
// special handling fo UP,DOWN,TOGGLE,STOP command comming with payload -99
|
||||||
if ((XdrvMailbox.data_len > 1) && (XdrvMailbox.payload <= 0)) {
|
if ((XdrvMailbox.data_len > 1) && (XdrvMailbox.payload <= 0)) {
|
||||||
//UpperCase(XdrvMailbox.data, XdrvMailbox.data);
|
//UpperCase(XdrvMailbox.data, XdrvMailbox.data);
|
||||||
if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_UP) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_OPEN) || ((Shutter.direction[index]==0) && !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEUP))) {
|
if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_UP) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_OPEN) || ((Shutter.direction[index]==0) && !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEUP))) {
|
||||||
@ -773,13 +770,13 @@ void CmndShutterPosition(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t target_pos_percent = (XdrvMailbox.payload < 0) ? 0 : ((XdrvMailbox.payload > 100) ? 100 : XdrvMailbox.payload);
|
int8_t target_pos_percent = (XdrvMailbox.payload < 0) ? (XdrvMailbox.payload == -99 ? ShutterRealToPercentPosition(Shutter.real_position[index], index) : 0) : ((XdrvMailbox.payload > 100) ? 100 : XdrvMailbox.payload);
|
||||||
// webgui still send also on inverted shutter the native position.
|
// webgui still send also on inverted shutter the native position.
|
||||||
target_pos_percent = ((Settings.shutter_options[index] & 1) && (SRC_WEBGUI != last_source)) ? 100 - target_pos_percent : target_pos_percent;
|
target_pos_percent = ((Settings.shutter_options[index] & 1) && (SRC_WEBGUI != last_source)) ? 100 - target_pos_percent : target_pos_percent;
|
||||||
if (XdrvMailbox.payload != -99) {
|
if (XdrvMailbox.payload != -99) {
|
||||||
//target_pos_percent = (Settings.shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent;
|
//target_pos_percent = (Settings.shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent;
|
||||||
Shutter.target_position[index] = ShutterPercentToRealPosition(target_pos_percent, index);
|
Shutter.target_position[index] = ShutterPercentToRealPosition(target_pos_percent, index);
|
||||||
Shutter.accelerator[index] = Shutter.max_pwm_frequency / ((Shutter.motordelay[index] > 0) ? Shutter.motordelay[index] : 1);
|
//Shutter.accelerator[index] = Shutter.max_pwm_frequency / ((Shutter.motordelay[index] > 0) ? Shutter.motordelay[index] : 1);
|
||||||
//Shutter.target_position[index] = XdrvMailbox.payload < 5 ? Settings.shuttercoeff[2][index] * XdrvMailbox.payload : Settings.shuttercoeff[1][index] * XdrvMailbox.payload + Settings.shuttercoeff[0,index];
|
//Shutter.target_position[index] = XdrvMailbox.payload < 5 ? Settings.shuttercoeff[2][index] * XdrvMailbox.payload : Settings.shuttercoeff[1][index] * XdrvMailbox.payload + Settings.shuttercoeff[0,index];
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: lastsource %d:, real %d, target %d, payload %d"), last_source, Shutter.real_position[index] ,Shutter.target_position[index],target_pos_percent);
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: lastsource %d:, real %d, target %d, payload %d"), last_source, Shutter.real_position[index] ,Shutter.target_position[index],target_pos_percent);
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ bool DhtRead(uint32_t sensor)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
bool error = false;
|
bool error = false;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) {
|
if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) {
|
||||||
@ -127,6 +128,22 @@ bool DhtRead(uint32_t sensor)
|
|||||||
}
|
}
|
||||||
interrupts();
|
interrupts();
|
||||||
if (error) { return false; }
|
if (error) { return false; }
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint32_t i = 0;
|
||||||
|
noInterrupts();
|
||||||
|
if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) {
|
||||||
|
for (i = 0; i < 40; i++) {
|
||||||
|
if (!DhtWaitState(sensor, 1)) { break; }
|
||||||
|
delayMicroseconds(35); // Was 30
|
||||||
|
if (digitalRead(Dht[sensor].pin)) {
|
||||||
|
dht_data[i / 8] |= (1 << (7 - i % 8));
|
||||||
|
}
|
||||||
|
if (!DhtWaitState(sensor, 0)) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interrupts();
|
||||||
|
if (i < 40) { return false; }
|
||||||
|
|
||||||
uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF;
|
uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF;
|
||||||
if (dht_data[4] != checksum) {
|
if (dht_data[4] != checksum) {
|
||||||
|
@ -41,7 +41,8 @@
|
|||||||
#define XSNS_35 35
|
#define XSNS_35 35
|
||||||
|
|
||||||
#define TX2X_BIT_TIME 1220 // microseconds
|
#define TX2X_BIT_TIME 1220 // microseconds
|
||||||
#define TX2X_RESET_VALUES 60 // seconds
|
#define TX2X_WEIGHT_AVG_SAMPLE 150 // seconds
|
||||||
|
#define TX23_READ_INTERVAL 4 // seconds (don't use less than 3)
|
||||||
|
|
||||||
// The Arduino standard GPIO routines are not enough,
|
// The Arduino standard GPIO routines are not enough,
|
||||||
// must use some from the Espressif SDK as well
|
// must use some from the Espressif SDK as well
|
||||||
@ -59,8 +60,9 @@ extern "C" {
|
|||||||
const char HTTP_SNS_TX2X[] PROGMEM =
|
const char HTTP_SNS_TX2X[] PROGMEM =
|
||||||
"{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
"{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
||||||
"{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_AVG "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
"{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_AVG "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
||||||
|
"{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MIN "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
||||||
"{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
"{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
||||||
"{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION "{m}%s (%s°){e}";
|
"{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION "{m}%s %s°{e}";
|
||||||
#endif // USE_WEBSERVER
|
#endif // USE_WEBSERVER
|
||||||
|
|
||||||
const char kTx2xDirections[] PROGMEM = D_TX20_NORTH "|"
|
const char kTx2xDirections[] PROGMEM = D_TX20_NORTH "|"
|
||||||
@ -88,11 +90,12 @@ uint16_t tx2x_sc = 0;
|
|||||||
uint16_t tx2x_sf = 0;
|
uint16_t tx2x_sf = 0;
|
||||||
|
|
||||||
float tx2x_wind_speed_kmh = 0;
|
float tx2x_wind_speed_kmh = 0;
|
||||||
|
float tx2x_wind_speed_min = 200.0;
|
||||||
float tx2x_wind_speed_max = 0;
|
float tx2x_wind_speed_max = 0;
|
||||||
float tx2x_wind_speed_avg = 0;
|
float tx2x_wind_speed_avg = 0;
|
||||||
float tx2x_wind_sum = 0;
|
|
||||||
int tx2x_count = 0;
|
|
||||||
uint8_t tx2x_wind_direction = 0;
|
uint8_t tx2x_wind_direction = 0;
|
||||||
|
int tx2x_count = 0;
|
||||||
|
uint16_t tx2x_avg_samples;
|
||||||
|
|
||||||
bool tx2x_available = false;
|
bool tx2x_available = false;
|
||||||
|
|
||||||
@ -121,7 +124,6 @@ void TX2xStartRead(void)
|
|||||||
* La Crosse TX23 Anemometer datagram after setting TxD to low/high
|
* La Crosse TX23 Anemometer datagram after setting TxD to low/high
|
||||||
* 1-1 0 1 0-0 11011 0011 111010101111 0101 1100 000101010000 1-1 - Received pin data at 1200 uSec per bit
|
* 1-1 0 1 0-0 11011 0011 111010101111 0101 1100 000101010000 1-1 - Received pin data at 1200 uSec per bit
|
||||||
* t s c sa sb sc sd se sf
|
* t s c sa sb sc sd se sf
|
||||||
* 1 0 1-1 00100 1100 000101010000 1010 1100 000101010000 - sa to sd inverted user data, LSB first
|
|
||||||
* t - host pulls TxD low - signals TX23 to sent measurement
|
* t - host pulls TxD low - signals TX23 to sent measurement
|
||||||
* s - TxD released - TxD is pulled high due to pullup
|
* s - TxD released - TxD is pulled high due to pullup
|
||||||
* c - TX23U pulls TxD low - calculation in progress
|
* c - TX23U pulls TxD low - calculation in progress
|
||||||
@ -152,26 +154,8 @@ void TX2xStartRead(void)
|
|||||||
for (int32_t bitcount = 41; bitcount > 0; bitcount--) {
|
for (int32_t bitcount = 41; bitcount > 0; bitcount--) {
|
||||||
uint8_t dpin = (digitalRead(pin[GPIO_TX2X_TXD_BLACK]));
|
uint8_t dpin = (digitalRead(pin[GPIO_TX2X_TXD_BLACK]));
|
||||||
#ifdef USE_TX23_WIND_SENSOR
|
#ifdef USE_TX23_WIND_SENSOR
|
||||||
if (bitcount > 41 - 5) {
|
dpin ^= 1;
|
||||||
// start
|
#endif
|
||||||
tx2x_sa = (tx2x_sa << 1) | (dpin);
|
|
||||||
} else if (bitcount > 41 - 5 - 4) {
|
|
||||||
// wind dir
|
|
||||||
tx2x_sb = tx2x_sb >> 1 | ((dpin) << 3);
|
|
||||||
} else if (bitcount > 41 - 5 - 4 - 12) {
|
|
||||||
// windspeed
|
|
||||||
tx2x_sc = tx2x_sc >> 1 | ((dpin) << 11);
|
|
||||||
} else if (bitcount > 41 - 5 - 4 - 12 - 4) {
|
|
||||||
// checksum
|
|
||||||
tx2x_sd = tx2x_sd >> 1 | ((dpin) << 3);
|
|
||||||
} else if (bitcount > 41 - 5 - 4 - 12 - 4 - 4) {
|
|
||||||
// wind dir (invert)
|
|
||||||
tx2x_se = tx2x_se >> 1 | ((dpin ^ 1) << 3);
|
|
||||||
} else {
|
|
||||||
// windspeed (invert)
|
|
||||||
tx2x_sf = tx2x_sf >> 1 | ((dpin ^ 1) << 11);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (bitcount > 41 - 5) {
|
if (bitcount > 41 - 5) {
|
||||||
// start frame (invert)
|
// start frame (invert)
|
||||||
tx2x_sa = (tx2x_sa << 1) | (dpin ^ 1);
|
tx2x_sa = (tx2x_sa << 1) | (dpin ^ 1);
|
||||||
@ -191,23 +175,14 @@ void TX2xStartRead(void)
|
|||||||
// windspeed
|
// windspeed
|
||||||
tx2x_sf = tx2x_sf >> 1 | (dpin << 11);
|
tx2x_sf = tx2x_sf >> 1 | (dpin << 11);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
delayMicroseconds(TX2X_BIT_TIME);
|
delayMicroseconds(TX2X_BIT_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t chk = (tx2x_sb + (tx2x_sc & 0xf) + ((tx2x_sc >> 4) & 0xf) + ((tx2x_sc >> 8) & 0xf));
|
uint8_t chk = (tx2x_sb + (tx2x_sc & 0xf) + ((tx2x_sc >> 4) & 0xf) + ((tx2x_sc >> 8) & 0xf));
|
||||||
chk &= 0xf;
|
chk &= 0xf;
|
||||||
|
|
||||||
#ifdef USE_TX23_WIND_SENSOR
|
|
||||||
// check checksum, non-inverted with inverted values and max. speed
|
// check checksum, non-inverted with inverted values and max. speed
|
||||||
if ((chk == tx2x_sd) && (tx2x_sb==tx2x_se) && (tx2x_sc==tx2x_sf) && (tx2x_sc < 511)) {
|
tx2x_available = ((chk == tx2x_sd) && (tx2x_sb==tx2x_se) && (tx2x_sc==tx2x_sf) && (tx2x_sc < 511));
|
||||||
tx2x_available = true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if ((chk == tx2x_sd) && (tx2x_sc < 511)) { // if checksum seems to be ok and wind speed below 51.1 m/s
|
|
||||||
tx2x_available = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef USE_TX23_WIND_SENSOR
|
#ifdef USE_TX23_WIND_SENSOR
|
||||||
}
|
}
|
||||||
tx23_stage++;
|
tx23_stage++;
|
||||||
@ -219,49 +194,82 @@ void TX2xStartRead(void)
|
|||||||
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << pin[GPIO_TX2X_TXD_BLACK]);
|
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << pin[GPIO_TX2X_TXD_BLACK]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tx2xReset(void)
|
void Tx2xResetStat(void)
|
||||||
{
|
{
|
||||||
|
tx2x_wind_speed_min = tx2x_wind_speed_kmh;
|
||||||
|
tx2x_wind_speed_max = tx2x_wind_speed_kmh;
|
||||||
|
uint16_t tx2x_prev_avg_samples = tx2x_avg_samples;
|
||||||
|
if (Settings.tele_period) {
|
||||||
|
// number for avg samples = teleperiod value if set
|
||||||
|
tx2x_avg_samples = Settings.tele_period;
|
||||||
|
} else {
|
||||||
|
// otherwise use default number of samples for this driver
|
||||||
|
tx2x_avg_samples = TX2X_WEIGHT_AVG_SAMPLE;
|
||||||
|
}
|
||||||
|
if (tx2x_prev_avg_samples != tx2x_avg_samples) {
|
||||||
|
tx2x_wind_speed_avg = tx2x_wind_speed_kmh;
|
||||||
tx2x_count = 0;
|
tx2x_count = 0;
|
||||||
tx2x_wind_sum = 0;
|
}
|
||||||
tx2x_wind_speed_max = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tx2xRead(void)
|
void Tx2xRead(void)
|
||||||
{
|
{
|
||||||
#ifdef USE_TX23_WIND_SENSOR
|
#ifdef USE_TX23_WIND_SENSOR
|
||||||
|
// TX23 needs to trigger start transmission - TxD Line
|
||||||
|
// ___________ _ ___ ___
|
||||||
|
// |____| |___________| |_| |__XXXXXXXXXX
|
||||||
|
// trigger start conv Startframe Data
|
||||||
|
//
|
||||||
// note: TX23 speed calculation is unstable when conversion starts
|
// note: TX23 speed calculation is unstable when conversion starts
|
||||||
// less than 2 seconds after last request
|
// less than 2 seconds after last request
|
||||||
if ((uptime % 3)==0) {
|
if ((uptime % TX23_READ_INTERVAL)==0) {
|
||||||
// TX23 start transmission by pulling down TxD line for at minimum 500ms
|
// TX23 start transmission by pulling down TxD line for at minimum 500ms
|
||||||
// so we pull TxD signal to low every 3 seconds
|
// so we pull TxD signal to low every 3 seconds
|
||||||
tx23_stage = 0;
|
tx23_stage = 0;
|
||||||
pinMode(pin[GPIO_TX2X_TXD_BLACK], OUTPUT);
|
pinMode(pin[GPIO_TX2X_TXD_BLACK], OUTPUT);
|
||||||
digitalWrite(pin[GPIO_TX2X_TXD_BLACK], LOW);
|
digitalWrite(pin[GPIO_TX2X_TXD_BLACK], LOW);
|
||||||
} else if ((uptime % 3)==1) {
|
} else if ((uptime % TX23_READ_INTERVAL)==1) {
|
||||||
// after pulling down TxD every 3 second we pull-up TxD every 3+1 seconds
|
// after pulling down TxD: pull-up TxD every x+1 seconds
|
||||||
// to trigger start transmission
|
// to trigger TX23 start transmission
|
||||||
tx23_stage = 1; // first rising signal is invalid
|
tx23_stage = 1; // first rising signal is invalid
|
||||||
pinMode(pin[GPIO_TX2X_TXD_BLACK], INPUT_PULLUP);
|
pinMode(pin[GPIO_TX2X_TXD_BLACK], INPUT_PULLUP);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (0==Settings.tele_period && !(uptime % TX2X_RESET_VALUES)) {
|
if (0!=Settings.tele_period && Settings.tele_period!=tx2x_avg_samples) {
|
||||||
Tx2xReset();
|
// new teleperiod value
|
||||||
|
Tx2xResetStat();
|
||||||
}
|
}
|
||||||
else if (tx2x_available) {
|
if (tx2x_available) {
|
||||||
// Wind speed spec: 0 to 180 km/h (0 to 50 m/s)
|
// Wind speed spec: 0 to 180 km/h (0 to 50 m/s)
|
||||||
tx2x_wind_speed_kmh = float(tx2x_sc) * 0.36;
|
tx2x_wind_speed_kmh = float(tx2x_sc) * 0.36;
|
||||||
|
if (tx2x_wind_speed_kmh < tx2x_wind_speed_min) {
|
||||||
|
tx2x_wind_speed_min = tx2x_wind_speed_kmh;
|
||||||
|
}
|
||||||
if (tx2x_wind_speed_kmh > tx2x_wind_speed_max) {
|
if (tx2x_wind_speed_kmh > tx2x_wind_speed_max) {
|
||||||
tx2x_wind_speed_max = tx2x_wind_speed_kmh;
|
tx2x_wind_speed_max = tx2x_wind_speed_kmh;
|
||||||
}
|
}
|
||||||
tx2x_count++;
|
// exponentially weighted average is not quite as smooth as the arithmetic average
|
||||||
tx2x_wind_sum += tx2x_wind_speed_kmh;
|
// but close enough to the moving average and does not require the regular reset
|
||||||
tx2x_wind_speed_avg = tx2x_wind_sum / tx2x_count;
|
// of the divider with the associated jump in avg values after period is over
|
||||||
|
if (tx2x_count <= tx2x_avg_samples) {
|
||||||
|
tx2x_count++;
|
||||||
|
}
|
||||||
|
tx2x_wind_speed_avg -= tx2x_wind_speed_avg / tx2x_count;
|
||||||
|
tx2x_wind_speed_avg += tx2x_wind_speed_kmh / tx2x_count;
|
||||||
|
|
||||||
tx2x_wind_direction = tx2x_sb;
|
tx2x_wind_direction = tx2x_sb;
|
||||||
|
|
||||||
|
if (!(uptime % tx2x_avg_samples)) {
|
||||||
|
tx2x_wind_speed_min = tx2x_wind_speed_kmh;
|
||||||
|
tx2x_wind_speed_max = tx2x_wind_speed_kmh;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tx2xInit(void)
|
void Tx2xInit(void)
|
||||||
{
|
{
|
||||||
|
Tx2xResetStat();
|
||||||
#ifdef USE_TX23_WIND_SENSOR
|
#ifdef USE_TX23_WIND_SENSOR
|
||||||
tx23_stage = 0;
|
tx23_stage = 0;
|
||||||
pinMode(pin[GPIO_TX2X_TXD_BLACK], OUTPUT);
|
pinMode(pin[GPIO_TX2X_TXD_BLACK], OUTPUT);
|
||||||
@ -276,22 +284,46 @@ void Tx2xShow(bool json)
|
|||||||
{
|
{
|
||||||
char wind_speed_string[33];
|
char wind_speed_string[33];
|
||||||
dtostrfd(tx2x_wind_speed_kmh, 1, wind_speed_string);
|
dtostrfd(tx2x_wind_speed_kmh, 1, wind_speed_string);
|
||||||
|
char wind_speed_min_string[33];
|
||||||
|
dtostrfd(tx2x_wind_speed_min, 1, wind_speed_min_string);
|
||||||
char wind_speed_max_string[33];
|
char wind_speed_max_string[33];
|
||||||
dtostrfd(tx2x_wind_speed_max, 1, wind_speed_max_string);
|
dtostrfd(tx2x_wind_speed_max, 1, wind_speed_max_string);
|
||||||
char wind_speed_avg_string[33];
|
char wind_speed_avg_string[33];
|
||||||
dtostrfd(tx2x_wind_speed_avg, 1, wind_speed_avg_string);
|
dtostrfd(tx2x_wind_speed_avg, 1, wind_speed_avg_string);
|
||||||
|
char wind_direction_degree_string[33];
|
||||||
|
dtostrfd(tx2x_wind_direction*22.5, 1, wind_direction_degree_string);
|
||||||
char wind_direction_string[4];
|
char wind_direction_string[4];
|
||||||
GetTextIndexed(wind_direction_string, sizeof(wind_direction_string), tx2x_wind_direction, kTx2xDirections);
|
GetTextIndexed(wind_direction_string, sizeof(wind_direction_string), tx2x_wind_direction, kTx2xDirections);
|
||||||
char wind_direction_degree[33];
|
|
||||||
dtostrfd(tx2x_wind_direction*22.5, 1, wind_direction_degree);
|
|
||||||
|
|
||||||
if (json) {
|
if (json) {
|
||||||
|
#ifdef USE_TX2x_LEGACY_JSON
|
||||||
ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"Speed\":%s,\"SpeedAvg\":%s,\"SpeedMax\":%s,\"Direction\":\"%s\",\"Degree\":%s}"),
|
ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"Speed\":%s,\"SpeedAvg\":%s,\"SpeedMax\":%s,\"Direction\":\"%s\",\"Degree\":%s}"),
|
||||||
wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string, wind_direction_degree);
|
wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string, wind_direction_degree);
|
||||||
Tx2xReset();
|
#else
|
||||||
|
// new format grouped by Speed and Dir(ection)
|
||||||
|
// Card = cardianal (N../O../S../W..)
|
||||||
|
// Deg = Degree
|
||||||
|
ResponseAppend_P(
|
||||||
|
PSTR(",\"" D_TX2x_NAME "\":{\"Speed\":{\"Act\":%s,\"Avg\":%s,\"Min\":%s,\"Max\":%s},\"Direction\":{\"Cardinal\":\"%s\",\"Degree\":%s}}"),
|
||||||
|
wind_speed_string,
|
||||||
|
wind_speed_avg_string,
|
||||||
|
wind_speed_min_string,
|
||||||
|
wind_speed_max_string,
|
||||||
|
wind_direction_string,
|
||||||
|
wind_direction_degree_string
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifdef USE_WEBSERVER
|
#ifdef USE_WEBSERVER
|
||||||
} else {
|
} else {
|
||||||
WSContentSend_PD(HTTP_SNS_TX2X, wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string, wind_direction_degree);
|
WSContentSend_PD(
|
||||||
|
HTTP_SNS_TX2X,
|
||||||
|
wind_speed_string,
|
||||||
|
wind_speed_avg_string,
|
||||||
|
wind_speed_min_string,
|
||||||
|
wind_speed_max_string,
|
||||||
|
wind_direction_string,
|
||||||
|
wind_direction_degree_string
|
||||||
|
);
|
||||||
#endif // USE_WEBSERVER
|
#endif // USE_WEBSERVER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,11 +345,11 @@ bool Xsns35(uint8_t function)
|
|||||||
Tx2xRead();
|
Tx2xRead();
|
||||||
break;
|
break;
|
||||||
case FUNC_JSON_APPEND:
|
case FUNC_JSON_APPEND:
|
||||||
Tx2xShow(1);
|
Tx2xShow(true);
|
||||||
break;
|
break;
|
||||||
#ifdef USE_WEBSERVER
|
#ifdef USE_WEBSERVER
|
||||||
case FUNC_WEB_SENSOR:
|
case FUNC_WEB_SENSOR:
|
||||||
Tx2xShow(0);
|
Tx2xShow(false);
|
||||||
break;
|
break;
|
||||||
#endif // USE_WEBSERVER
|
#endif // USE_WEBSERVER
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,9 @@
|
|||||||
Version yyyymmdd Action Description
|
Version yyyymmdd Action Description
|
||||||
--------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
0.9.3.0 20200222 integrate - use now the correct id-word instead of MAC-OUI,
|
||||||
|
add CGG1
|
||||||
|
---
|
||||||
0.9.2.0 20200212 integrate - "backports" from MI-HM10, change reading pattern,
|
0.9.2.0 20200212 integrate - "backports" from MI-HM10, change reading pattern,
|
||||||
add missing PDU-types, renaming driver
|
add missing PDU-types, renaming driver
|
||||||
---
|
---
|
||||||
@ -58,18 +61,21 @@
|
|||||||
#define MJ_HT_V1 2
|
#define MJ_HT_V1 2
|
||||||
#define LYWSD02 3
|
#define LYWSD02 3
|
||||||
#define LYWSD03 4
|
#define LYWSD03 4
|
||||||
|
#define CGG1 5
|
||||||
|
|
||||||
uint8_t kMINRFSlaveID[4][3] = { 0xC4,0x7C,0x8D, // Flora
|
const uint16_t kMINRFSlaveID[5]={ 0x0098, // Flora
|
||||||
0x58,0x2D,0x34, // MJ_HT_V1
|
0x01aa, // MJ_HT_V1
|
||||||
0xE7,0x2E,0x00, // LYWSD02
|
0x045b, // LYWSD02
|
||||||
0xA4,0xC1,0x38, // LYWSD03
|
0x055b, // LYWSD03
|
||||||
};
|
0x0347 // CGG1
|
||||||
|
};
|
||||||
|
|
||||||
const char kMINRFSlaveType1[] PROGMEM = "Flora";
|
const char kMINRFSlaveType1[] PROGMEM = "Flora";
|
||||||
const char kMINRFSlaveType2[] PROGMEM = "MJ_HT_V1";
|
const char kMINRFSlaveType2[] PROGMEM = "MJ_HT_V1";
|
||||||
const char kMINRFSlaveType3[] PROGMEM = "LYWSD02";
|
const char kMINRFSlaveType3[] PROGMEM = "LYWSD02";
|
||||||
const char kMINRFSlaveType4[] PROGMEM = "LYWSD03";
|
const char kMINRFSlaveType4[] PROGMEM = "LYWSD03";
|
||||||
const char * kMINRFSlaveType[] PROGMEM = {kMINRFSlaveType1,kMINRFSlaveType2,kMINRFSlaveType3,kMINRFSlaveType4};
|
const char kMINRFSlaveType5[] PROGMEM = "CGG1";
|
||||||
|
const char * kMINRFSlaveType[] PROGMEM = {kMINRFSlaveType1,kMINRFSlaveType2,kMINRFSlaveType3,kMINRFSlaveType4,kMINRFSlaveType5};
|
||||||
|
|
||||||
// PDU's or different channels 37-39
|
// PDU's or different channels 37-39
|
||||||
const uint32_t kMINRFFloPDU[3] = {0x3eaa857d,0xef3b8730,0x71da7b46};
|
const uint32_t kMINRFFloPDU[3] = {0x3eaa857d,0xef3b8730,0x71da7b46};
|
||||||
@ -77,10 +83,11 @@ const uint32_t kMINRFMJPDU[3] = {0x4760cd66,0xdbcc0cd3,0x33048df5};
|
|||||||
const uint32_t kMINRFL2PDU[3] = {0x3eaa057d,0xef3b0730,0x71da7646}; // 1 and 3 unsure
|
const uint32_t kMINRFL2PDU[3] = {0x3eaa057d,0xef3b0730,0x71da7646}; // 1 and 3 unsure
|
||||||
// const uint32_t kMINRFL3PDU[3] = {0x4760dd78,0xdbcc1ccd,0xffffffff}; //encrypted - 58 58
|
// const uint32_t kMINRFL3PDU[3] = {0x4760dd78,0xdbcc1ccd,0xffffffff}; //encrypted - 58 58
|
||||||
const uint32_t kMINRFL3PDU[3] = {0x4760cb78,0xdbcc0acd,0x33048beb}; //unencrypted - 30 58
|
const uint32_t kMINRFL3PDU[3] = {0x4760cb78,0xdbcc0acd,0x33048beb}; //unencrypted - 30 58
|
||||||
|
const uint32_t kMINRFCGPDU[3] = {0x4760cd6e,0xdbcc0cdb,0x33048dfd};
|
||||||
|
|
||||||
// start-LSFR for different channels 37-39
|
// start-LSFR for different channels 37-39
|
||||||
const uint8_t kMINRFlsfrList_A[3] = {0x4b,0x17,0x23}; // Flora, LYWSD02
|
const uint8_t kMINRFlsfrList_A[3] = {0x4b,0x17,0x23}; // Flora, LYWSD02
|
||||||
const uint8_t kMINRFlsfrList_B[3] = {0x21,0x72,0x43}; // MJ_HT_V1, LYWSD03
|
const uint8_t kMINRFlsfrList_B[3] = {0x21,0x72,0x43}; // MJ_HT_V1, LYWSD03, CGG1
|
||||||
|
|
||||||
|
|
||||||
#pragma pack(1) // important!!
|
#pragma pack(1) // important!!
|
||||||
@ -269,7 +276,7 @@ struct {
|
|||||||
} MINRF;
|
} MINRF;
|
||||||
|
|
||||||
struct mi_sensor_t{
|
struct mi_sensor_t{
|
||||||
uint8_t type; //Flora = 1; MJ_HT_V1=2; LYWSD02=3; LYWSD03=4
|
uint8_t type; //Flora = 1; MJ_HT_V1=2; LYWSD02=3; LYWSD03=4; ; CGG1=5
|
||||||
uint8_t serial[6];
|
uint8_t serial[6];
|
||||||
uint8_t showedUp;
|
uint8_t showedUp;
|
||||||
float temp; //Flora, MJ_HT_V1, LYWSD0x
|
float temp; //Flora, MJ_HT_V1, LYWSD0x
|
||||||
@ -362,6 +369,9 @@ bool MINRFreceivePacket(void)
|
|||||||
case 4:
|
case 4:
|
||||||
MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); // "LYWSD03" mode
|
MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); // "LYWSD03" mode
|
||||||
break;
|
break;
|
||||||
|
case 5:
|
||||||
|
MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); // "CGG1" mode
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// DEBUG_SENSOR_LOG(PSTR("MINRF: LSFR:%x"),_lsfr);
|
// DEBUG_SENSOR_LOG(PSTR("MINRF: LSFR:%x"),_lsfr);
|
||||||
// if (_lsfr>254) _lsfr=0;
|
// if (_lsfr>254) _lsfr=0;
|
||||||
@ -470,6 +480,9 @@ void MINRFchangePacketModeTo(uint8_t _mode) {
|
|||||||
if(kMINRFL3PDU[_nextchannel]==0xffffffff) break;
|
if(kMINRFL3PDU[_nextchannel]==0xffffffff) break;
|
||||||
NRF24radio.openReadingPipe(0,kMINRFL3PDU[_nextchannel]);// 95 fe 58 30 -> LYWSD03 (= no data message)
|
NRF24radio.openReadingPipe(0,kMINRFL3PDU[_nextchannel]);// 95 fe 58 30 -> LYWSD03 (= no data message)
|
||||||
break;
|
break;
|
||||||
|
case 5: // special CGG1 packet
|
||||||
|
NRF24radio.openReadingPipe(0,kMINRFCGPDU[_nextchannel]); // 95 fe 50 30 -> CGG1
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// DEBUG_SENSOR_LOG(PSTR("MINRF: Change Mode to %u"),_mode);
|
// DEBUG_SENSOR_LOG(PSTR("MINRF: Change Mode to %u"),_mode);
|
||||||
MINRF.packetMode = _mode;
|
MINRF.packetMode = _mode;
|
||||||
@ -479,24 +492,25 @@ void MINRFchangePacketModeTo(uint8_t _mode) {
|
|||||||
* @brief Return the slot number of a known sensor or return create new sensor slot
|
* @brief Return the slot number of a known sensor or return create new sensor slot
|
||||||
*
|
*
|
||||||
* @param _serial BLE address of the sensor
|
* @param _serial BLE address of the sensor
|
||||||
* @param _type Type number of the sensor, 0xff for Auto-type
|
* @param _type Type number of the sensor
|
||||||
* @return uint32_t Known or new slot in the sensors-vector
|
* @return uint32_t Known or new slot in the sensors-vector
|
||||||
*/
|
*/
|
||||||
uint32_t MINRFgetSensorSlot(uint8_t (&_serial)[6], uint8_t _type){
|
uint32_t MINRFgetSensorSlot(uint8_t (&_serial)[6], uint16_t _type){
|
||||||
if(_type==0xff){
|
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: will test MAC-type"));
|
DEBUG_SENSOR_LOG(PSTR("MINRF: will test ID-type: %x"), _type);
|
||||||
for (uint32_t i=0;i<4;i++){
|
bool _success = false;
|
||||||
if(memcmp(_serial,kMINRFSlaveID+i,3)==0){
|
for (uint32_t i=0;i<5;i++){
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: MAC is type %u"), i);
|
if(_type == kMINRFSlaveID[i]){
|
||||||
_type = i+1;
|
DEBUG_SENSOR_LOG(PSTR("MINRF: ID is type %u"), i);
|
||||||
}
|
_type = i+1;
|
||||||
else {
|
_success = true;
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: MAC-type is unknown"));
|
}
|
||||||
}
|
else {
|
||||||
|
DEBUG_SENSOR_LOG(PSTR("MINRF: ID-type is not: %x"),kMINRFSlaveID[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(_type==0xff) return _type; // error
|
if(!_success) return 0xff;
|
||||||
|
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: vector size %u"), MIBLEsensors.size());
|
DEBUG_SENSOR_LOG(PSTR("MINRF: vector size %u"), MIBLEsensors.size());
|
||||||
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
|
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
|
||||||
if(memcmp(_serial,MIBLEsensors.at(i).serial,sizeof(_serial))==0){
|
if(memcmp(_serial,MIBLEsensors.at(i).serial,sizeof(_serial))==0){
|
||||||
@ -550,13 +564,13 @@ void MINRFpurgeFakeSensors(void){
|
|||||||
|
|
||||||
|
|
||||||
void MINRFhandleFloraPacket(void){
|
void MINRFhandleFloraPacket(void){
|
||||||
if(MINRF.buffer.floraPacket.T.idWord!=0x9800 && MINRF.buffer.floraPacket.T.valueTen!=0x10){
|
if(MINRF.buffer.floraPacket.T.valueTen!=0x10){
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected Flora packet"));
|
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected Flora packet"));
|
||||||
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MINRFreverseMAC(MINRF.buffer.floraPacket.T.serial);
|
MINRFreverseMAC(MINRF.buffer.floraPacket.T.serial);
|
||||||
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.floraPacket.T.serial, 0xff); // T is not specific, any struct would be possible to use
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.floraPacket.T.serial, MINRF.buffer.floraPacket.T.idWord); // T is not specific, any struct would be possible to use
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
||||||
if(_slot==0xff) return;
|
if(_slot==0xff) return;
|
||||||
|
|
||||||
@ -593,13 +607,13 @@ void MINRFhandleFloraPacket(void){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MINRFhandleMJ_HT_V1Packet(void){
|
void MINRFhandleMJ_HT_V1Packet(void){
|
||||||
if(MINRF.buffer.MJ_HT_V1Packet.TH.idWord != 0xaa01 && MINRF.buffer.MJ_HT_V1Packet.TH.valueTen!=0x10){
|
if(MINRF.buffer.MJ_HT_V1Packet.TH.valueTen!=0x10){
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected MJ_HT_V1-packet"));
|
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected MJ_HT_V1-packet"));
|
||||||
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MINRFreverseMAC(MINRF.buffer.MJ_HT_V1Packet.TH.serial);
|
MINRFreverseMAC(MINRF.buffer.MJ_HT_V1Packet.TH.serial);
|
||||||
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.MJ_HT_V1Packet.TH.serial, 0xff); // B would be possible too
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.MJ_HT_V1Packet.TH.serial, MINRF.buffer.MJ_HT_V1Packet.TH.idWord); // B would be possible too
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
||||||
if(_slot==0xff) return;
|
if(_slot==0xff) return;
|
||||||
|
|
||||||
@ -635,9 +649,9 @@ void MINRFhandleLYWSD02Packet(void){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MINRFreverseMAC(MINRF.buffer.LYWSD02Packet.TH.serial);
|
MINRFreverseMAC(MINRF.buffer.LYWSD02Packet.TH.serial);
|
||||||
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.LYWSD02Packet.TH.serial, 0xff); // H would be possible too
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.LYWSD02Packet.TH.serial, MINRF.buffer.LYWSD02Packet.TH.idWord); // H would be possible too
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
||||||
if(_slot==0xff) return;
|
if(_slot==0xff) return;
|
||||||
|
|
||||||
static float _tempFloat;
|
static float _tempFloat;
|
||||||
switch(MINRF.buffer.LYWSD02Packet.TH.mode) { // we can use any struct with a mode, they are all same at this point
|
switch(MINRF.buffer.LYWSD02Packet.TH.mode) { // we can use any struct with a mode, they are all same at this point
|
||||||
@ -661,7 +675,7 @@ void MINRFhandleLYWSD02Packet(void){
|
|||||||
void MINRFhandleLYWSD03Packet(void){
|
void MINRFhandleLYWSD03Packet(void){
|
||||||
// not much to do ATM, just show the sensor without data
|
// not much to do ATM, just show the sensor without data
|
||||||
MINRFreverseMAC(MINRF.buffer.LYWSD02Packet.TH.serial); //the beginning is equal to the LYWSD02-packet
|
MINRFreverseMAC(MINRF.buffer.LYWSD02Packet.TH.serial); //the beginning is equal to the LYWSD02-packet
|
||||||
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.LYWSD02Packet.TH.serial, 0xff);
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.LYWSD02Packet.TH.serial, MINRF.buffer.LYWSD02Packet.TH.idWord);
|
||||||
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
||||||
if(_slot==0xff) return;
|
if(_slot==0xff) return;
|
||||||
|
|
||||||
@ -670,6 +684,46 @@ void MINRFhandleLYWSD03Packet(void){
|
|||||||
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MINRFhandleCGG1Packet(void){ // we assume, that the packet structure is equal to the MJ_HT_V1
|
||||||
|
if(MINRF.buffer.MJ_HT_V1Packet.TH.valueTen!=0x10){
|
||||||
|
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected CGG1-packet"));
|
||||||
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MINRFreverseMAC(MINRF.buffer.MJ_HT_V1Packet.TH.serial);
|
||||||
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.MJ_HT_V1Packet.TH.serial, MINRF.buffer.MJ_HT_V1Packet.TH.idWord); // B would be possible too
|
||||||
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
||||||
|
if(_slot==0xff) return;
|
||||||
|
|
||||||
|
static float _tempFloat;
|
||||||
|
switch(MINRF.buffer.MJ_HT_V1Packet.TH.mode) { // we can use any struct with a mode, they are all same at this point
|
||||||
|
case 0x0d:
|
||||||
|
_tempFloat=(float)(MINRF.buffer.MJ_HT_V1Packet.TH.temp)/10.0f;
|
||||||
|
if(_tempFloat<60){
|
||||||
|
MIBLEsensors.at(_slot).temp = _tempFloat;
|
||||||
|
DEBUG_SENSOR_LOG(PSTR("CGG1: temp updated"));
|
||||||
|
}
|
||||||
|
_tempFloat=(float)(MINRF.buffer.MJ_HT_V1Packet.TH.hum)/10.0f;
|
||||||
|
if(_tempFloat<100){
|
||||||
|
MIBLEsensors.at(_slot).hum = _tempFloat;
|
||||||
|
DEBUG_SENSOR_LOG(PSTR("CGG1: hum updated"));
|
||||||
|
}
|
||||||
|
DEBUG_SENSOR_LOG(PSTR("CGG1 mode:0x0d: U16: %x Temp U16: %x Hum"), MINRF.buffer.MJ_HT_V1Packet.TH.temp, MINRF.buffer.MJ_HT_V1Packet.TH.hum);
|
||||||
|
break;
|
||||||
|
case 0x0a:
|
||||||
|
if(MINRF.buffer.MJ_HT_V1Packet.B.battery<101){
|
||||||
|
MIBLEsensors.at(_slot).bat = MINRF.buffer.MJ_HT_V1Packet.B.battery;
|
||||||
|
DEBUG_SENSOR_LOG(PSTR("CGG1: bat updated"));
|
||||||
|
}
|
||||||
|
DEBUG_SENSOR_LOG(PSTR("CGG1 mode:0x0a: U8: %x %%"), MINRF.buffer.MJ_HT_V1Packet.B.battery);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Main loop of the driver
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
void MINRF_EVERY_50_MSECOND() { // Every 50mseconds
|
void MINRF_EVERY_50_MSECOND() { // Every 50mseconds
|
||||||
|
|
||||||
if(MINRF.timer>6000){ // happens every 6000/20 = 300 seconds
|
if(MINRF.timer>6000){ // happens every 6000/20 = 300 seconds
|
||||||
@ -712,15 +766,16 @@ void MINRF_EVERY_50_MSECOND() { // Every 50mseconds
|
|||||||
else if (MINRF.packetMode == LYWSD03){
|
else if (MINRF.packetMode == LYWSD03){
|
||||||
MINRFhandleLYWSD03Packet();
|
MINRFhandleLYWSD03Packet();
|
||||||
}
|
}
|
||||||
|
else if (MINRF.packetMode == CGG1){
|
||||||
// DEBUG_SENSOR_LOG(PSTR("MINRF: Change packet mode every 50 msec"));
|
MINRFhandleCGG1Packet();
|
||||||
if (MINRF.packetMode == LYWSD03){
|
}
|
||||||
|
if (MINRF.packetMode == CGG1){
|
||||||
MINRFinitBLE(1); // no real ble packets in release mode, otherwise for developing use 0
|
MINRFinitBLE(1); // no real ble packets in release mode, otherwise for developing use 0
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
MINRFinitBLE(++MINRF.packetMode);
|
MINRFinitBLE(++MINRF.packetMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
MINRFhopChannel();
|
MINRFhopChannel();
|
||||||
NRF24radio.startListening();
|
NRF24radio.startListening();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user