From c0a5156d8f2b04079b5cb746d1f63f8be7bf00d1 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Mon, 24 Feb 2020 07:19:15 -0600 Subject: [PATCH 01/13] Initial pwm dimmer support --- BUILDS.md | 7 + I2CDEVICES.md | 1 + MODULES.md | 1 + PWM_Dimmer.md | 197 ++++ RELEASENOTES.md | 3 +- TEMPLATES.md | 1 + platformio.ini | 1 + platformio_override_sample.ini | 11 +- platformio_tasmota_env.ini | 1 + tasmota/CHANGELOG.md | 3 + tasmota/i18n.h | 8 + tasmota/my_user_config.h | 4 +- tasmota/settings.h | 14 + tasmota/settings.ino | 42 +- tasmota/support_features.ino | 5 +- tasmota/support_tasmota.ino | 6 + tasmota/tasmota_post.h | 10 + tasmota/tasmota_template.h | 31 + tasmota/xdrv_01_webserver.ino | 11 + tasmota/xdrv_04_light.ino | 11 +- tasmota/xdrv_07_domoticz.ino | 6 +- tasmota/xdrv_12_home_assistant.ino | 8 +- tasmota/xdrv_23_zigbee_1_headers.ino | 2 +- tasmota/xdrv_23_zigbee_3_devices.ino | 31 +- tasmota/xdrv_23_zigbee_5_converters.ino | 12 +- tasmota/xdrv_23_zigbee_6_commands.ino | 265 ++++- tasmota/xdrv_23_zigbee_7_statemachine.ino | 1 + tasmota/xdrv_23_zigbee_8_parsers.ino | 53 +- tasmota/xdrv_23_zigbee_9_impl.ino | 123 +-- tasmota/xdrv_27_shutter.ino | 109 +- tasmota/xdrv_35_pwm_dimmer.ino | 1141 +++++++++++++++++++++ tasmota/xsns_06_dht_v5.ino | 17 + tasmota/xsns_61_MI_NRF24.ino | 121 ++- tools/decode-status.py | 4 +- 34 files changed, 2037 insertions(+), 224 deletions(-) create mode 100644 PWM_Dimmer.md create mode 100644 tasmota/xdrv_35_pwm_dimmer.ino diff --git a/BUILDS.md b/BUILDS.md index aa1834061..88ee58cd0 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -62,6 +62,7 @@ | USE_DDS2382 | - | - | - | - | x | - | - | | USE_DDSU666 | - | - | - | - | x | - | - | | USE_SOLAX_X1 | - | - | - | - | - | - | - | +| USE_LE01MR | - | - | - | - | - | - | - | | | | | | | | | | | USE_ADC_VCC | x | x | - | - | - | - | - | | USE_COUNTER | - | - | x | x | x | x | x | @@ -109,6 +110,8 @@ | USE_HIH6 | - | - | - | - | x | - | - | | USE_DHT12 | - | - | - | - | x | - | - | | USE_DS1624 | - | - | - | - | x | - | - | +| USE_AHT1x | - | - | - | - | - | - | - | +| USE_WEMOS_MOTOR_V1 | - | - | - | - | x | - | - | | | | | | | | | | | Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks | USE_SPI | - | - | - | - | - | - | x | @@ -124,6 +127,10 @@ | USE_RDM6300 | - | - | - | - | x | - | - | | USE_IBEACON | - | - | - | - | x | - | - | | USE_GPS | - | - | - | - | - | - | - | +| USE_HM10 | - | - | - | - | x | - | - | +| | | | | | | | | +| USE_NRF24 | - | - | - | - | - | - | - | +| USE_MIBLE | - | - | - | - | - | - | - | | USE_ZIGBEE | - | - | - | - | - | - | - | | | | | | | | | | | USE_IR_REMOTE | - | - | x | x | x | x | x | diff --git a/I2CDEVICES.md b/I2CDEVICES.md index b607b3241..f9cf5b0e3 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -65,3 +65,4 @@ Index | Define | Driver | Device | Address(es) | Description 42 | USE_DS1624 | xsns_59 | DS1621 | 0x48 - 0x4F | Temperature sensor 42 | USE_DS1624 | xsns_59 | DS1624 | 0x48 - 0x4F | Temperature sensor 43 | USE_AHT1x | xsns_63 | AHT10/15 | 0x38 | Temperature and humidity sensor + 44 | USE_WEMOS_MOTOR_V1 | xdrv_34 | | 0x2D - 0x30 | WEMOS motor shield v1.0.0 (6612FNG) diff --git a/MODULES.md b/MODULES.md index bb02bfe73..be1d5d110 100644 --- a/MODULES.md +++ b/MODULES.md @@ -76,5 +76,6 @@ Module | Description 70 Sonoff L1 | Sonoff L1 light strip 71 Sonoff iFan03 | Sonoff iFan03 Wifi Smart Ceiling Fan with Light 72 EXS Dimmer | EXS Wifi Dimmer v4 +73 PWM Dimmer | Martin Jerry/acenx/Tessan/NTONPOWER SD0x PWM Dimmer Switches Over 600 additional devices are supported using [templates](TEMPLATES.md). diff --git a/PWM_Dimmer.md b/PWM_Dimmer.md new file mode 100644 index 000000000..528b3f9fb --- /dev/null +++ b/PWM_Dimmer.md @@ -0,0 +1,197 @@ +# PWM Dimmer + +The PWM Dimmer module adds support for Martin Jerry/acenx/Tessan/NTONPOWER SD0x PWM dimmer switches. The brightness of the load for these dimmers is controlled by a PWM GPIO pin. They typically have power, up and down buttons, a powered-on LED, five brightness LEDs and another status LED. Examples are:[ https://www.amazon.com/dp/B07FXYSVR1](https://www.amazon.com/dp/B07FXYSVR1),[ https://www.amazon.com/dp/B07V26Q3VD](https://www.amazon.com/dp/B07V26Q3VD),[ https://www.amazon.com/dp/B07K67D43J](https://www.amazon.com/dp/B07K67D43J),[ https://www.amazon.com/dp/B07TTGFWFM](https://www.amazon.com/dp/B07TTGFWFM) + +To include PWM dimmer support in the build, define USE_PWM_DIMMER in your user_config_override. This adds 4.5K to the code size. The light module is not required for PWM dimmer operation so you can #undef USE_LIGHT to reduce the firmware bin size. + +To enable PWM dimmer operation, select the PWM Dimmer module. + + +## PWM Dimmer Operation + +Pressing and releasing the power button toggles the power on/off. If the toggle turns the power on, the load is returned to the last brightness it was adjusted to. If Fade is enabled, the load is faded on/off at the rate defined by the Speed setting. + +When the power is on, holding the down or up button decreases/increases the brightness (PWM value). The brightness is changed faster as higher brightnesses. The BriMin command defines the lowest value the brightness can be decreased to. + +The brightness can also be changed using just the power button. When the power is on, holding the power button alternately increases or decreases the brightness. Initially, holding the power button increases the brightness. Releasing and then holding the power button again decreases the brightness. + +When the power is off, holding the down or up button turns the power on at a temporary brightness of the low/high levels set by the BriPreset command (default =10,255). Turning the power on at the low preset can also be accomplished by holding the power button while the power is off. The brightness presets are intended to enable quickly turning on a light to a dim or bright level without changing the normal desired brightness. Turning the power on to a preset does not change the brightness the load will be set to when the switch is turned on the next time. For example, if the light is on and you adjust the brightness to 80 and then turn the light off, when you turn it back on, the brightness will return to 80. If you turn the power off again and then press the down button, the light will be turned on with a brightness of the low preset. If you then turn the light off and on again, the brightness will return to 80. + +If there are LED’s defined in the template, they are turned on to indicate the current brightness. More LEDs are turned on at higher brightnesses. The LedTimeout command enables/disables an LED timeout. If LED timeout is enabled, the LED’s turn off five seconds after the last change in brightness. Note that the lowest LED and the blue power LED are always on when the power is on. + +The LEDLink LED can be used as a nightlight/powered-off indicator. The PoweredOffLed command enables/disables turning the LEDLink LED on when the power is off. + +Tapping (pressing and releasing quickly) the down or up buttons a given number of times and then holding the down or up button decreases or increases settings according to the table below. For example, tapping the down button once and then holding the up button sets all RGB lights in the device group to the next fixed color. Tapping the up button three times and then holding the down button decreases the high brightness preset. + + + + + + + + + + + + + + + + + + + + + + + +
Taps + Down Button + Up Button +
1 + Set fixed color1 + Publish MQTT event2 +
2 + Adjust minimum brightness + Adjust fade speed +
3 + Adjust low brightness preset + Adjust high brightness preset +
+ + +1. Setting the previous/next color only functions when remote device mode is enabled (see below) and only when the switch is in a device group with an RGB light. The color sequence as defined by the Light module is red, green, blue, orange, light green, light blue, amber, cyan, purple, yellow, pink, white using RGB channels, white using CT channels. + +2. The MQTT topic has the format %group-topic%/cmnd/Event, where %group-topic% is the group topic set by the GroupTopic command. The MQTT payload is SwitchTrigger#, where # is 1 if the down button is held or 2 if the up button is held. These triggers can be used in rules on remote devices (ON Event#SwitchTrigger1) or by automation software to trigger automations such as scene changes. For example, the Event topic SwitchTrigger1 payload could trigger the automation software to turn on the previous scene in a list and the SwitchTrigger2 payload could trigger the automation software to turn on the next scene in a list. + +Holding the power button, pressing the down/up buttons a given number of times and then releasing the power button toggles options according to the table below. Note that you must press a down or up button within 0.5 seconds to prevent the power button hold action from taking place. + + + + + + + + + + + + + + + + + + +
Presses + Down Button + Up Button +
1 + Toggle powered-off LED + Toggle brightness LED timeout +
2 + Toggle fading + +
+ + +Holding any button for over 10 seconds executes the WiFiConfig 2 command. + +When Device Groups are enabled, the PWM Dimmer minimum brightness and brightness presets are kept in sync across all switches in the group. The powered-off LED and LED timeout settings are specific to each switch. Changing them does not replicate the change to the other switches in the group. + + +### Commands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Command + Parameters +
BriMin + 1..255 = set minimum brightness +

++ = increase minimum brightness +

+- = decrease minimum brightness +

BriPreset + ,<high> = set brightness low and high presets +

+1..255 = set brightness preset +

++ = increase brightness preset +

+- = decrease brightness preset +

Dimmer + 0..100 = set dimmer value from 0 to 100% +

++ = increase by 10 +

+- = decrease by 10 +

Fade + 0 = do not use fade (default) +

+1 = use fade +

LedTimeout + 0 = disable LED timeout +

+1 = enable LED timeout +

PoweredOffLed + 0 = disable powered-off LED +

+1 = disable powered-off LED +

Speed + 1..20 = set fade speed from fast 1 to very slow 20 +

++ = increase speed +

+- = decrease speed +

+ + + +### Remote Device Mode + +Remote device mode allows PWM Dimmer switches to control remote devices. With remote device mode enabled, each button controls a different device. Note that dimmer switches with toggle-style down/up buttons have limited functionality as remote device mode switches because you can not push the down and up buttons simultaneously. + +To include remote device mode support in the build, define USE_PWM_DIMMER_REMOTE in your user_config_override. Remote device mode support requires device group support so USE_DEVICE_GROUPS is automatically defined if USE_PWM_DIMMER_REMOTE is defined. Remote device mode support adds 0.7K to the code size in addition to the code size required for device groups support. + +To enable remote device mode, set Option71 to 1. Each remote device must be running firmware with device group support and have remote device support enabled. The remote devices do not need to be built with PWM dimmer support nor do they need to be switches. + +If a remote device is a PWM dimmer, the device acts like a 3-way dimmer switch would and may or may not have a load connected to it. It’s also possible to use a PWM dimmer switch without a load to act as a wall switch to control the power, brightness and color of one or more smart lights with Tasmota with device group support loaded on them. + +With remote device mode enabled, button 1 is the power button for the local device while buttons 2 and 3 are the power buttons for remote devices. Group names for buttons 2 and 3 are set by the GroupTopic2 and GroupTopic3 commands respectively. Note that the button numbers are defined by the module template and can be in any physical order on the switch (button 1 can be defined as the top button, the middle button or the bottom button). + +Pressing and releasing a power button toggles the power on all devices in the group assigned to the button. When the power is on, holding the button alternately increases or decreases the brightness. When the power is off, holding the button turns the power on at a temporary brightness of the low level set by the BriPreset command (default =10). + +While holding a power button, the other two buttons act like the down and up buttons for the remote device. All the functions performed by the down and up buttons in non-remote device mode are available in remote device mode. While holding button 1, button 2 performs the functions of the down button and button 3 performs the functions of the up button. While holding button 2, button 1 performs the functions of the down button and button 3 performs the functions of the up button. While holding button 3, button 1 performs the functions of the down button and button 2 performs the functions of the up button. diff --git a/RELEASENOTES.md b/RELEASENOTES.md index dba7abeed..e5e08674c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -103,4 +103,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for FiF LE-01MR energy meter by saper-2 (#7584) - Add new DHT driver. The old driver can still be used using define USE_DHT_OLD (#7468) - 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) \ No newline at end of file +- Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596) +- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764) diff --git a/TEMPLATES.md b/TEMPLATES.md index 2cb5b4773..d922a4174 100644 --- a/TEMPLATES.md +++ b/TEMPLATES.md @@ -130,6 +130,7 @@ Kingart Touch {"NAME":"PS-16-DZ","GPIO":[255,148,255,149,255,255,0,0,255 Moes DS01 {"NAME":"MOES - DS01","GPIO":[255,255,255,255,255,255,0,0,255,108,255,107,255],"FLAG":0,"BASE":54} Moes QS-WiFi-D01 Dimmer 150W {"NAME":"WiFi-Dimmer","GPIO":[0,148,0,149,0,0,0,0,0,42,37,0,0],"FLAG":0,"BASE":18} PS-16-DZ {"NAME":"PS-16-DZ","GPIO":[255,148,255,149,255,255,0,0,255,52,255,255,255],"FLAG":0,"BASE":58} +PWM Dimmer {"NAME":"PWM Dimmer","GPIO":[19,18,0,59,158,58,0,0,57,37,56,122,29],"FLAG":0,"BASE":73} Zemismart KS-7011 {"NAME":"KS-7011 Dimmer","GPIO":[255,107,255,108,255,255,0,0,255,255,255,255,255],"FLAG":0,"BASE":54} ``` diff --git a/platformio.ini b/platformio.ini index 16553f0d6..552408790 100755 --- a/platformio.ini +++ b/platformio.ini @@ -56,6 +56,7 @@ default_envs = framework = arduino board = esp01_1m board_build.flash_mode = dout +board_build.ldscript = eagle.flash.1m.ld platform = ${core_active.platform} platform_packages = ${core_active.platform_packages} diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index 93bbbb5ba..0c63c6e8f 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -135,10 +135,9 @@ build_flags = ${esp82xx_defaults.build_flags} [core_2_6_3] ; *** Esp8266 core for Arduino version 2.6.3 -platform = espressif8266@2.3.2 +platform = espressif8266@2.3.3 platform_packages = build_flags = ${esp82xx_defaults.build_flags} - -Wl,-Teagle.flash.1m.ld -DBEARSSL_SSL_BASIC ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 @@ -180,12 +179,10 @@ build_flags = ${esp82xx_defaults.build_flags} [tasmota_core_stage] ; *** 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 build_flags = ${esp82xx_defaults.build_flags} - -Wl,-Teagle.flash.1m.ld -DBEARSSL_SSL_BASIC - -DNOPRINTFLOAT ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 ; NONOSDK22x_190313 @@ -226,12 +223,10 @@ build_flags = ${esp82xx_defaults.build_flags} [core_stage] ; *** 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 -board_build.ldscript = eagle.flash.1m.ld build_flags = ${esp82xx_defaults.build_flags} -DBEARSSL_SSL_BASIC - -DNOPRINTFLOAT ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 ; NONOSDK22x_190313 diff --git a/platformio_tasmota_env.ini b/platformio_tasmota_env.ini index 2a26ba4d2..cfcd53686 100644 --- a/platformio_tasmota_env.ini +++ b/platformio_tasmota_env.ini @@ -3,6 +3,7 @@ platform = ${common.platform} platform_packages = ${common.platform_packages} framework = ${common.framework} board = ${common.board} +board_build.ldscript = ${common.board_build.ldscript} board_build.flash_mode = ${common.board_build.flash_mode} board_build.f_cpu = ${common.board_build.f_cpu} build_unflags = ${common.build_unflags} diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 2689a7222..ef9ce17f4 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -4,6 +4,9 @@ - Revert most wifi connectivity changes introduced in 8.1.0.5 (#7746, #7602, #7621) - Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596) +- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764) +- Fix Zigbee auto-increment transaction number (#7757) +- Add Zigbee enhanced commands decoding, added ``ZbPing`` ### 8.1.0.8 20200212 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index cb9bb1196..630da2f6f 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -532,6 +532,14 @@ // Commands xdrv_32_hotplug.ino #define D_CMND_HOTPLUG "HotPlug" +// Commands xdrv_34_pwm_dimmer.ino +#ifdef USE_PWM_DIMMER +#define D_CMND_BRI_MIN "BriMin" +#define D_CMND_BRI_PRESET "BriPreset" +#define D_CMND_LED_TIMEOUT "LedTimeout" +#define D_CMND_POWERED_OFF_LED "PoweredOffLed" +#endif + // Commands xsns_02_analog.ino #define D_CMND_ADCPARAM "AdcParam" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 57e10c955..0d43b9489 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -415,7 +415,9 @@ //#define USE_EXS_DIMMER // Add support for ES-Store WiFi Dimmer (+1k5 code) // #define EXS_MCU_CMNDS // Add command to send MCU commands (+0k8 code) //#define USE_HOTPLUG // Add support for sensor HotPlug -// #define USE_DEVICE_GROUPS // Add support for device groups (+4k code) +// #define USE_DEVICE_GROUPS // Add support for device groups (+3k5 code) +// #define USE_PWM_DIMMER // Add support for MJ-SD01/acenx/NTONPOWER PWM dimmers (+4k5 code) +// #define USE_PWM_DIMMER_REMOTE // Add support for remote switches to PWM Dimmer, also adds device groups support (+0k7 code, also includes device groups) // -- Optional light modules ---------------------- #define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by // diff --git a/tasmota/settings.h b/tasmota/settings.h index 972af6974..88862f090 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -578,4 +578,18 @@ typedef union { #define device_group_share_in domoticz_sensor_idx[0] // Bitmask of device group items imported #define device_group_share_out domoticz_sensor_idx[1] // Bitmask of device group items exported +// Settings re-purposed for the PWM_DIMMER module +#ifdef USE_PWM_DIMMER +#define led_timeout light_signal // SetOption18 - Turn brightness LED's off 5 seconds after last change +#define powered_off_led buzzer_enable // SetOption67 - Turn red LED on when powered off +#define bri_power_on pcf8574_config[0] // Brightness when next powered-on +#define bri_min pcf8574_config[1] // Minimum brightness +#define bri_preset_low pcf8574_config[2] // Bri preset low +#define bri_preset_high pcf8574_config[3] // Bri preset high +#define button_devices pcf8574_config[4] // Button-device map +#ifdef USE_PWM_DIMMER_REMOTE +#define remote_device_mode dds2382_model // SetOption71 - Buttons control remote devices +#endif // USE_PWM_DIMMER_REMOTE +#endif // USE_PWM_DIMMER + #endif // _SETTINGS_H_ diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 33da93562..c2ddc6607 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -277,6 +277,23 @@ bool RtcRebootValid(void) /*********************************************************************************************\ * 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" { @@ -745,8 +762,17 @@ void SettingsErase(uint8_t type) _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 } - - AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " %d " D_UNIT_SECTORS), _sectorEnd - _sectorStart); +#ifdef USE_WIFI_SDK_ERASE + else if (4 == type) { + _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) + } +#endif // USE_WIFI_SDK_ERASE + 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 EsptoolErase(_sectorStart, _sectorEnd); // Esptool - erases flash completely @@ -755,11 +781,21 @@ void SettingsErase(uint8_t type) void SettingsSdkErase(void) { - WiFi.disconnect(true); // Delete SDK wifi config + WiFi.disconnect(false); // Delete SDK wifi config SettingsErase(1); delay(1000); } +#ifdef USE_WIFI_SDK_ERASE +void SettingsSdkWifiErase(void) +{ + WiFi.disconnect(false); // Delete SDK wifi config + SettingsErase(4); + SettingsErase(5); + delay(200); +} +#endif // USE_WIFI_SDK_ERASE + /********************************************************************************************/ void SettingsDefault(void) diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index ba1a2e465..b6101b1cf 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -516,7 +516,10 @@ void GetFeatures(void) #ifdef USE_AHT1x feature5 |= 0x10000000; // xsns_63_aht1x.ino #endif -// feature5 |= 0x20000000; +#ifdef USE_WEMOS_MOTOR_V1 + feature5 |= 0x20000000; // xdrv_34_wemos_motor_v1.ino +#endif + // feature5 |= 0x40000000; // feature5 |= 0x80000000; diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index b4d699cb6..ad9b38165 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -640,6 +640,12 @@ void MqttShowState(void) break; } #endif // USE_SONOFF_IFAN +#ifdef USE_PWM_DIMMER + if (PWM_DIMMER == my_module_type) { + ResponseAppend_P(PSTR(",\"" D_CMND_DIMMER "\":%d,\"" D_CMND_FADE "\":\"%s\",\"" D_CMND_SPEED "\":%d"), + Settings.light_dimmer, GetStateText(Settings.light_fade), Settings.light_speed); + } +#endif // USE_PWM_DIMMER #ifdef USE_LIGHT } #endif diff --git a/tasmota/tasmota_post.h b/tasmota/tasmota_post.h index fc94e5167..a8ff65967 100644 --- a/tasmota/tasmota_post.h +++ b/tasmota/tasmota_post.h @@ -708,6 +708,16 @@ const char kDeviceGroupMessage[] PROGMEM = DEVICE_GROUP_MESSAGE; uint8_t device_group_count = 1; #endif // USE_DEVICE_GROUPS +#ifdef USE_PWM_DIMMER_REMOTE +#ifdef USE_PWM_DIMMER +#ifndef USE_DEVICE_GROUPS +#define USE_DEVICE_GROUPS +#endif // USE_DEVICE_GROUPS +#else // USE_PWM_DIMMER +#undef USE_PWM_DIMMER_REMOTE +#endif // USE_PWM_DIMMER +#endif // USE_PWM_DIMMER_REMOTE + #ifdef DEBUG_TASMOTA_CORE #define DEBUG_CORE_LOG(...) AddLog_Debug(__VA_ARGS__) #else diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h index c4ee7b19d..6f5aa0f8c 100644 --- a/tasmota/tasmota_template.h +++ b/tasmota/tasmota_template.h @@ -418,6 +418,7 @@ enum SupportedModules { SONOFF_L1, SONOFF_IFAN03, EXS_DIMMER, + PWM_DIMMER, MAXMODULE}; #define USER_MODULE 255 @@ -864,6 +865,9 @@ const uint8_t kModuleNiceList[] PROGMEM = { #endif #ifdef USE_EXS_DIMMER EXS_DIMMER, +#endif +#ifdef USE_PWM_DIMMER + PWM_DIMMER, #endif H801, // Light Devices MAGICHOME, @@ -2204,6 +2208,33 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { GPIO_USER, // GPIO14 0, // GPIO15 0, 0 + }, + { "PWM Dimmer", // PWM_DIMMER - Support for Martin Jerry/acenx/Tessan/NTONPOWER SD0x PWM + // dimmer switches. The brightness of the load for these dimmers is + // controlled by a PWM GPIO pin. There are typically power, up & down + // buttons and 4 LED's. Examples are: + // https://www.amazon.com/dp/B07FXYSVR1 + // https://www.amazon.com/dp/B07V26Q3VD + // https://www.amazon.com/dp/B07K67D43J + // https://www.amazon.com/dp/B07TTGFWFM + GPIO_KEY3, // GPIO00 Up button + GPIO_KEY2, // GPIO01 Down button + 0, // GPIO02 + GPIO_LED4_INV, // GPIO03 Level 5 LED + GPIO_LEDLNK_INV, // GPIO04 LED Link + GPIO_LED3_INV, // GPIO05 Level 4 LED + // GPIO06 (SD_CLK Flash) + // GPIO07 (SD_DATA0 Flash QIO/DIO/DOUT) + // GPIO08 (SD_DATA1 Flash QIO/DIO/DOUT) + 0, // GPIO09 (SD_DATA2 Flash QIO or ESP8285) + 0, // GPIO10 (SD_DATA3 Flash QIO or ESP8285) + // GPIO11 (SD_CMD Flash) + GPIO_LED2_INV, // GPIO12 Level 3 LED + GPIO_PWM1, // GPIO13 Dimmer PWM + GPIO_LED1_INV, // GPIO12 Level 2 LED + GPIO_KEY1_INV, // GPIO15 Power button + GPIO_REL1_INV, // GPIO16 Power relay/Level 1 LED + 0 } }; diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 3eaa24ef5..377799a72 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -1123,6 +1123,17 @@ void HandleRoot(void) } // Settings.flag3.pwm_multi_channels } #endif // USE_LIGHT +#ifdef USE_PWM_DIMMER + if (PWM_DIMMER == my_module_type) { + WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Brightness - Black to White + "c", // c - Unique HTML id + "#000", "#fff", // Black to White + 4, // sl4 - Unique range HTML id - Used as source for Saturation begin color + Settings.flag3.slider_dimmer_stay_on, 100, // Range 0/1 to 100% + Settings.light_dimmer, + 'd', 0); // d0 - Value id is related to lc("d0", value) and WebGetArg("d0", tmp, sizeof(tmp)); + } +#endif // USE_PWM_DIMMER #ifdef USE_SHUTTER if (Settings.flag3.shutter_mode) { // SetOption80 - Enable shutter support for (uint32_t i = 0; i < shutters_present; i++) { diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index baaf2b7a7..d54598077 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -2588,6 +2588,8 @@ void CmndRgbwwTable(void) ResponseCmndChar(scolor); } +#endif // USE_LIGHT +#if defined(USE_LIGHT) || defined(USE_PWM_DIMMER) void CmndFade(void) { // Fade - Show current Fade state @@ -2606,7 +2608,9 @@ void CmndFade(void) #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 +#ifdef USE_LIGHT if (!Settings.light_fade) { Light.fade_running = false; } +#endif // USE_LIGHT ResponseCmndStateText(Settings.light_fade); } @@ -2632,6 +2636,8 @@ void CmndSpeed(void) } ResponseCmndNumber(Settings.light_speed); } +#endif // #if defined(USE_LIGHT) || defined(USE_PWM_DIMMER) +#ifdef USE_LIGHT void CmndWakeupDuration(void) { @@ -2664,7 +2670,10 @@ bool Xdrv04(uint8_t function) bool result = false; if (FUNC_MODULE_INIT == function) { - return LightModuleInit(); +#ifdef USE_PWM_DIMMER + if (PWM_DIMMER != my_module_type) +#endif // USE_PWM_DIMMER + return LightModuleInit(); } else if (light_type) { switch (function) { diff --git a/tasmota/xdrv_07_domoticz.ino b/tasmota/xdrv_07_domoticz.ino index 49e546b65..90563eeed 100644 --- a/tasmota/xdrv_07_domoticz.ino +++ b/tasmota/xdrv_07_domoticz.ino @@ -296,6 +296,7 @@ bool DomoticzMqttData(void) found = true; } else #endif // USE_SHUTTER +#ifdef USE_LIGHT 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 JsonObject& color = domoticz["Color"]; @@ -333,8 +334,9 @@ bool DomoticzMqttData(void) snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_DIMMER)); snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue); found = true; - } - else if (1 == nvalue || 0 == nvalue) { + } else +#endif // USE_LIGHT + if (1 == nvalue || 0 == nvalue) { if (((power >> i) &1) == (power_t)nvalue) { return true; // Stop loop } diff --git a/tasmota/xdrv_12_home_assistant.ino b/tasmota/xdrv_12_home_assistant.ino index 7396694af..fde47a623 100644 --- a/tasmota/xdrv_12_home_assistant.ino +++ b/tasmota/xdrv_12_home_assistant.ino @@ -200,8 +200,8 @@ void HAssAnnounceRelayLight(void) TryResponseAppend_P(HASS_DISCOVER_RELAY, command_topic, value_template, SettingsText(SET_STATE_TXT1), SettingsText(SET_STATE_TXT2)); TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str()); -#ifdef USE_LIGHT - if (is_light) +#if defined(USE_LIGHT) || defined(USE_PWM_DIMMER) + if (is_light || PWM_DIMMER == my_module_type) { char *brightness_command_topic = stemp1; @@ -209,6 +209,7 @@ void HAssAnnounceRelayLight(void) strncpy_P(stemp3, Settings.flag.not_power_linked ? PSTR("last") : PSTR("brightness"), sizeof(stemp3)); // SetOption20 - Control power in relation to Dimmer/Color/Ct changes TryResponseAppend_P(HASS_DISCOVER_LIGHT_DIMMER, brightness_command_topic, state_topic, stemp3); +#ifdef USE_LIGHT if (Light.subtype >= LST_RGB) { char *rgb_command_topic = stemp1; @@ -234,8 +235,9 @@ void HAssAnnounceRelayLight(void) GetTopic_P(color_temp_command_topic, CMND, mqtt_topic, D_CMND_COLORTEMPERATURE); TryResponseAppend_P(HASS_DISCOVER_LIGHT_CT, color_temp_command_topic, state_topic); } +#endif // USE_LIGHT } -#endif // USE_LIGHT +#endif // defined(USE_LIGHT) || defined(USE_PWM_DIMMER) TryResponseAppend_P(PSTR("}")); } MqttPublish(stopic, true); diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index 101d5703d..0bc592266 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -21,7 +21,7 @@ // 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 diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino index 4814bdb8a..71752f4e3 100644 --- a/tasmota/xdrv_23_zigbee_3_devices.ino +++ b/tasmota/xdrv_23_zigbee_3_devices.ino @@ -19,6 +19,10 @@ #ifdef USE_ZIGBEE +#ifndef ZIGBEERECEIVED +#define ZIGBEERECEIVED 1 +#endif + #include #include @@ -49,6 +53,8 @@ typedef struct Z_Device { // json buffer used for attribute reporting DynamicJsonBuffer *json_buffer; JsonObject *json; + // sequence number for Zigbee frames + uint8_t seqNumber; } Z_Device; // All devices are stored in a Vector @@ -96,6 +102,9 @@ public: // device just seen on the network, update the lastSeen field void updateLastSeen(uint16_t shortaddr); + // get next sequence number for (increment at each all) + uint8_t getNextSeqNumber(uint16_t shortaddr); + // Dump json String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const; @@ -133,6 +142,7 @@ public: private: std::vector _devices = {}; uint32_t _saveTimer = 0; + uint8_t _seqNumber = 0; // global seqNumber if device is unknown template < typename T> static bool findInVector(const std::vector & vecOfElements, const T & element); @@ -226,7 +236,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { std::vector(), 0,0,0,0, nullptr, - nullptr, nullptr }; + nullptr, nullptr, + 0, // seqNumber + }; device.json_buffer = new DynamicJsonBuffer(); _devices.push_back(device); dirty(); @@ -532,6 +544,19 @@ void Z_Devices::updateLastSeen(uint16_t shortaddr) { _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 // // 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()); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); +#if ZIGBEERECEIVED // DEPRECATED TODO Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str()); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); +#endif } else { Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); +#if ZIGBEERECEIVED // DEPRECATED TODO Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); +#endif } // MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); // XdrvRulesProcess(); diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index d97e5e7d4..4debd73fd 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -486,18 +486,8 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) { // 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) { - uint32_t i = offset; - 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; + convertClusterSpecific(json, _cluster_id, _cmd_id, _frame_control.b.direction, _payload); } // return value: diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index 073ab8e4c..5c3e3fc1b 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -19,34 +19,81 @@ #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 { 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; +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 const Z_CommandConverter Z_Commands[] = { - { "Power", "0006!xx" }, // 0=Off, 1=On, 2=Toggle - { "Dimmer", "0008!04/xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid) - { "Dimmer+", "0008!06/001902" }, // Step up by 10%, 0.2 secs - { "Dimmer-", "0008!06/011902" }, // Step down by 10%, 0.2 secs - { "DimmerStop", "0008!03" }, // Stop any Dimmer animation - { "ResetAlarm", "0009!00/xxyyyy" }, // Reset alarm (alarm code + cluster identifier) - { "ResetAllAlarms","0009!01" }, // Reset all alarms - { "Hue", "0300!00/xx000A00" }, // Move to Hue, shortest time, 1s - { "Sat", "0300!03/xx0A00" }, // Move to Sat - { "HueSat", "0300!06/xxyy0A00" }, // Hue, Sat - { "Color", "0300!07/xxxxyyyy0A00" }, // x, y (uint16) - { "CT", "0300!0A/xxxx0A00" }, // Color Temperature Mireds (uint16) - { "Shutter", "0102!xx" }, - { "ShutterOpen", "0102!00" }, - { "ShutterClose", "0102!01" }, - { "ShutterStop", "0102!02" }, - { "ShutterLift", "0102!05xx" }, // Lift percentage, 0%=open, 100%=closed - { "ShutterTilt", "0102!08xx" }, // Tilt percentage + // Group adress commands + { "AddGroup", 0x0004, 0x00, 0x01, "xxxx00" }, // Add group id, group name is not supported + { "ViewGroup", 0x0004, 0x01, 0x01, "xxxx" }, // Ask for the group name + { "GetGroup", 0x0004, 0x02, 0x01, "01xxxx" }, // Get one group membership + { "GetAllGroups", 0x0004, 0x02, 0x01, "00" }, // Get all groups membership + { "RemoveGroup", 0x0004, 0x03, 0x01, "xxxx" }, // Remove one group + { "RemoveAllGroups",0x0004, 0x04, 0x01, "" }, // Remove all groups + // Light & Shutter commands + { "Power", 0x0006, 0xFF, 0x01, "" }, // 0=Off, 1=On, 2=Toggle + { "Dimmer", 0x0008, 0x04, 0x01, "xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid) + { "Dimmer+", 0x0008, 0x06, 0x01, "001902" }, // Step up by 10%, 0.2 secs + { "Dimmer-", 0x0008, 0x06, 0x01, "011902" }, // Step down by 10%, 0.2 secs + { "DimmerStop", 0x0008, 0x03, 0x01, "" }, // Stop any Dimmer animation + { "ResetAlarm", 0x0009, 0x00, 0x01, "xxyyyy" }, // Reset alarm (alarm code + cluster identifier) + { "ResetAllAlarms", 0x0009, 0x01, 0x01, "" }, // Reset all alarms + { "Hue", 0x0300, 0x00, 0x01, "xx000A00" }, // Move to Hue, shortest time, 1s + { "Sat", 0x0300, 0x03, 0x01, "xx0A00" }, // Move to Sat + { "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 // 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_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) { size_t attrs_len = 0; const uint8_t* attrs = nullptr; @@ -78,11 +126,10 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoi break; } 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 void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) { 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) { - char parm_uc[16]; // used to convert JSON keys to uppercase +// returns true if char is 'x', 'y' or 'z' +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" = "!": "" for commands to devices + // Format: "0004<00": "00" = "<": "" 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++) { const Z_CommandConverter *conv = &Z_Commands[i]; 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; } -inline bool isXYZ(char c) { - return (c >= 'x') && (c <= 'z'); -} - // take the lower 4 bits and turn it to an hex char inline char hexDigit(uint32_t h) { uint32_t nybble = h & 0x0F; diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index 5a3d78e85..d0616a40b 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -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_SIMPLE_DESC = 33; // Simple Descriptor (clusters) const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; // Device announces its address +const uint8_t ZIGBEE_STATUS_DEVICE_IEEE = 35; // Request of device address const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 668f85471..b67add374 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -176,15 +176,24 @@ int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) { return -1; } +// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address +void Z_SendIEEEAddrReq(uint16_t shortaddr) { + uint8_t IEEEAddrReq[] = { Z_SREQ | Z_ZDO, ZDO_IEEE_ADDR_REQ, + Z_B0(shortaddr), Z_B1(shortaddr), 0x00, 0x00 }; + + ZigbeeZNPSend(IEEEAddrReq, sizeof(IEEEAddrReq)); +} + // Send ACTIVE_EP_REQ to collect active endpoints for this address void Z_SendActiveEpReq(uint16_t shortaddr) { uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) }; - 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)); + + // 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 } @@ -335,6 +344,40 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) { 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) { Z_ShortAddress srcAddr = buf.get16(2); 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()); zcl_received.postProcessAttributes(srcaddr, json); + // Add Endpoint + json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint; // Add 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_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585 ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584 +ZBM(AREQ_ZDO_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) // 4581 const Z_Dispatcher Z_DispatchTable[] PROGMEM = { { AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage }, @@ -491,6 +537,7 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = { { AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc }, { AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp }, { AREQ_ZDO_SIMPLEDESCRSP, &Z_ReceiveSimpleDesc }, + { AREQ_ZDO_IEEE_ADDR_RSP, &Z_ReceiveIEEEAddr }, }; int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 6ea08c4ac..08055ba9e 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -33,19 +33,22 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" - D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND ; + D_CMND_ZIGBEE_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 D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" - D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND ; + D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|" + D_CMND_ZIGBEE_PING ; void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZbZNPSend, &CmndZbPermitJoin, &CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive, - &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind + &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind, + &CmndZbPing, }; 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))); } -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); buf.add8(Z_SREQ | Z_AF); // 24 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(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(cmdId); if (len > 0) { @@ -367,61 +370,16 @@ void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8 ZigbeeZNPSend(buf.getBuffer(), buf.len()); } -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; -} - -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); +void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, bool clusterSpecific, + uint16_t cluster, uint8_t cmd, const char *param) { + size_t size = param ? strlen(param) : 0; SBuffer buf((size+2)/2); // actual bytes buffer for data - while (*data) { - uint8_t code = parseHex(&data, 2); - buf.add8(code); + if (param) { + while (*param) { + uint8_t code = parseHex_P(¶m, 2); + buf.add8(code); + } } 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: 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) { 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 - 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 if (clusterSpecific) { zigbeeSetCommandTimer(dstAddr, cluster, endpoint); @@ -467,8 +425,12 @@ void CmndZbSend(void) { static char delim[] = ", "; // delimiters for parameters uint16_t device = 0xFFFF; // 0xFFFF is broadcast, so considered valid 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 + // parse JSON const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); if (nullptr != &val_device) { device = zigbee_devices.parseDeviceParam(val_device.as()); @@ -496,8 +458,9 @@ void CmndZbSend(void) { String key = it->key; JsonVariant& value = it->value; 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) { cmd_str = tasmota_cmd; } 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 - 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 { // 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 } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"), - device, endpoint, cmd_str.c_str()); - zigbeeZCLSendStr(device, endpoint, cmd_str.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%04X!%02X/%s\"}"), + device, endpoint, cluster, cmd, cmd_str.c_str()); + zigbeeZCLSendStr(device, endpoint, true, cluster, cmd, cmd_str.c_str()); } else { Response_P(PSTR("Missing zigbee 'Send'")); return; @@ -614,16 +584,28 @@ void CmndZbBind(void) { // Probe a specific device to get its endpoints and supported clusters void CmndZbProbe(void) { + CmndZbProbeOrPing(true); +} + +void CmndZbProbeOrPing(boolean probe) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } // everything is good, we can send the command - Z_SendActiveEpReq(shortaddr); + Z_SendIEEEAddrReq(shortaddr); + if (probe) { + Z_SendActiveEpReq(shortaddr); + } ResponseCmndDone(); } +// Ping a device, actually a simplified version of ZbProbe +void CmndZbPing(void) { + CmndZbProbeOrPing(false); +} + // Specify, read or erase a Friendly Name void CmndZbName(void) { // 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)) { - 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(); } else { ResponseCmndChar("Missing parameters"); diff --git a/tasmota/xdrv_27_shutter.ino b/tasmota/xdrv_27_shutter.ino index c353a1dd4..6f4d711d7 100644 --- a/tasmota/xdrv_27_shutter.ino +++ b/tasmota/xdrv_27_shutter.ino @@ -48,7 +48,7 @@ void (* const ShutterCommand[])(void) PROGMEM = { &CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay, &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}"; #include @@ -70,7 +70,7 @@ struct SHUTTER { 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 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_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 @@ -82,17 +82,18 @@ void ShutterLogPos(uint32_t i) char stemp2[10]; 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"), - 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) { - for (uint32_t i = 0; i < shutters_present; i++) { + for (uint8_t i = 0; i < shutters_present; i++) { Shutter.time[i]++; if (Shutter.accelerator[i]) { - Shutter.pwm_frequency += 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)); - analogWriteFreq(Shutter.pwm_frequency); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: accelerator i=%d -> %d"),i, Shutter.accelerator[i]); + Shutter.pwm_frequency[i] += Shutter.accelerator[i]; + 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); } } @@ -102,8 +103,6 @@ void ShutterRtc50mS(void) 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) { return (percent <= 5) ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index]; } else { @@ -138,8 +137,6 @@ int32_t ShutterPercentToRealPosition(uint32_t percent, 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) { 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 { @@ -203,8 +200,9 @@ void ShutterInit(void) Shutter.mode = SHT_OFF_ON__OPEN_CLOSE; if ((pin[GPIO_PWM1+i] < 99) && (pin[GPIO_CNTR1+i] < 99)) { Shutter.mode = SHT_OFF_ON__OPEN_CLOSE_STEPPER; - Shutter.pwm_frequency = 0; - analogWriteFreq(Shutter.pwm_frequency); + Shutter.pwm_frequency[i] = 0; + Shutter.accelerator[i] = 0; + analogWriteFreq(Shutter.pwm_frequency[i]); analogWrite(pin[GPIO_PWM1+i], 50); } } @@ -256,29 +254,24 @@ void ShutterInit(void) void ShutterReportPosition(bool always) { - uint32_t shutter_moving = 0; Response_P(PSTR("{")); 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]); uint32_t position = ShutterRealToPercentPosition(Shutter.real_position[i], i); if (Shutter.direction[i] != 0) { - shutter_moving = 1; + rules_flag.shutter_moving = 1; ShutterLogPos(i); } 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(); - if (always || (1 == shutter_moving)) { + if (always || (rules_flag.shutter_moving)) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER)); + XdrvRulesProcess(); } - if (rules_flag.shutter_moving > shutter_moving) { - rules_flag.shutter_moved = 1; - } else { - rules_flag.shutter_moved = 0; - } - rules_flag.shutter_moving = shutter_moving; - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved); + } 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_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 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 ; - 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); //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, - 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] ) { 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]); - } 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; } } else { @@ -343,13 +336,13 @@ void ShutterUpdatePosition(void) 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]; //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.pwm_frequency = Shutter.pwm_frequency > 250 ? 250 : Shutter.pwm_frequency; - analogWriteFreq(Shutter.pwm_frequency); + Shutter.pwm_frequency[i] = Shutter.pwm_frequency[i] > 250 ? 250 : Shutter.pwm_frequency[i]; + analogWriteFreq(Shutter.pwm_frequency[i]); analogWrite(pin[GPIO_PWM1+i], 50); - Shutter.pwm_frequency = 0; - analogWriteFreq(Shutter.pwm_frequency); + Shutter.pwm_frequency[i] = 0; + 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) { delay(1); } @@ -391,6 +384,7 @@ void ShutterUpdatePosition(void) Shutter.direction[i] = 0; ShutterReportPosition(true); + rules_flag.shutter_moved = 1; XdrvRulesProcess(); } } @@ -413,8 +407,8 @@ void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos) Shutter.skip_relay_change = 1; } else { if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) { - Shutter.pwm_frequency = 0; - analogWriteFreq(Shutter.pwm_frequency); + Shutter.pwm_frequency[i] = 0; + analogWriteFreq(Shutter.pwm_frequency[i]); analogWrite(pin[GPIO_PWM1+i], 0); // can be operated without counter, but then not that acurate. 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_STEPPER == Shutter.mode) { //AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Frequency change %d"), Shutter.pwm_frequency); - while (Shutter.pwm_frequency > 0) { - Shutter.accelerator[i] = 0; - 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); - analogWriteFreq(Shutter.pwm_frequency); + while (Shutter.pwm_frequency[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[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); + //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); delay(50); } @@ -532,7 +527,6 @@ void ShutterButtonHandler(void) uint8_t shutter_index = Settings.shutter_button[button_index] & 0x03; uint16_t loops_per_second = 1000 / Settings.button_debounce; // ButtonDebounce (50) - 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 buttonState = SHT_PRESSED_MULTI; @@ -542,9 +536,9 @@ void ShutterButtonHandler(void) buttonState = SHT_PRESSED_IMMEDIATE; press_index = 1; 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.window_timer[button_index] = loops_per_second / 2; // 0.5 second multi press window + } } blinks = 201; } @@ -596,14 +590,18 @@ void ShutterButtonHandler(void) // check for simultaneous shutter button press uint32 min_shutterbutton_press_counter = -1; 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]; + } + } if (min_shutterbutton_press_counter == Button.press_counter[button_index]) { // simultaneous shutter button press detected + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT:simultanous presss deteced")); press_index = Button.press_counter[button_index]; 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 buttonState = SHT_PRESSED_MULTI_SIMULTANEOUS; } @@ -628,7 +626,6 @@ void ShutterButtonHandler(void) return; } } else if (buttonState == SHT_PRESSED_EXT_HOLD_SIMULTANEOUS) { - // simultaneous shutter button extend hold detected if (!Settings.flag.button_restrict) { // no SetOption1 (0) char scmnd[20]; snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); @@ -649,8 +646,7 @@ void ShutterButtonHandler(void) if (buttonState == SHT_PRESSED_IMMEDIATE) { XdrvMailbox.payload = XdrvMailbox.index; CmndShutterStop(); - } - else { + } else { uint8_t position = (Settings.shutter_button[button_index]>>(6*pos_press_index + 2)) & 0x03f; if (position) { if (Shutter.direction[shutter_index]) { @@ -670,12 +666,12 @@ void ShutterButtonHandler(void) Response_P("%d", position); 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("{")); 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 ); // 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)) { //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))) { @@ -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. target_pos_percent = ((Settings.shutter_options[index] & 1) && (SRC_WEBGUI != last_source)) ? 100 - target_pos_percent : target_pos_percent; if (XdrvMailbox.payload != -99) { //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.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]; 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); } diff --git a/tasmota/xdrv_35_pwm_dimmer.ino b/tasmota/xdrv_35_pwm_dimmer.ino new file mode 100644 index 000000000..ea3211563 --- /dev/null +++ b/tasmota/xdrv_35_pwm_dimmer.ino @@ -0,0 +1,1141 @@ +/* + xdrv_35_pwm_dimmer.ino - PWM Dimmer Switch support for Tasmota + + Copyright (C) 2020 Paul C Diem + + 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 . +*/ + +#ifdef USE_PWM_DIMMER + +/*********************************************************************************************\ +* Support for Martin Jerry/acenx/Tessan/NTONPOWER SD0x PWM dimmer switches. The brightness of +* the load for these dimmers is controlled by a PWM GPIO pin. Examples are: +* +* https://www.amazon.com/dp/B07FXYSVR1 +* https://www.amazon.com/dp/B07V26Q3VD +* https://www.amazon.com/dp/B07K67D43J +* https://www.amazon.com/dp/B07TTGFWFM +* +\*********************************************************************************************/ + +#define XDRV_35 35 + +#ifndef MAX_FIXED_COLOR +#define MAX_FIXED_COLOR 12 +#endif // MAX_FIXED_COLOR + +const char kPWMDimmerCommands[] PROGMEM = "|" // No prefix + D_CMND_BRI_MIN "|" D_CMND_BRI_PRESET "|" D_CMND_DIMMER "|" D_CMND_FADE "|" D_CMND_LED_TIMEOUT "|" + D_CMND_POWERED_OFF_LED "|" D_CMND_SPEED; + +void (* const PWMDimmerCommand[])(void) PROGMEM = { + &CmndBriMin, &CmndBriPreset, &PWMDmmerCmndDimmer, &CmndFade, &CmndLedTimeout, + &CmndPoweredOffLed, &CmndSpeed }; + +#ifdef USE_PWM_DIMMER_REMOTE +struct remote_pwm_dimmer { + power_t power; + uint8_t light_speed; + uint8_t x_bri_power_on; + uint8_t x_bri_min; + uint8_t x_bri_preset_low; + uint8_t x_bri_preset_high; + uint8_t fixed_color_index; + uint8_t bri; + bool power_button_increases_bri; + bool light_fade; +}; +#endif // USE_PWM_DIMMER_REMOTE + +uint32_t led_timeout_time = 0; +uint32_t turn_off_brightness_leds_time = 0; +uint32_t button_hold_time[3]; +uint8_t current_bri; +uint8_t target_bri; +uint8_t restore_powered_off_led = 0; +uint8_t power_button_index = 0; +uint8_t down_button_index = 1; +uint8_t up_button_index = 2; +uint8_t fixed_color_index; +uint8_t button_press_count[3] = { 0, 0, 0 }; +bool relay_is_on = false; +bool ignore_power_button_hold; +bool ignore_power_button_release; +bool button_was_held = false; +bool power_button_increases_bri = true; +bool invert_power_button_bri_direction = false; +bool button_hold_sent[3]; +bool button_pressed[3] = { false, false, false }; +#ifdef USE_PWM_DIMMER_REMOTE +struct remote_pwm_dimmer * remote_pwm_dimmers; +struct device_group * active_device_group; +struct remote_pwm_dimmer * active_remote_pwm_dimmer; +uint8_t buttons_pressed = 0; +bool active_device_is_local; +#endif // USE_PWM_DIMMER_REMOTE + +void PWMDimmerCheckBri(uint8_t * value_ptr) +{ + if (*value_ptr < Settings.bri_min) *value_ptr = Settings.bri_min; +} + +void PWMModuleInit() +{ + Settings.seriallog_level = 0; + Settings.flag.mqtt_serial = 0; // Disable serial logging + Settings.ledstate = 0; // Disable LED usage + Settings.flag.pwm_control = 0; // Use basic PWM control instead of Light + + if (Settings.last_module != Settings.module) { + Settings.bri_min = 8; + Settings.bri_power_on = 50; + Settings.bri_preset_low = 8; + Settings.bri_preset_high = 255; + Settings.last_module = Settings.module; + } + else { + if (Settings.bri_min < 1) Settings.bri_min = 8; + PWMDimmerCheckBri(&Settings.bri_power_on); + PWMDimmerCheckBri(&Settings.bri_preset_low); + PWMDimmerCheckBri(&Settings.bri_preset_high); + } + + if (Settings.light_speed < 1) Settings.light_speed = 1; + + target_bri = ((Settings.power & 1) ? changeUIntScale(Settings.light_dimmer, 0, 100, 0, 255) : 0); + PWMDimmerCheckBri(&target_bri); + current_bri = target_bri; + + if (pin[GPIO_PWM1] < 99) { + uint32_t pwm_value = changeUIntScale(current_bri, 0, 255, 0, Settings.pwm_range); + analogWrite(pin[GPIO_PWM1], bitRead(pwm_inverted, 0) ? Settings.pwm_range - pwm_value : pwm_value); + } + + relay_is_on = (current_bri > 0); + if (pin[GPIO_REL1] < 99) DigitalWrite(GPIO_REL1, bitRead(rel_inverted, 0) ? !relay_is_on : relay_is_on); + + PWMDimmerSetPoweredOffLed(); + PWMDimmerSetBrightnessLeds(0); +} + +void PWMDimmerInit(void) +{ +#ifdef USE_PWM_DIMMER_REMOTE + if (Settings.flag3.remote_device_mode) { + if (device_group_count > 1) { + if ((remote_pwm_dimmers = (struct remote_pwm_dimmer *) calloc(device_group_count - 1, sizeof(struct remote_pwm_dimmer))) == nullptr) { + AddLog_P2(LOG_LEVEL_ERROR, "PWMDimmer: error allocating PWM dimmer array"); + Settings.flag3.remote_device_mode = false; + } + } + } + + active_device_group = &device_groups[0]; + active_device_is_local = true; +#endif // USE_PWM_DIMMER_REMOTE +} + +// operation: 0 = normal, -1 = all off, 1 = all on +void PWMDimmerSetBrightnessLeds(int32_t operation) +{ + if (leds_present) { + uint32_t step = (!operation ? (255 - Settings.bri_min) / (leds_present + 1) : operation < 0 ? 255 : 0); + uint32_t level = step; + SetLedPowerIdx(0, current_bri >= level); + if (leds_present > 1) { + level += step; + SetLedPowerIdx(1, current_bri >= level); + if (leds_present > 2) { + level += step; + SetLedPowerIdx(2, current_bri >= level); + if (leds_present > 3) { + level += step; + SetLedPowerIdx(3, current_bri >= level); + } + } + } + + // If enabled, set the LED timeout. + if (!operation) led_timeout_time = (current_bri && Settings.flag.led_timeout ? millis() + 5000 : 0); + } +} + +void PWMDimmerSetPoweredOffLed(void) +{ + // Set the powered-off LED state. + if (pin[GPIO_LEDLNK] < 99) { + bool power_off_led_on = !power && Settings.flag3.powered_off_led; + if (ledlnk_inverted) power_off_led_on ^= 1; + digitalWrite(pin[GPIO_LEDLNK], power_off_led_on); + } +} + +void PWMDimmerAnimate(bool no_fade) +{ + // We're only here if something changed. If this is no longer the case, uncomment the following + // line. + //if (current_bri == target_bri) return; + + // Advance the current brightness towards the target. + if (!no_fade && Settings.light_fade) { + if (current_bri < Settings.bri_min) { + current_bri = Settings.bri_min; + } + else { + uint8_t offset = current_bri / Settings.light_speed / 5 + 1; + uint8_t max_offset = abs(target_bri - current_bri); + if (offset > max_offset) offset = max_offset; + if (current_bri < target_bri) + current_bri += offset; + else + current_bri -= offset; + } + } + else { + current_bri = target_bri; + } + + // If the current brightness is now < bri_min, set the current and target brightness to 0. + if (current_bri < Settings.bri_min) current_bri = target_bri = 0; + + // Sset the new PWM value. + if (pin[GPIO_PWM1] < 99) { + uint32_t pwm_value = changeUIntScale(current_bri, 0, 255, 0, Settings.pwm_range); + analogWrite(pin[GPIO_PWM1], bitRead(pwm_inverted, 0) ? Settings.pwm_range - pwm_value : pwm_value); + } + + // Handle a power state change. + if (relay_is_on != (current_bri > 0)) { + bool power_is_on = ((power & 1) != 0); + if (power_is_on == relay_is_on) { +AddLog_P2(LOG_LEVEL_DEBUG, "****************** ExecuteCommandPower(1, %d, SRC_SWITCH)", (relay_is_on ? 0 : 1)); + ExecuteCommandPower(1, (relay_is_on ? POWER_OFF : POWER_ON), SRC_SWITCH); + } + + relay_is_on = !relay_is_on; + if (pin[GPIO_REL1] < 99) DigitalWrite(GPIO_REL1, bitRead(rel_inverted, 0) ? !relay_is_on : relay_is_on); + + // Set the powered-off LED. + PWMDimmerSetPoweredOffLed(); + } + + // Set the brightness LED's. + PWMDimmerSetBrightnessLeds(0); +} + +void PWMDimmerSetBri(uint8_t bri) +{ + if (bri == target_bri) return; + target_bri = bri; + if (target_bri && target_bri < Settings.bri_min) target_bri = Settings.bri_min; + Settings.bri_power_on = target_bri; + Settings.light_dimmer = changeUIntScale(target_bri, 0, 255, 0, 100); + if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT + MqttPublishTeleState(); + } + PWMDimmerAnimate(false); +} + +#ifdef USE_DEVICE_GROUPS +void PWMDimmerHandleDeviceGroupRequest() +{ + static bool send_state = false; + uint8_t value = XdrvMailbox.payload; + switch (XdrvMailbox.command_code) { + case DGR_ITEM_BRI_MIN: + Settings.bri_min = 1; + case DGR_ITEM_BRI_PRESET_LOW: + case DGR_ITEM_BRI_PRESET_HIGH: + case DGR_ITEM_BRI_POWER_ON: + PWMDimmerCheckBri(&value); + break; + case DGR_ITEM_LIGHT_BRI: + if (value) PWMDimmerCheckBri(&value); + break; + } + + switch (XdrvMailbox.command_code) { + case DGR_ITEM_EOL: + if (send_state && !(XdrvMailbox.index & DGR_FLAG_MORE_TO_COME)) { + 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 (target_bri != value) { + PWMDimmerSetBri(value); + 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_BRI_MIN: + if (Settings.bri_min != value) { + Settings.bri_min = value; + send_state = true; + } + break; + case DGR_ITEM_BRI_PRESET_LOW: + if (Settings.bri_preset_low != value) { + Settings.bri_preset_low = value; + send_state = true; + } + break; + case DGR_ITEM_BRI_PRESET_HIGH: + if (Settings.bri_preset_high != value) { + Settings.bri_preset_high = value; + send_state = true; + } + break; + case DGR_ITEM_BRI_POWER_ON: + if (Settings.bri_power_on != value) { + Settings.bri_power_on = value; + send_state = true; + } + break; + case DGR_ITEM_STATUS: + SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade, + DGR_ITEM_LIGHT_SPEED, Settings.light_speed, DGR_ITEM_LIGHT_BRI, target_bri, + DGR_ITEM_BRI_MIN, Settings.bri_min, DGR_ITEM_BRI_PRESET_LOW, Settings.bri_preset_low, + DGR_ITEM_BRI_PRESET_HIGH, Settings.bri_preset_high, DGR_ITEM_BRI_POWER_ON, Settings.bri_power_on); + break; + } +} +#endif // USE_DEVICE_GROUPS + +void PWMDimmerHandleButton() +{ + /* + * Power Button Up/Down Buttons State Remote Mode Action + * -------------------- ---------------------- ----- ------------ ---------------------------- + * Press & release Released Any Any Toggle power + * Hold for hold time Released On Any Brighter/dimmer while held + * then reverse direction + * Hold for hold time Released Off Any Power on at bri preset low + * Hold while Press & release up Any Any Toggle/change options + * Hold while Press & release down Any Any Toggle/change options + * Hold while Press up On Yes Brighter + * Hold while Press down On Yes Dimmer + * Released Press up On No Brigther + * Released Press down On No Dimmer + * Released Press & release up Off No Power on at bri preset low + * Released Press & release down Off No Power on at bri preset high + * + * Holding any button for over 10 seconds executes the WiFiConfig 2 command. + * + * In remote mode, whichever button is pressed first becomes the power button and any buttons + * pressed while it is held affect the device associated with it. The up and down buttons change + * depeneding on which button is the current power button: + * + * Power Down Up + * ----- ---- -- + * 1 2 3 + * 2 1 3 + * 3 1 2 + */ + uint32_t button_index = XdrvMailbox.index; + uint32_t now = millis(); + bool toggle_power = false; + uint8_t dgr_item = 0; + uint8_t dgr_value; + + // If the button is pressed, ... + if (!XdrvMailbox.payload) { + int8_t bri_direction = 0; + + // If the button was just pressed, reset the press count if it was released for longer than 1 + // second, flag the button as pressed, clear the hold sent flag and increment the buttons + // pressed count. + if (!button_pressed[button_index]) { + if (now > button_hold_time[button_index] && now - button_hold_time[button_index] > 1000) button_press_count[button_index] = 0; + button_pressed[button_index] = true; + button_hold_sent[button_index] = false; +#ifdef USE_PWM_DIMMER_REMOTE + buttons_pressed++; + + // If there are no other buttons pressed right now and remote mode is enabled, make the device + // associated with this button the device we're going to control. + if (buttons_pressed == 1 && Settings.flag3.remote_device_mode) { + power_button_index = button_index; + up_button_index = (button_index == 2 ? 1 : 2); + down_button_index = (button_index ? 0 : 1); + active_device_group = &device_groups[power_button_index]; + active_device_is_local = active_device_group->local; + if (!active_device_is_local) active_remote_pwm_dimmer = &remote_pwm_dimmers[power_button_index - 1]; + } + if (button_index == power_button_index) { +#else // USE_PWM_DIMMER_REMOTE + // If this is about the power button, initialize some variables. + if (!button_index) { +#endif // USE_PWM_DIMMER_REMOTE + button_hold_time[button_index] = now + 500; + ignore_power_button_hold = false; + ignore_power_button_release = false; + return; + } + + // If this is not about the power button, load the new hold time. Note that the hold time for + // the power button is longer than the hold time for the other buttons. + button_hold_time[button_index] = now + 250; + } + + // If the button is being held, send a button hold. + else if (button_hold_time[button_index] < now) { + if (!button_hold_sent[button_index]) { + button_hold_sent[button_index] = true; + SendKey(KEY_BUTTON, button_index + 1, POWER_HOLD); + } + + // If the button has been held for over 10 seconds, execute the WiFiConfig 2 command. + if (now - button_hold_time[button_index] > 10000) { + char scmnd[20]; + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_WIFICONFIG " 2")); + ExecuteCommand(scmnd, SRC_BUTTON); + return; + } + } + + // If this is about the power button, ... +#ifdef USE_PWM_DIMMER_REMOTE + if (button_index == power_button_index) { +#else // USE_PWM_DIMMER_REMOTE + if (!button_index) { +#endif // USE_PWM_DIMMER_REMOTE + + // If the power button has been held with no other buttons pressed, ... + if (!ignore_power_button_hold && button_hold_time[button_index] < now) { + ignore_power_button_release = true; + + // If the device power is on, adjust the brightness. Set the direction based on the current + // direction for the device and then invert the direction when the power button is released. + // The new brightness will be calculated below. +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local ? active_remote_pwm_dimmer->power : power) { + bri_direction = (!active_device_is_local ? (active_remote_pwm_dimmer->power_button_increases_bri ? 1 : -1) : (power_button_increases_bri ? 1 : -1)); +#else // USE_PWM_DIMMER_REMOTE + if (power) { + bri_direction = (power_button_increases_bri ? 1 : -1); +#endif // USE_PWM_DIMMER_REMOTE + invert_power_button_bri_direction = true; + } + + // If the power is not on, turn it on using an initial brightness of bri_preset_low and set + // the power button hold dimmer direction to true so holding the power switch increases the + // brightness. + else { +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) { + active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->x_bri_preset_low; + active_remote_pwm_dimmer->power_button_increases_bri = true; + } + else { +#endif // USE_PWM_DIMMER_REMOTE + target_bri = Settings.bri_preset_low; + power_button_increases_bri = true; +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + toggle_power = true; + + // Reset the power button hold time to delay before we start increasing the brightness. + button_hold_time[button_index] = now + 500; + } + } + } + + // If this is about the down or up buttons, ... + else { + + // If the power button is also pressed, set flags to ignore the power button being held and + // the next power button release. + if (button_pressed[power_button_index]) { + ignore_power_button_release = ignore_power_button_hold = true; + } + + // If the active device is local or the power button if also pressed, then if the down/up + // button has been held, handle the action based on the number of times the down or up button + // was pressed and released before holding it. +#ifdef USE_PWM_DIMMER_REMOTE + if (active_device_is_local == !button_pressed[power_button_index]) { +#endif // USE_PWM_DIMMER_REMOTE + if (button_hold_time[button_index] < now) { + + uint8_t uint8_value; + uint8_t min_uint8_value; + bool down_button_is_held = (button_index == down_button_index); + bool down_button_was_tapped = (button_press_count[down_button_index] > 0); + uint8_t tap_count = (down_button_was_tapped ? button_press_count[down_button_index] : button_press_count[up_button_index]); + switch (tap_count) { + case 0: + // If the power is on, adjust the brightness. Set the direction based on which button is + // pressed. The new brightness will be calculated below. +#ifdef USE_PWM_DIMMER_REMOTE + if ((!active_device_is_local ? active_remote_pwm_dimmer->power : power)) { +#else // USE_PWM_DIMMER_REMOTE + if (power) { +#endif // USE_PWM_DIMMER_REMOTE + bri_direction = (down_button_is_held ? -1 : 1); + } + + // If the power is not on, turn it on using a temporary brightness of bri_preset_low if + // the down button is pressed or bri_preset_low if the up button is pressed. + else { +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + active_remote_pwm_dimmer->bri = (down_button_is_held ? active_remote_pwm_dimmer->x_bri_preset_low : active_remote_pwm_dimmer->x_bri_preset_high); + else +#endif // USE_PWM_DIMMER_REMOTE + target_bri = (down_button_is_held ? Settings.bri_preset_low : Settings.bri_preset_high); + toggle_power = true; + button_hold_time[button_index] = now + 1000; + + // If the power button is also pressed, set the power button hold dimmer direction + // so holding the power switch adjusts the brightness away from the brightness we + // just set. +#ifdef USE_PWM_DIMMER_REMOTE + if (button_pressed[power_button_index]) active_remote_pwm_dimmer->power_button_increases_bri = down_button_is_held; +#endif // USE_PWM_DIMMER_REMOTE + } + break; + + case 1: + if (down_button_was_tapped) { + // Select the previous/next color. +#ifdef USE_DEVICE_GROUPS +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + uint8_value = active_remote_pwm_dimmer->fixed_color_index; + else +#endif // USE_PWM_DIMMER_REMOTE + uint8_value = fixed_color_index; + if (down_button_is_held) { + if (uint8_value) + uint8_value--; + else + uint8_value = MAX_FIXED_COLOR; + } + else { + if (uint8_value < MAX_FIXED_COLOR) + uint8_value++; + else + uint8_value = 0; + } +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + active_remote_pwm_dimmer->fixed_color_index = uint8_value; + else +#endif // USE_PWM_DIMMER_REMOTE + fixed_color_index = uint8_value; + dgr_item = DGR_ITEM_LIGHT_FIXED_COLOR; + dgr_value = uint8_value; +#endif // USE_DEVICE_GROUPS + ; + } + else { + // Publish MQTT Event SwitchTrigger#. + char topic[TOPSZ]; +#ifdef USE_PWM_DIMMER_REMOTE + snprintf(topic, sizeof(topic), "%s/cmnd/Event", active_device_group->group_name); +#else // USE_PWM_DIMMER_REMOTE + snprintf(topic, sizeof(topic), "%s/cmnd/Event", SettingsText(SET_MQTT_GRP_TOPIC)); +#endif // USE_PWM_DIMMER_REMOTE + sprintf(mqtt_data, "SwitchTrigger%u", (down_button_is_held ? 1 : 2)); + MqttPublish(topic); + } + break; + + case 2: + if (down_button_was_tapped) { + // Decrease/increase the minimum brightness. +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + uint8_value = active_remote_pwm_dimmer->x_bri_min; + else +#endif // USE_PWM_DIMMER_REMOTE + uint8_value = Settings.bri_min; + if (down_button_is_held) { + if (uint8_value > 4) uint8_value--; + } + else if (uint8_value < 255) { + uint8_value++; + } +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) { + active_remote_pwm_dimmer->x_bri_min = uint8_value; + if (active_remote_pwm_dimmer->x_bri_power_on < uint8_value) active_remote_pwm_dimmer->x_bri_power_on = uint8_value; + } + else { +#endif // USE_PWM_DIMMER_REMOTE + Settings.bri_min = uint8_value; + if (Settings.bri_power_on < uint8_value) Settings.bri_power_on = uint8_value; +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + dgr_item = DGR_ITEM_BRI_MIN; + dgr_value = uint8_value; + } + else { + // Decrease/increase the fade speed. +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + uint8_value = active_remote_pwm_dimmer->light_speed; + else +#endif // USE_PWM_DIMMER_REMOTE + uint8_value = Settings.light_speed; + if (down_button_is_held) { + if (uint8_value > 1) uint8_value--; + } + else if (uint8_value < 40) { + uint8_value++; + } +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + active_remote_pwm_dimmer->light_speed = uint8_value; + else +#endif // USE_PWM_DIMMER_REMOTE + Settings.light_speed = uint8_value; + dgr_item = DGR_ITEM_LIGHT_SPEED; + dgr_value = uint8_value; + } + break; + + case 3: + if (down_button_was_tapped) { + // Decrease/increase the low brightness preset. +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) { + uint8_value = active_remote_pwm_dimmer->x_bri_preset_low; + min_uint8_value =active_remote_pwm_dimmer->x_bri_min; + } + else { +#endif // USE_PWM_DIMMER_REMOTE + uint8_value = Settings.bri_preset_low; + min_uint8_value = Settings.bri_min; +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + if (down_button_is_held) { + if (uint8_value > min_uint8_value) uint8_value--; + } + else if (uint8_value < 255) { + uint8_value++; + } +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + active_remote_pwm_dimmer->x_bri_preset_low = uint8_value; + else +#endif // USE_PWM_DIMMER_REMOTE + Settings.bri_preset_low = uint8_value; + dgr_item = DGR_ITEM_BRI_PRESET_LOW; + dgr_value = uint8_value; + } + else { + // Decrease/increase the high brightness preset. +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + uint8_value = active_remote_pwm_dimmer->x_bri_preset_high; + else +#endif // USE_PWM_DIMMER_REMOTE + uint8_value = Settings.bri_preset_high; + if (down_button_is_held) { + if (uint8_value > 4) uint8_value--; + } + else if (uint8_value < 255) { + uint8_value++; + } +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + active_remote_pwm_dimmer->x_bri_preset_high = uint8_value; + else +#endif // USE_PWM_DIMMER_REMOTE + Settings.bri_preset_high = uint8_value; + dgr_item = DGR_ITEM_BRI_PRESET_HIGH; + dgr_value = uint8_value; + } + break; + } + + button_was_held = true; + + // If the button was tapped before it was held, reset the button hold time to one second and + // turn all the brightness LEDs on for 250ms. + if (tap_count > 0) { + button_hold_time[button_index] = now + 1000; + turn_off_brightness_leds_time = now + 250; + PWMDimmerSetBrightnessLeds(1); + } + } + else if (turn_off_brightness_leds_time && turn_off_brightness_leds_time < millis()) { + turn_off_brightness_leds_time = 0; + PWMDimmerSetBrightnessLeds(-1); + } +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + } + + // If we need to adjust the brightness, do it. The brightness is adjusted faster the longer the + // button is held. + if (bri_direction) { + int32_t bri; +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + bri = active_remote_pwm_dimmer->bri; + else +#endif // USE_PWM_DIMMER_REMOTE + bri = target_bri; + int32_t new_bri; + int32_t offset = bri / 12 + 1; + if (bri_direction > 0) { + new_bri = bri + offset; + if (new_bri > 255) new_bri = 255; + } + else { + new_bri = bri - offset; + if (new_bri < Settings.bri_min) new_bri = Settings.bri_min; + } + if (new_bri != bri) { +#ifdef USE_DEVICE_GROUPS + SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_ITEM_LIGHT_BRI, new_bri); +#endif // USE_DEVICE_GROUPS +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + active_remote_pwm_dimmer->x_bri_power_on = active_remote_pwm_dimmer->bri = new_bri; + else { +#endif // USE_PWM_DIMMER_REMOTE + Settings.bri_power_on = target_bri = new_bri; + Settings.light_dimmer = changeUIntScale(target_bri, 0, 255, 0, 100); + PWMDimmerAnimate(true); +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + } + } + } + + // If the button was just released, ... + else if (button_pressed[button_index]) { + + // If the button was held, send a button off; otherwise, send a button toggle. + SendKey(KEY_BUTTON, button_index + 1, (button_hold_sent[button_index] ? POWER_OFF : POWER_TOGGLE)); + + // If this is about the power button, ... +#ifdef USE_PWM_DIMMER_REMOTE + if (button_index == power_button_index) { +#else // USE_PWM_DIMMER_REMOTE + if (!button_index) { +#endif // USE_PWM_DIMMER_REMOTE + + // If we're ignoring the next power button released, ... + if (ignore_power_button_release) { + ignore_power_button_release = false; + + // If the power button was held with no other buttons pressed, we changed the brightness so + // invert the bri direction for the next time and send a final update. + if (invert_power_button_bri_direction) { + invert_power_button_bri_direction = false; + dgr_item = DGR_ITEM_LIGHT_BRI; +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) { + active_remote_pwm_dimmer->power_button_increases_bri ^= 1; + dgr_value = active_remote_pwm_dimmer->bri; + } + else { +#endif // USE_PWM_DIMMER_REMOTE + power_button_increases_bri ^= 1; + dgr_value = target_bri; +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + } + + // The up and/or down buttons were pressed while the power button was pressed. Handle the + // options based on the number of times the buttons were pressed. + else { + switch (button_press_count[down_button_index]) { + case 1: + // Toggle the powered-off LED option. +#ifdef USE_PWM_DIMMER_REMOTE + if (active_device_is_local) { +#endif // USE_PWM_DIMMER_REMOTE + Settings.flag3.powered_off_led ^= 1; + PWMDimmerSetPoweredOffLed(); +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + break; + case 2: + // Toggle fading. + dgr_item = DGR_ITEM_LIGHT_FADE; +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) { + active_remote_pwm_dimmer->light_fade ^= 1; + dgr_value = active_remote_pwm_dimmer->light_fade; + } + else { +#endif // USE_PWM_DIMMER_REMOTE + Settings.light_fade ^= 1; + dgr_value = Settings.light_fade; +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + break; + } + button_press_count[down_button_index] = 0; + + switch (button_press_count[up_button_index]) { + case 1: + // Toggle the LED timeout. +#ifdef USE_PWM_DIMMER_REMOTE + if (active_device_is_local) { +#endif // USE_PWM_DIMMER_REMOTE + Settings.flag.led_timeout ^= 1; + if (relay_is_on) PWMDimmerSetBrightnessLeds(Settings.flag.led_timeout ? -1 : 0); +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + break; + } + button_press_count[up_button_index] = 0; + } + } + + // If we're not ignoring the power button until it's released, toggle the power. + else { +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->x_bri_power_on; + else +#endif // USE_PWM_DIMMER_REMOTE + target_bri = Settings.bri_power_on; + toggle_power = true; + } + } + + // If this is about the up or down buttons, ... + else { + + // If the button was held, ... + if (button_was_held) { + button_was_held = false; + + // If the button was tapped before it was held, we used the brightness LEDs to inidcate the + // operation so reset the brightness LEDs. + if (button_press_count[down_button_index] > 0 || button_press_count[up_button_index]) { + turn_off_brightness_leds_time = 0; + PWMDimmerSetBrightnessLeds(0); + } + + // If the button was not tapped before it was held, we changed the brightness and sent + // updates with the more-to-come message type. Send a final update. + else { + dgr_item = DGR_ITEM_LIGHT_BRI; +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) + dgr_value = active_remote_pwm_dimmer->bri; + else +#endif // USE_PWM_DIMMER_REMOTE + dgr_value = target_bri; + } + + // Reset the the button press count. + button_press_count[button_index] = 0; + } + + // If the button was tapped (pressed and released quickly), increment the count of how many + // times this button has been pressed. + else if (button_hold_time[button_index] >= now) { + button_press_count[button_index]++; + } + } + + // Flag the button as released. + button_pressed[button_index] = false; +#ifdef USE_PWM_DIMMER_REMOTE + buttons_pressed--; +#endif // USE_PWM_DIMMER_REMOTE + } + + if (toggle_power) { + power_t new_power; + uint8_t new_bri; +#ifdef USE_DEVICE_GROUPS +#ifdef USE_PWM_DIMMER_REMOTE + if (!active_device_is_local) { + active_remote_pwm_dimmer->power ^= 1; + new_power = active_remote_pwm_dimmer->power; + new_bri = active_remote_pwm_dimmer->bri; + } + else { +#endif // USE_PWM_DIMMER_REMOTE + new_power = power ^ 1; + new_bri = target_bri; +#ifdef USE_PWM_DIMMER_REMOTE + } +#endif // USE_PWM_DIMMER_REMOTE + if (new_power) + SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_BRI, new_bri, DGR_ITEM_POWER, new_power); + else + SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, new_power); +#endif // USE_DEVICE_GROUPS + +#ifdef USE_PWM_DIMMER_REMOTE + // The target brightness has already been set. Execute the toggle power command with a source of + // SRC_RETRY. This will turn the power on using the current target brightness (see + // FUNC_SET_DEVICE_POWER below). + if (active_device_is_local) +#endif // USE_PWM_DIMMER_REMOTE + ExecuteCommandPower(1, POWER_TOGGLE, SRC_RETRY); + } + + // If we're not toggling the power and we made changes, send a group update. + else if (dgr_item) { +#ifdef USE_DEVICE_GROUPS + SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE_DIRECT, dgr_item, dgr_value); +#endif // USE_DEVICE_GROUPS + if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT + MqttPublishTeleState(); + } + } +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +void CmndBriMin(void) +{ + uint8_t new_bri_min = Settings.bri_min; + if (XdrvMailbox.payload > 0 && XdrvMailbox.payload <= 255) { + new_bri_min = (XdrvMailbox.payload < 4 ? 4 : XdrvMailbox.payload); + } + else if (1 == XdrvMailbox.data_len) { + if ('-' == XdrvMailbox.data[0]) { + if (new_bri_min > 4) new_bri_min--; + } + else if ('+' == XdrvMailbox.data[0] && new_bri_min < 255) + new_bri_min++; + } + + if (new_bri_min != Settings.bri_min) { + Settings.bri_min = new_bri_min; + PWMDimmerCheckBri(&Settings.bri_power_on); + PWMDimmerCheckBri(&Settings.bri_preset_low); + PWMDimmerCheckBri(&Settings.bri_preset_high); + +#ifdef DEVICE_GROUPS + SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_BRI_MIN, Settings.bri_min, + DGR_ITEM_BRI_POWER_ON, Settings.bri_power_on, DGR_ITEM_BRI_PRESET_LOW, Settings.bri_preset_low, + DGR_ITEM_BRI_PRESET_HIGH, Settings.bri_preset_high); +#endif // DEVICE_GROUPS + } + + ResponseCmndNumber(Settings.bri_min); +} + +void CmndBriPreset(void) +{ + if (XdrvMailbox.data_len > 0) { + bool valid = true; + uint32_t value; + uint8_t parm[2]; + parm[0] = Settings.bri_preset_low; + parm[1] = Settings.bri_preset_high; + char * ptr = XdrvMailbox.data; + for (uint32_t i = 0; i < 2; i++) { + while (*ptr == ' ') ptr++; + if (*ptr == '+') { + if (parm[i] < 255) parm[i]++; + } + else if (*ptr == '-') { + if (parm[i] > Settings.bri_min) parm[i]--; + } + else { + value = strtoul(ptr, &ptr, 0); + if (value < 1 || parm[i] > 255) { + valid = false; + break; + } + parm[i] = value; + PWMDimmerCheckBri(&parm[i]); + if (*ptr != ',') break; + } + ptr++; + } + if (valid && !*ptr) { + if (parm[0] < parm[1]) { + Settings.bri_preset_low = parm[0]; + Settings.bri_preset_high = parm[1]; + } else + { + Settings.bri_preset_low = parm[1]; + Settings.bri_preset_high = parm[0]; + } +#ifdef DEVICE_GROUPS + SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_BRI_PRESET_LOW, Settings.bri_preset_low, DGR_ITEM_BRI_PRESET_HIGH, Settings.bri_preset_high); +#endif // DEVICE_GROUPS + } + } + Response_P(PSTR("{\"" D_CMND_BRI_PRESET "\":{\"Low\":%d,\"High\":%d}}"), Settings.bri_preset_low, Settings.bri_preset_high); +} + +void PWMDmmerCmndDimmer(void) +{ + uint8_t dimmer = changeUIntScale(target_bri, 0, 255, 0, 100); + if (1 == XdrvMailbox.data_len) { + if ('+' == XdrvMailbox.data[0]) + XdrvMailbox.payload = (dimmer > 89) ? 100 : dimmer + 10; + else if ('-' == XdrvMailbox.data[0]) { + XdrvMailbox.payload = (dimmer < 11) ? 1 : dimmer - 10; + } + } + + if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 100) { + uint8_t bri = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 255); +#ifdef USE_DEVICE_GROUPS + SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_BRI, bri); +#endif // USE_DEVICE_GROUPS + PWMDimmerSetBri(bri); + } + + ResponseCmndNumber(Settings.light_dimmer); +} + +void CmndLedTimeout(void) +{ + // Set the brightness LED timeout state. + switch (XdrvMailbox.payload) { + case 0: // Off + case 1: // On + Settings.flag.led_timeout = XdrvMailbox.payload; + break; + case 2: // Toggle + Settings.flag.led_timeout ^= 1; + break; + } + if (relay_is_on) PWMDimmerSetBrightnessLeds(0); + ResponseCmndStateText(Settings.flag.led_timeout); +} + +void CmndPoweredOffLed(void) +{ + // Set the powered-off LED state. + switch (XdrvMailbox.payload) { + case 0: // Off + case 1: // On + Settings.flag3.powered_off_led = XdrvMailbox.payload; + break; + case 2: // Toggle + Settings.flag3.powered_off_led ^= 1; + break; + } + PWMDimmerSetPoweredOffLed(); + ResponseCmndStateText(Settings.flag3.powered_off_led); +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv35(uint8_t function) +{ + bool result = false; + + if (PWM_DIMMER != my_module_type) return result; + + switch (function) { + case FUNC_LOOP: + if (current_bri != target_bri) PWMDimmerAnimate(false); + break; + + case FUNC_EVERY_SECOND: + // Turn off the brightness LED's if it's time. + if (led_timeout_time && led_timeout_time < millis()) { + for (uint32_t index = 0; index < leds_present; index++) SetLedPowerIdx(index, 0); + led_timeout_time = 0; + } + + // The powered-off LED is also the LedLink LED. If we lose the WiFi or MQTT server + // connection, the LED will be set to a blinking state and will be turned off when the + // connection is restored. If the state is blinking now, set a flag so we know that we need + // to restore it when it stops blinking. + if (global_state.data) + restore_powered_off_led = 5; + else if (restore_powered_off_led) { + PWMDimmerSetPoweredOffLed(); + restore_powered_off_led--; + } + break; + + case FUNC_BUTTON_PRESSED: + // Handle the button press/release instead of the button handler. + PWMDimmerHandleButton(); + result = true; + break; + +#ifdef USE_DEVICE_GROUPS + case FUNC_DEVICE_GROUP_REQUEST: + PWMDimmerHandleDeviceGroupRequest(); + break; +#endif // USE_DEVICE_GROUPS + + case FUNC_SET_DEVICE_POWER: + // Handle turning the power on/off here so we can fade off. Set the target brightness and let + // Animate handle turning the power off/on. If the source is SRC_RETRY, we're turning the + // power on with a brightness preset and the target brightness has already been set. + if (!XdrvMailbox.index) + target_bri = 0; + else if (SRC_RETRY != XdrvMailbox.payload) { + target_bri = Settings.bri_power_on; + Settings.light_dimmer = changeUIntScale(target_bri, 0, 255, 0, 100); + } + result = true; + break; + + case FUNC_COMMAND: + result = DecodeCommand(kPWMDimmerCommands, PWMDimmerCommand); + break; + + case FUNC_INIT: + PWMDimmerInit(); + break; + + case FUNC_PRE_INIT: +#ifdef USE_PWM_DIMMER_REMOTE + // If remote device mode is enabled, set the device group count to the number of buttons + // present. + if (Settings.flag3.remote_device_mode) { + for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) { + if (pin[GPIO_KEY1 + button_index] < 99) device_group_count++; + } + } +#endif // USE_PWM_DIMMER_REMOTE + break; + + case FUNC_MODULE_INIT: + PWMModuleInit(); + result = true; + break; + } + return result; +} + +#endif // USE_PWM_DIMMER \ No newline at end of file diff --git a/tasmota/xsns_06_dht_v5.ino b/tasmota/xsns_06_dht_v5.ino index f08286b43..9c7003970 100644 --- a/tasmota/xsns_06_dht_v5.ino +++ b/tasmota/xsns_06_dht_v5.ino @@ -100,6 +100,7 @@ bool DhtRead(uint32_t sensor) break; } +/* bool error = false; noInterrupts(); if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) { @@ -127,6 +128,22 @@ bool DhtRead(uint32_t sensor) } interrupts(); 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; if (dht_data[4] != checksum) { diff --git a/tasmota/xsns_61_MI_NRF24.ino b/tasmota/xsns_61_MI_NRF24.ino index 8527c9749..f1db19d74 100644 --- a/tasmota/xsns_61_MI_NRF24.ino +++ b/tasmota/xsns_61_MI_NRF24.ino @@ -21,6 +21,9 @@ 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, add missing PDU-types, renaming driver --- @@ -58,18 +61,21 @@ #define MJ_HT_V1 2 #define LYWSD02 3 #define LYWSD03 4 +#define CGG1 5 -uint8_t kMINRFSlaveID[4][3] = { 0xC4,0x7C,0x8D, // Flora - 0x58,0x2D,0x34, // MJ_HT_V1 - 0xE7,0x2E,0x00, // LYWSD02 - 0xA4,0xC1,0x38, // LYWSD03 - }; +const uint16_t kMINRFSlaveID[5]={ 0x0098, // Flora + 0x01aa, // MJ_HT_V1 + 0x045b, // LYWSD02 + 0x055b, // LYWSD03 + 0x0347 // CGG1 + }; const char kMINRFSlaveType1[] PROGMEM = "Flora"; const char kMINRFSlaveType2[] PROGMEM = "MJ_HT_V1"; const char kMINRFSlaveType3[] PROGMEM = "LYWSD02"; 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 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 kMINRFL3PDU[3] = {0x4760dd78,0xdbcc1ccd,0xffffffff}; //encrypted - 58 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 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!! @@ -269,7 +276,7 @@ struct { } MINRF; 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 showedUp; float temp; //Flora, MJ_HT_V1, LYWSD0x @@ -362,6 +369,9 @@ bool MINRFreceivePacket(void) case 4: MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); // "LYWSD03" mode 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); // if (_lsfr>254) _lsfr=0; @@ -470,6 +480,9 @@ void MINRFchangePacketModeTo(uint8_t _mode) { if(kMINRFL3PDU[_nextchannel]==0xffffffff) break; NRF24radio.openReadingPipe(0,kMINRFL3PDU[_nextchannel]);// 95 fe 58 30 -> LYWSD03 (= no data message) 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); 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 * * @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 */ -uint32_t MINRFgetSensorSlot(uint8_t (&_serial)[6], uint8_t _type){ - if(_type==0xff){ - DEBUG_SENSOR_LOG(PSTR("MINRF: will test MAC-type")); - for (uint32_t i=0;i<4;i++){ - if(memcmp(_serial,kMINRFSlaveID+i,3)==0){ - DEBUG_SENSOR_LOG(PSTR("MINRF: MAC is type %u"), i); - _type = i+1; - } - else { - DEBUG_SENSOR_LOG(PSTR("MINRF: MAC-type is unknown")); - } +uint32_t MINRFgetSensorSlot(uint8_t (&_serial)[6], uint16_t _type){ + + DEBUG_SENSOR_LOG(PSTR("MINRF: will test ID-type: %x"), _type); + bool _success = false; + for (uint32_t i=0;i<5;i++){ + if(_type == kMINRFSlaveID[i]){ + DEBUG_SENSOR_LOG(PSTR("MINRF: ID is type %u"), i); + _type = i+1; + _success = true; + } + 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()); for(uint32_t i=0; i6000){ // happens every 6000/20 = 300 seconds @@ -712,15 +766,16 @@ void MINRF_EVERY_50_MSECOND() { // Every 50mseconds else if (MINRF.packetMode == LYWSD03){ MINRFhandleLYWSD03Packet(); } - - // DEBUG_SENSOR_LOG(PSTR("MINRF: Change packet mode every 50 msec")); - if (MINRF.packetMode == LYWSD03){ + else if (MINRF.packetMode == CGG1){ + MINRFhandleCGG1Packet(); + } + if (MINRF.packetMode == CGG1){ MINRFinitBLE(1); // no real ble packets in release mode, otherwise for developing use 0 } else { MINRFinitBLE(++MINRF.packetMode); } - + MINRFhopChannel(); NRF24radio.startListening(); } diff --git a/tools/decode-status.py b/tools/decode-status.py index 210175de8..3838d8faf 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -193,7 +193,7 @@ a_features = [[ "USE_ARDUINO_SLAVE","USE_HIH6","USE_HPMA","USE_TSL2591", "USE_DHT12","USE_DS1624","USE_GPS","USE_HOTPLUG", "USE_NRF24","USE_MIBLE","USE_HM10","USE_LE01MR", - "USE_AHT1x","","","" + "USE_AHT1x","USE_WEMOS_MOTOR_V1","","" ],[ "","","","", "","","","", @@ -236,7 +236,7 @@ else: obj = json.load(fp) def StartDecode(): - print ("\n*** decode-status.py v20200220 by Theo Arends and Jacek Ziolkowski ***") + print ("\n*** decode-status.py v20200222 by Theo Arends and Jacek Ziolkowski ***") # print("Decoding\n{}".format(obj)) From 2b94cd77a0a474a363d4f43223dffa1578e82214 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Mon, 24 Feb 2020 07:34:30 -0600 Subject: [PATCH 02/13] Build with device groups, pwm dimmer (with remote) --- tasmota/my_user_config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 353dc1130..0ae6d4663 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -415,9 +415,9 @@ //#define USE_EXS_DIMMER // Add support for ES-Store WiFi Dimmer (+1k5 code) // #define EXS_MCU_CMNDS // Add command to send MCU commands (+0k8 code) //#define USE_HOTPLUG // Add support for sensor HotPlug -// #define USE_DEVICE_GROUPS // Add support for device groups (+3k5 code) -// #define USE_PWM_DIMMER // Add support for MJ-SD01/acenx/NTONPOWER PWM dimmers (+4k5 code) -// #define USE_PWM_DIMMER_REMOTE // Add support for remote switches to PWM Dimmer, also adds device groups support (+0k7 code, also includes device groups) +#define USE_DEVICE_GROUPS // Add support for device groups (+3k5 code) +#define USE_PWM_DIMMER // Add support for MJ-SD01/acenx/NTONPOWER PWM dimmers (+4k5 code) +#define USE_PWM_DIMMER_REMOTE // Add support for remote switches to PWM Dimmer, also adds device groups support (+0k7 code, also includes device groups) // -- Optional light modules ---------------------- #define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by // From 631d9045f136c7fcde3525021ac73ce19b24401f Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Mon, 24 Feb 2020 20:52:06 -0600 Subject: [PATCH 03/13] Deal with late device group initializaton --- tasmota/xdrv_35_pwm_dimmer.ino | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tasmota/xdrv_35_pwm_dimmer.ino b/tasmota/xdrv_35_pwm_dimmer.ino index ea3211563..fca67efe6 100644 --- a/tasmota/xdrv_35_pwm_dimmer.ino +++ b/tasmota/xdrv_35_pwm_dimmer.ino @@ -80,7 +80,6 @@ bool button_hold_sent[3]; bool button_pressed[3] = { false, false, false }; #ifdef USE_PWM_DIMMER_REMOTE struct remote_pwm_dimmer * remote_pwm_dimmers; -struct device_group * active_device_group; struct remote_pwm_dimmer * active_remote_pwm_dimmer; uint8_t buttons_pressed = 0; bool active_device_is_local; @@ -136,13 +135,12 @@ void PWMDimmerInit(void) if (Settings.flag3.remote_device_mode) { if (device_group_count > 1) { if ((remote_pwm_dimmers = (struct remote_pwm_dimmer *) calloc(device_group_count - 1, sizeof(struct remote_pwm_dimmer))) == nullptr) { - AddLog_P2(LOG_LEVEL_ERROR, "PWMDimmer: error allocating PWM dimmer array"); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("PWMDimmer: error allocating PWM dimmer array")); Settings.flag3.remote_device_mode = false; } } } - active_device_group = &device_groups[0]; active_device_is_local = true; #endif // USE_PWM_DIMMER_REMOTE } @@ -220,7 +218,6 @@ void PWMDimmerAnimate(bool no_fade) if (relay_is_on != (current_bri > 0)) { bool power_is_on = ((power & 1) != 0); if (power_is_on == relay_is_on) { -AddLog_P2(LOG_LEVEL_DEBUG, "****************** ExecuteCommandPower(1, %d, SRC_SWITCH)", (relay_is_on ? 0 : 1)); ExecuteCommandPower(1, (relay_is_on ? POWER_OFF : POWER_ON), SRC_SWITCH); } @@ -377,8 +374,7 @@ void PWMDimmerHandleButton() power_button_index = button_index; up_button_index = (button_index == 2 ? 1 : 2); down_button_index = (button_index ? 0 : 1); - active_device_group = &device_groups[power_button_index]; - active_device_is_local = active_device_group->local; + active_device_is_local = device_groups[power_button_index].local; if (!active_device_is_local) active_remote_pwm_dimmer = &remote_pwm_dimmers[power_button_index - 1]; } if (button_index == power_button_index) { @@ -553,11 +549,11 @@ void PWMDimmerHandleButton() // Publish MQTT Event SwitchTrigger#. char topic[TOPSZ]; #ifdef USE_PWM_DIMMER_REMOTE - snprintf(topic, sizeof(topic), "%s/cmnd/Event", active_device_group->group_name); + snprintf_P(topic, sizeof(topic), PSTR("%s/cmnd/Event"), device_groups[power_button_index].group_name); #else // USE_PWM_DIMMER_REMOTE - snprintf(topic, sizeof(topic), "%s/cmnd/Event", SettingsText(SET_MQTT_GRP_TOPIC)); + snprintf_P(topic, sizeof(topic), PSTR("%s/cmnd/Event"), SettingsText(SET_MQTT_GRP_TOPIC)); #endif // USE_PWM_DIMMER_REMOTE - sprintf(mqtt_data, "SwitchTrigger%u", (down_button_is_held ? 1 : 2)); + sprintf_P(mqtt_data, PSTR("SwitchTrigger%u"), (down_button_is_held ? 1 : 2)); MqttPublish(topic); } break; From b3d918f46595579ecb58e2e4599bdd5779c4d9e9 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Tue, 25 Feb 2020 06:19:12 -0600 Subject: [PATCH 04/13] Add MQTT commands --- PWM_Dimmer.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PWM_Dimmer.md b/PWM_Dimmer.md index 528b3f9fb..172a7663a 100644 --- a/PWM_Dimmer.md +++ b/PWM_Dimmer.md @@ -97,6 +97,8 @@ Holding the power button, pressing the down/up buttons a given number of times a Holding any button for over 10 seconds executes the WiFiConfig 2 command. +Pressing and releasing a button publishes an MQTT TOGGLE command. Holding a button publishes an MQTT HOLD command followed by an MQTT OFF command when the button is released. + When Device Groups are enabled, the PWM Dimmer minimum brightness and brightness presets are kept in sync across all switches in the group. The powered-off LED and LED timeout settings are specific to each switch. Changing them does not replicate the change to the other switches in the group. From 1655c1b3aa94055d58535f539081a6b91c0a5e3d Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Tue, 25 Feb 2020 16:49:19 +0100 Subject: [PATCH 05/13] Add some wifi debug options --- tasmota/settings.h | 2 +- tasmota/settings.ino | 16 ++------ tasmota/support_wifi.ino | 80 ++++++++++++++++++++++++++++++---------- tasmota/tasmota_post.h | 4 +- 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index fe7c31549..d2570d9ba 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -132,7 +132,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t spare28 : 1; uint32_t spare29 : 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; diff --git a/tasmota/settings.ino b/tasmota/settings.ino index c2ddc6607..1563897b0 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -745,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 2 = Erase Tasmota parameter area (0x0xF3xxx - 0x0xFBFFF) 3 = Erase Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF) + 4 = Erase SDK parameter area used for wifi calibration (0x0FCxxx - 0x0FCFFF) */ #ifndef FIRMWARE_MINIMAL @@ -762,16 +763,17 @@ void SettingsErase(uint8_t type) _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 } -#ifdef USE_WIFI_SDK_ERASE else if (4 == type) { +// _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) } -#endif // USE_WIFI_SDK_ERASE +*/ 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 @@ -786,16 +788,6 @@ void SettingsSdkErase(void) delay(1000); } -#ifdef USE_WIFI_SDK_ERASE -void SettingsSdkWifiErase(void) -{ - WiFi.disconnect(false); // Delete SDK wifi config - SettingsErase(4); - SettingsErase(5); - delay(200); -} -#endif // USE_WIFI_SDK_ERASE - /********************************************************************************************/ void SettingsDefault(void) diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index dd8d2441c..93dba06a9 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -21,6 +21,10 @@ * 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 #define WIFI_RSSI_THRESHOLD 10 // Difference in dB between current network and scanned network #endif @@ -609,6 +613,32 @@ void WifiSetOutputPower(void) 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) { WifiSetState(0); @@ -618,37 +648,47 @@ void WifiConnect(void) Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2); Wifi.retry = Wifi.retry_init; 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 -// Re-enabled from 6.3.0.7 with ESP.restart replaced by ESP.reset -void WifiDisconnect(void) -{ -#ifdef USE_WIFI_SDK_ERASE // Do not enable with DeepSleep as it will wear out flash - SettingsSdkWifiErase(); -#else - // 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. -#endif // USE_WIFI_SDK_ERASE -} - -void WifiShutdown(void) +void WifiShutdown(bool option = false) { + // 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 + +#ifdef USE_EMULATION + UdpDisconnect(); +#endif // USE_EMULATION + if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT 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) { - WifiShutdown(); + WifiShutdown(true); CrashDumpClear(); // Clear the stack dump in RTC // ESP.restart(); // This results in exception 3 on restarts on core 2.3.0 ESP.reset(); diff --git a/tasmota/tasmota_post.h b/tasmota/tasmota_post.h index 5f031a2f9..d8a8ea5d1 100644 --- a/tasmota/tasmota_post.h +++ b/tasmota/tasmota_post.h @@ -708,11 +708,11 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #endif #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 #endif -//#include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0) +//#include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0) #ifndef ARDUINO_ESP8266_RELEASE #define ARDUINO_ESP8266_RELEASE "STAGE" #endif From 8ca95996f98d64e868945097a023d5d66ac1526c Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Tue, 25 Feb 2020 16:52:07 +0100 Subject: [PATCH 06/13] Restore default wifi functionality --- tasmota/support_wifi.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index 93dba06a9..8f7de0f96 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -23,7 +23,7 @@ // 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 +//#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 #define WIFI_RSSI_THRESHOLD 10 // Difference in dB between current network and scanned network From 344187cf94d03464408c9f15dd8cdd126e44f065 Mon Sep 17 00:00:00 2001 From: stefanbode Date: Tue, 25 Feb 2020 17:55:45 +0100 Subject: [PATCH 07/13] minor bug fixes and enhancements - fix bug in multi-push from last commit - remove rules execution every second. only at start and stop - enable rules for Shutter#Button - tbd: How to correctly detect a simultaneous button push --- tasmota/xdrv_27_shutter.ino | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tasmota/xdrv_27_shutter.ino b/tasmota/xdrv_27_shutter.ino index 6f4d711d7..1131a5f70 100644 --- a/tasmota/xdrv_27_shutter.ino +++ b/tasmota/xdrv_27_shutter.ino @@ -255,6 +255,7 @@ void ShutterInit(void) void ShutterReportPosition(bool always) { Response_P(PSTR("{")); + rules_flag.shutter_moving = 0; 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]); uint32_t position = ShutterRealToPercentPosition(Shutter.real_position[i], i); @@ -268,7 +269,6 @@ void ShutterReportPosition(bool always) ResponseJsonEnd(); if (always || (rules_flag.shutter_moving)) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER)); - XdrvRulesProcess(); } //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved); @@ -299,18 +299,23 @@ void ShutterUpdatePosition(void) 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[i]*1000 / max_freq_change_per_sec; int32_t velocity = Shutter.direction[i] == 1 ? 100 : Shutter.close_velocity[i]; - int32_t minstopway = min_runtime_ms * velocity / 100 * Shutter.pwm_frequency[i] / 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 ; stop_position_delta =200 * Shutter.pwm_frequency[i]/max_frequency + Shutter.direction[i] * (next_possible_stop - Shutter.target_position[i]); + if (Shutter.time[i] == 1) { + ShutterReportPosition(true); + rules_flag.shutter_moving = 1; + XdrvRulesProcess(); + } //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; 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[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]- (100 * Shutter.direction[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*11/200); //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Ramp down: acc: %d"), Shutter.accelerator[i]); } else if ( Shutter.accelerator[i] > 0 && Shutter.pwm_frequency[i] == max_frequency) { Shutter.accelerator[i] = 0; @@ -348,7 +353,7 @@ void ShutterUpdatePosition(void) } analogWrite(pin[GPIO_PWM1+i], 0); Shutter.real_position[i] = ShutterCounterBasedPosition(i); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT:Real %d, pulsecount %d, start %d"), Shutter.real_position[i],RtcSettings.pulse_counter[i], Shutter.start_position[i]); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Real %d, pulsecount %d, start %d"), Shutter.real_position[i],RtcSettings.pulse_counter[i], Shutter.start_position[i]); if ((1 << (Settings.shutter_startrelay[i]-1)) & power) { ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER); @@ -422,6 +427,8 @@ void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos) Shutter.time[i] = 0; Shutter.skip_relay_change = 0; Shutter.direction[i] = direction; + rules_flag.shutter_moving = 1; + rules_flag.shutter_moved = 0; //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: real %d, start %d, counter %d, max_freq %d, dir %d, freq %d"),Shutter.real_position[i], Shutter.start_position[i] ,RtcSettings.pulse_counter[i],Shutter.max_pwm_frequency , Shutter.direction[i] ,Shutter.max_pwm_frequency ); } //AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start shutter: %d from %d to %d in directin %d"), i, Shutter.start_position[i], Shutter.target_position[i], Shutter.direction[i]); @@ -538,6 +545,7 @@ void ShutterButtonHandler(void) Button.press_counter[button_index] = 99; // Remember to discard further action for press & hold within button timings } else { 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; @@ -590,15 +598,16 @@ void ShutterButtonHandler(void) // check for simultaneous shutter button press uint32 min_shutterbutton_press_counter = -1; for (uint32_t i = 0; i < MAX_KEYS; i++) { - 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)) { + 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, i %d"), Settings.shutter_button[i], shutter_index, Button.press_counter[i] , min_shutterbutton_press_counter, i); + if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index) && (i != button_index) && (Button.press_counter[i] < min_shutterbutton_press_counter)) { min_shutterbutton_press_counter = Button.press_counter[i]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: min_shutterbutton_press_counter %d"), min_shutterbutton_press_counter); } } if (min_shutterbutton_press_counter == Button.press_counter[button_index]) { // simultaneous shutter button press detected - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT:simultanous presss deteced")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: simultanous presss deteced")); press_index = Button.press_counter[button_index]; for (uint32_t i = 0; i < MAX_KEYS; i++) if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) != shutter_index)) @@ -678,6 +687,7 @@ void ShutterButtonHandler(void) ResponseJsonEnd(); MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER)); XdrvRulesProcess(); + } } From f2074932f7113b7143d375539cc89324b7d12493 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 26 Feb 2020 13:45:46 +0100 Subject: [PATCH 08/13] Clean up wifi connection / disconnection --- tasmota/settings.h | 2 +- tasmota/settings.ino | 4 ++++ tasmota/support_wifi.ino | 22 ++++++++++++++-------- tasmota/xdrv_01_webserver.ino | 4 ++++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index d2570d9ba..be9e7bf40 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -132,7 +132,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t spare28 : 1; uint32_t spare29 : 1; uint32_t spare30 : 1; - uint32_t force_sdk_erase : 1; // bit 31 (v8.1.0.9) - SetOption113 - Force erase of SDK wifi calibrate secore on restart + uint32_t spare31 : 1; // bit 31 }; } SysBitfield4; diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 1563897b0..5ebf970d4 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -774,6 +774,10 @@ void SettingsErase(uint8_t type) _sectorEnd = _sectorStart +1; // SDK end of phy area and Core calibration sector (0xxFCFFF) } */ + else { + return; + } + 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 diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index 8f7de0f96..c98bdc226 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -21,7 +21,8 @@ * Wifi \*********************************************************************************************/ -// Enable only one of two below debug options +// Enable one of three below options for wifi re-connection debugging +//#define WIFI_FORCE_RF_CAL_ERASE // Erase rf calibration sector on restart only //#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 @@ -34,7 +35,7 @@ const uint8_t WIFI_CONFIG_SEC = 180; // seconds before restart const uint8_t WIFI_CHECK_SEC = 20; // seconds -const uint8_t WIFI_RETRY_OFFSET_SEC = 20; // seconds +const uint8_t WIFI_RETRY_OFFSET_SEC = 12; // seconds #include // Wifi, MQTT, Ota, WifiManager #if LWIP_IPV6 @@ -645,7 +646,7 @@ void WifiConnect(void) WifiSetOutputPower(); WiFi.persistent(false); // Solve possible wifi init errors Wifi.status = 0; - Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2); + Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + (ESP.getChipId() & 0xF); // Add extra delay to stop overrun by simultanous re-connects Wifi.retry = Wifi.retry_init; Wifi.counter = 1; @@ -659,29 +660,34 @@ void WifiConnect(void) void WifiShutdown(bool option = false) { // option = false - Legacy disconnect also used by DeepSleep - // option = true - Disconnect with SDK wifi calibrate sector erase + // option = true - Disconnect with SDK wifi calibrate sector erase when WIFI_FORCE_RF_CAL_ERASE enabled delay(100); // Allow time for message xfer - disabled v6.1.0b #ifdef USE_EMULATION UdpDisconnect(); + delay(100); // Flush anything in the network buffers. #endif // USE_EMULATION if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT MqttDisconnect(); + delay(100); // Flush anything in the network buffers. } - if (option && Settings.flag4.force_sdk_erase) { // SetOption113 - Force erase of SDK wifi calibrate sector on restart +#ifdef WIFI_FORCE_RF_CAL_ERASE + if (option) { WiFi.disconnect(false); // Disconnect wifi SettingsErase(4); // Delete SDK wifi config and calibrate data - } else { + } else +#endif // WIFI_FORCE_RF_CAL_ERASE + { // 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 + 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 + WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters } delay(100); // Flush anything in the network buffers. } diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 705bd4a15..7e7292a8c 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -1893,6 +1893,7 @@ void HandleOtherConfiguration(void) } #ifdef USE_EMULATION +#if defined(USE_EMULATION_WEMO) || defined(USE_EMULATION_HUE) WSContentSend_P(PSTR("

 " D_EMULATION " 

")); // Keep close to Friendlynames so do not use
for (uint32_t i = 0; i < EMUL_MAX; i++) { #ifndef USE_EMULATION_WEMO @@ -1910,6 +1911,7 @@ void HandleOtherConfiguration(void) } } WSContentSend_P(PSTR("

")); +#endif // USE_EMULATION_WEMO || USE_EMULATION_HUE #endif // USE_EMULATION WSContentSend_P(HTTP_FORM_END); @@ -1928,8 +1930,10 @@ void OtherSaveSettings(void) SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp); Settings.flag.mqtt_enabled = WebServer->hasArg("b1"); // SetOption3 - Enable MQTT #ifdef USE_EMULATION +#if defined(USE_EMULATION_WEMO) || defined(USE_EMULATION_HUE) WebGetArg("b2", tmp, sizeof(tmp)); Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp); +#endif // USE_EMULATION_WEMO || USE_EMULATION_HUE #endif // USE_EMULATION snprintf_P(message, sizeof(message), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation); From 878d93ebdbd45e47b0704adc4ff23d4508b12630 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 26 Feb 2020 14:39:27 +0100 Subject: [PATCH 09/13] Prepare for PWM Dimmer Prepare for PWM Dimmer (#7791) --- tasmota/settings.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index be9e7bf40..13b4d03a1 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -105,9 +105,9 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu 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 device_groups_enabled : 1; // bit 3 (v8.1.0.9) - SetOption85 - Enable Device Groups - uint32_t spare04 : 1; - uint32_t spare05 : 1; - uint32_t spare06 : 1; + uint32_t led_timeout : 1; // bit 4 (v8.1.0.9) - SetOption86 - Turn brightness LED's off 5 seconds after last change + uint32_t powered_off_led : 1; // bit 5 (v8.1.0.9) - SetOption87 - Turn red LED on when powered off + uint32_t remote_device_mode : 1; // bit 6 (v8.1.0.9) - SetOption88 - Buttons control remote devices uint32_t spare07 : 1; uint32_t spare08 : 1; uint32_t spare09 : 1; @@ -465,9 +465,13 @@ struct SYSCFG { uint8_t mqttlog_level; // F01 uint8_t sps30_inuse_hours; // F02 uint8_t hotplug_scan; // F03 - uint8_t reserved1; // F04 - reserved for s-hadinger + uint8_t bri_power_on; // F04 + uint8_t bri_min; // F05 + uint8_t bri_preset_low; // F06 + uint8_t bri_preset_high; // F07 + uint8_t button_devices; // F08 - uint8_t free_f05[199]; // F05 + uint8_t free_f05[195]; // F09 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 From 1f1c956b26a79511929dfa535b820c844010f2a5 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Wed, 26 Feb 2020 21:27:09 -0600 Subject: [PATCH 10/13] Use newly allocated settings and flags --- tasmota/my_user_config.h | 4 +- tasmota/settings.h | 17 +-------- tasmota/xdrv_35_pwm_dimmer.ino | 68 +++++++++++++++++----------------- 3 files changed, 37 insertions(+), 52 deletions(-) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 0aedea795..0ae6d4663 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -415,9 +415,9 @@ //#define USE_EXS_DIMMER // Add support for ES-Store WiFi Dimmer (+1k5 code) // #define EXS_MCU_CMNDS // Add command to send MCU commands (+0k8 code) //#define USE_HOTPLUG // Add support for sensor HotPlug -//#define USE_DEVICE_GROUPS // Add support for device groups (+3k5 code) +#define USE_DEVICE_GROUPS // Add support for device groups (+3k5 code) #define USE_PWM_DIMMER // Add support for MJ-SD01/acenx/NTONPOWER PWM dimmers (+4k5 code) -//#define USE_PWM_DIMMER_REMOTE // Add support for remote switches to PWM Dimmer, also adds device groups support (+0k7 code, also includes device groups) +#define USE_PWM_DIMMER_REMOTE // Add support for remote switches to PWM Dimmer, also adds device groups support (+0k7 code, also includes device groups) // -- Optional light modules ---------------------- #define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by // diff --git a/tasmota/settings.h b/tasmota/settings.h index 13b4d03a1..da8da2f29 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -469,9 +469,8 @@ struct SYSCFG { uint8_t bri_min; // F05 uint8_t bri_preset_low; // F06 uint8_t bri_preset_high; // F07 - uint8_t button_devices; // F08 - uint8_t free_f05[195]; // F09 + uint8_t free_f05[196]; // F08 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 @@ -579,18 +578,4 @@ typedef union { ADC_MODE(ADC_VCC); // Set ADC input for Power Supply Voltage usage #endif -// Settings re-purposed for the PWM_DIMMER module -#ifdef USE_PWM_DIMMER -#define led_timeout light_signal // SetOption18 - Turn brightness LED's off 5 seconds after last change -#define powered_off_led buzzer_enable // SetOption67 - Turn red LED on when powered off -#define bri_power_on pcf8574_config[0] // Brightness when next powered-on -#define bri_min pcf8574_config[1] // Minimum brightness -#define bri_preset_low pcf8574_config[2] // Bri preset low -#define bri_preset_high pcf8574_config[3] // Bri preset high -#define button_devices pcf8574_config[4] // Button-device map -#ifdef USE_PWM_DIMMER_REMOTE -#define remote_device_mode dds2382_model // SetOption71 - Buttons control remote devices -#endif // USE_PWM_DIMMER_REMOTE -#endif // USE_PWM_DIMMER - #endif // _SETTINGS_H_ diff --git a/tasmota/xdrv_35_pwm_dimmer.ino b/tasmota/xdrv_35_pwm_dimmer.ino index fca67efe6..94bc4ea38 100644 --- a/tasmota/xdrv_35_pwm_dimmer.ino +++ b/tasmota/xdrv_35_pwm_dimmer.ino @@ -48,10 +48,10 @@ void (* const PWMDimmerCommand[])(void) PROGMEM = { struct remote_pwm_dimmer { power_t power; uint8_t light_speed; - uint8_t x_bri_power_on; - uint8_t x_bri_min; - uint8_t x_bri_preset_low; - uint8_t x_bri_preset_high; + uint8_t bri_power_on; + uint8_t bri_min; + uint8_t bri_preset_low; + uint8_t bri_preset_high; uint8_t fixed_color_index; uint8_t bri; bool power_button_increases_bri; @@ -98,14 +98,14 @@ void PWMModuleInit() Settings.flag.pwm_control = 0; // Use basic PWM control instead of Light if (Settings.last_module != Settings.module) { - Settings.bri_min = 8; + Settings.bri_min = 25; Settings.bri_power_on = 50; - Settings.bri_preset_low = 8; + Settings.bri_preset_low = 25; Settings.bri_preset_high = 255; Settings.last_module = Settings.module; } else { - if (Settings.bri_min < 1) Settings.bri_min = 8; + if (Settings.bri_min < 1) Settings.bri_min = 25; PWMDimmerCheckBri(&Settings.bri_power_on); PWMDimmerCheckBri(&Settings.bri_preset_low); PWMDimmerCheckBri(&Settings.bri_preset_high); @@ -132,11 +132,11 @@ void PWMModuleInit() void PWMDimmerInit(void) { #ifdef USE_PWM_DIMMER_REMOTE - if (Settings.flag3.remote_device_mode) { + if (Settings.flag4.remote_device_mode) { if (device_group_count > 1) { if ((remote_pwm_dimmers = (struct remote_pwm_dimmer *) calloc(device_group_count - 1, sizeof(struct remote_pwm_dimmer))) == nullptr) { AddLog_P2(LOG_LEVEL_ERROR, PSTR("PWMDimmer: error allocating PWM dimmer array")); - Settings.flag3.remote_device_mode = false; + Settings.flag4.remote_device_mode = false; } } } @@ -166,7 +166,7 @@ void PWMDimmerSetBrightnessLeds(int32_t operation) } // If enabled, set the LED timeout. - if (!operation) led_timeout_time = (current_bri && Settings.flag.led_timeout ? millis() + 5000 : 0); + if (!operation) led_timeout_time = (current_bri && Settings.flag4.led_timeout ? millis() + 5000 : 0); } } @@ -174,7 +174,7 @@ void PWMDimmerSetPoweredOffLed(void) { // Set the powered-off LED state. if (pin[GPIO_LEDLNK] < 99) { - bool power_off_led_on = !power && Settings.flag3.powered_off_led; + bool power_off_led_on = !power && Settings.flag4.powered_off_led; if (ledlnk_inverted) power_off_led_on ^= 1; digitalWrite(pin[GPIO_LEDLNK], power_off_led_on); } @@ -370,7 +370,7 @@ void PWMDimmerHandleButton() // If there are no other buttons pressed right now and remote mode is enabled, make the device // associated with this button the device we're going to control. - if (buttons_pressed == 1 && Settings.flag3.remote_device_mode) { + if (buttons_pressed == 1 && Settings.flag4.remote_device_mode) { power_button_index = button_index; up_button_index = (button_index == 2 ? 1 : 2); down_button_index = (button_index ? 0 : 1); @@ -439,7 +439,7 @@ void PWMDimmerHandleButton() else { #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) { - active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->x_bri_preset_low; + active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->bri_preset_low; active_remote_pwm_dimmer->power_button_increases_bri = true; } else { @@ -496,7 +496,7 @@ void PWMDimmerHandleButton() else { #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) - active_remote_pwm_dimmer->bri = (down_button_is_held ? active_remote_pwm_dimmer->x_bri_preset_low : active_remote_pwm_dimmer->x_bri_preset_high); + active_remote_pwm_dimmer->bri = (down_button_is_held ? active_remote_pwm_dimmer->bri_preset_low : active_remote_pwm_dimmer->bri_preset_high); else #endif // USE_PWM_DIMMER_REMOTE target_bri = (down_button_is_held ? Settings.bri_preset_low : Settings.bri_preset_high); @@ -563,7 +563,7 @@ void PWMDimmerHandleButton() // Decrease/increase the minimum brightness. #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) - uint8_value = active_remote_pwm_dimmer->x_bri_min; + uint8_value = active_remote_pwm_dimmer->bri_min; else #endif // USE_PWM_DIMMER_REMOTE uint8_value = Settings.bri_min; @@ -575,8 +575,8 @@ void PWMDimmerHandleButton() } #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) { - active_remote_pwm_dimmer->x_bri_min = uint8_value; - if (active_remote_pwm_dimmer->x_bri_power_on < uint8_value) active_remote_pwm_dimmer->x_bri_power_on = uint8_value; + active_remote_pwm_dimmer->bri_min = uint8_value; + if (active_remote_pwm_dimmer->bri_power_on < uint8_value) active_remote_pwm_dimmer->bri_power_on = uint8_value; } else { #endif // USE_PWM_DIMMER_REMOTE @@ -618,8 +618,8 @@ void PWMDimmerHandleButton() // Decrease/increase the low brightness preset. #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) { - uint8_value = active_remote_pwm_dimmer->x_bri_preset_low; - min_uint8_value =active_remote_pwm_dimmer->x_bri_min; + uint8_value = active_remote_pwm_dimmer->bri_preset_low; + min_uint8_value =active_remote_pwm_dimmer->bri_min; } else { #endif // USE_PWM_DIMMER_REMOTE @@ -636,7 +636,7 @@ void PWMDimmerHandleButton() } #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) - active_remote_pwm_dimmer->x_bri_preset_low = uint8_value; + active_remote_pwm_dimmer->bri_preset_low = uint8_value; else #endif // USE_PWM_DIMMER_REMOTE Settings.bri_preset_low = uint8_value; @@ -647,7 +647,7 @@ void PWMDimmerHandleButton() // Decrease/increase the high brightness preset. #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) - uint8_value = active_remote_pwm_dimmer->x_bri_preset_high; + uint8_value = active_remote_pwm_dimmer->bri_preset_high; else #endif // USE_PWM_DIMMER_REMOTE uint8_value = Settings.bri_preset_high; @@ -659,7 +659,7 @@ void PWMDimmerHandleButton() } #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) - active_remote_pwm_dimmer->x_bri_preset_high = uint8_value; + active_remote_pwm_dimmer->bri_preset_high = uint8_value; else #endif // USE_PWM_DIMMER_REMOTE Settings.bri_preset_high = uint8_value; @@ -714,7 +714,7 @@ void PWMDimmerHandleButton() #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) - active_remote_pwm_dimmer->x_bri_power_on = active_remote_pwm_dimmer->bri = new_bri; + active_remote_pwm_dimmer->bri_power_on = active_remote_pwm_dimmer->bri = new_bri; else { #endif // USE_PWM_DIMMER_REMOTE Settings.bri_power_on = target_bri = new_bri; @@ -772,7 +772,7 @@ void PWMDimmerHandleButton() #ifdef USE_PWM_DIMMER_REMOTE if (active_device_is_local) { #endif // USE_PWM_DIMMER_REMOTE - Settings.flag3.powered_off_led ^= 1; + Settings.flag4.powered_off_led ^= 1; PWMDimmerSetPoweredOffLed(); #ifdef USE_PWM_DIMMER_REMOTE } @@ -803,8 +803,8 @@ void PWMDimmerHandleButton() #ifdef USE_PWM_DIMMER_REMOTE if (active_device_is_local) { #endif // USE_PWM_DIMMER_REMOTE - Settings.flag.led_timeout ^= 1; - if (relay_is_on) PWMDimmerSetBrightnessLeds(Settings.flag.led_timeout ? -1 : 0); + Settings.flag4.led_timeout ^= 1; + if (relay_is_on) PWMDimmerSetBrightnessLeds(Settings.flag4.led_timeout ? -1 : 0); #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE @@ -818,7 +818,7 @@ void PWMDimmerHandleButton() else { #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) - active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->x_bri_power_on; + active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->bri_power_on; else #endif // USE_PWM_DIMMER_REMOTE target_bri = Settings.bri_power_on; @@ -1021,14 +1021,14 @@ void CmndLedTimeout(void) switch (XdrvMailbox.payload) { case 0: // Off case 1: // On - Settings.flag.led_timeout = XdrvMailbox.payload; + Settings.flag4.led_timeout = XdrvMailbox.payload; break; case 2: // Toggle - Settings.flag.led_timeout ^= 1; + Settings.flag4.led_timeout ^= 1; break; } if (relay_is_on) PWMDimmerSetBrightnessLeds(0); - ResponseCmndStateText(Settings.flag.led_timeout); + ResponseCmndStateText(Settings.flag4.led_timeout); } void CmndPoweredOffLed(void) @@ -1037,14 +1037,14 @@ void CmndPoweredOffLed(void) switch (XdrvMailbox.payload) { case 0: // Off case 1: // On - Settings.flag3.powered_off_led = XdrvMailbox.payload; + Settings.flag4.powered_off_led = XdrvMailbox.payload; break; case 2: // Toggle - Settings.flag3.powered_off_led ^= 1; + Settings.flag4.powered_off_led ^= 1; break; } PWMDimmerSetPoweredOffLed(); - ResponseCmndStateText(Settings.flag3.powered_off_led); + ResponseCmndStateText(Settings.flag4.powered_off_led); } /*********************************************************************************************\ @@ -1118,7 +1118,7 @@ bool Xdrv35(uint8_t function) #ifdef USE_PWM_DIMMER_REMOTE // If remote device mode is enabled, set the device group count to the number of buttons // present. - if (Settings.flag3.remote_device_mode) { + if (Settings.flag4.remote_device_mode) { for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) { if (pin[GPIO_KEY1 + button_index] < 99) device_group_count++; } From d8fcb396f6da31187fe2dc9179e5826b3f977202 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Wed, 26 Feb 2020 21:30:30 -0600 Subject: [PATCH 11/13] Doc update --- PWM_Dimmer.md | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/PWM_Dimmer.md b/PWM_Dimmer.md index 172a7663a..cc45466a3 100644 --- a/PWM_Dimmer.md +++ b/PWM_Dimmer.md @@ -11,7 +11,7 @@ To enable PWM dimmer operation, select the PWM Dimmer module. Pressing and releasing the power button toggles the power on/off. If the toggle turns the power on, the load is returned to the last brightness it was adjusted to. If Fade is enabled, the load is faded on/off at the rate defined by the Speed setting. -When the power is on, holding the down or up button decreases/increases the brightness (PWM value). The brightness is changed faster as higher brightnesses. The BriMin command defines the lowest value the brightness can be decreased to. +When the power is on, holding the down or up button decreases/increases the brightness (PWM value). The brightness is changed faster at higher brightnesses. The BriMin command defines the lowest value the brightness can be decreased to. The brightness can also be changed using just the power button. When the power is on, holding the power button alternately increases or decreases the brightness. Initially, holding the power button increases the brightness. Releasing and then holding the power button again decreases the brightness. @@ -99,7 +99,7 @@ Holding any button for over 10 seconds executes the WiFiConfig 2 command. Pressing and releasing a button publishes an MQTT TOGGLE command. Holding a button publishes an MQTT HOLD command followed by an MQTT OFF command when the button is released. -When Device Groups are enabled, the PWM Dimmer minimum brightness and brightness presets are kept in sync across all switches in the group. The powered-off LED and LED timeout settings are specific to each switch. Changing them does not replicate the change to the other switches in the group. +When Device Groups are enabled, the PWM Dimmer minimum brightness, brightness presets, fade and speed settings are kept in sync across all switches in the group. The powered-off LED and LED timeout settings are specific to each switch. Changing them does not replicate the change to the other switches in the group. ### Commands @@ -125,7 +125,7 @@ When Device Groups are enabled, the PWM Dimmer minimum brightness and brightness BriPreset - ,<high> = set brightness low and high presets + <low>,<high> = set brightness low and high presets

1..255 = set brightness preset

@@ -178,6 +178,36 @@ When Device Groups are enabled, the PWM Dimmer minimum brightness and brightness - = decrease speed + + SetOption86 + + Set brightness LED timeout +

+0 = disable timeout (default) +

+1 = enable timeout + + + + SetOption87 + + Set powered-off LED (nightlight) +

+0 = disable powered-off LED (default) +

+1 = enable powered-off LED + + + + SetOption88 + + Set remote device mode +

+0 = disable remote device mode(default) +

+1 = enable remote device mode + + From 33a6f28ae3c8f63893631d809fd952186643b4c3 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Wed, 26 Feb 2020 21:46:09 -0600 Subject: [PATCH 12/13] Doc update --- PWM_Dimmer.md | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/PWM_Dimmer.md b/PWM_Dimmer.md index 172a7663a..cc45466a3 100644 --- a/PWM_Dimmer.md +++ b/PWM_Dimmer.md @@ -11,7 +11,7 @@ To enable PWM dimmer operation, select the PWM Dimmer module. Pressing and releasing the power button toggles the power on/off. If the toggle turns the power on, the load is returned to the last brightness it was adjusted to. If Fade is enabled, the load is faded on/off at the rate defined by the Speed setting. -When the power is on, holding the down or up button decreases/increases the brightness (PWM value). The brightness is changed faster as higher brightnesses. The BriMin command defines the lowest value the brightness can be decreased to. +When the power is on, holding the down or up button decreases/increases the brightness (PWM value). The brightness is changed faster at higher brightnesses. The BriMin command defines the lowest value the brightness can be decreased to. The brightness can also be changed using just the power button. When the power is on, holding the power button alternately increases or decreases the brightness. Initially, holding the power button increases the brightness. Releasing and then holding the power button again decreases the brightness. @@ -99,7 +99,7 @@ Holding any button for over 10 seconds executes the WiFiConfig 2 command. Pressing and releasing a button publishes an MQTT TOGGLE command. Holding a button publishes an MQTT HOLD command followed by an MQTT OFF command when the button is released. -When Device Groups are enabled, the PWM Dimmer minimum brightness and brightness presets are kept in sync across all switches in the group. The powered-off LED and LED timeout settings are specific to each switch. Changing them does not replicate the change to the other switches in the group. +When Device Groups are enabled, the PWM Dimmer minimum brightness, brightness presets, fade and speed settings are kept in sync across all switches in the group. The powered-off LED and LED timeout settings are specific to each switch. Changing them does not replicate the change to the other switches in the group. ### Commands @@ -125,7 +125,7 @@ When Device Groups are enabled, the PWM Dimmer minimum brightness and brightness BriPreset - ,<high> = set brightness low and high presets + <low>,<high> = set brightness low and high presets

1..255 = set brightness preset

@@ -178,6 +178,36 @@ When Device Groups are enabled, the PWM Dimmer minimum brightness and brightness - = decrease speed + + SetOption86 + + Set brightness LED timeout +

+0 = disable timeout (default) +

+1 = enable timeout + + + + SetOption87 + + Set powered-off LED (nightlight) +

+0 = disable powered-off LED (default) +

+1 = enable powered-off LED + + + + SetOption88 + + Set remote device mode +

+0 = disable remote device mode(default) +

+1 = enable remote device mode + + From 47925c6f6281ed1f47f85b7b82543c92d2b50228 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Wed, 26 Feb 2020 21:47:08 -0600 Subject: [PATCH 13/13] Correct remote device mode option in doc --- PWM_Dimmer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PWM_Dimmer.md b/PWM_Dimmer.md index cc45466a3..da687bcf2 100644 --- a/PWM_Dimmer.md +++ b/PWM_Dimmer.md @@ -218,7 +218,7 @@ Remote device mode allows PWM Dimmer switches to control remote devices. With re To include remote device mode support in the build, define USE_PWM_DIMMER_REMOTE in your user_config_override. Remote device mode support requires device group support so USE_DEVICE_GROUPS is automatically defined if USE_PWM_DIMMER_REMOTE is defined. Remote device mode support adds 0.7K to the code size in addition to the code size required for device groups support. -To enable remote device mode, set Option71 to 1. Each remote device must be running firmware with device group support and have remote device support enabled. The remote devices do not need to be built with PWM dimmer support nor do they need to be switches. +To enable remote device mode, set Option88 to 1. Each remote device must be running firmware with device group support and have remote device support enabled. The remote devices do not need to be built with PWM dimmer support nor do they need to be switches. If a remote device is a PWM dimmer, the device acts like a 3-way dimmer switch would and may or may not have a load connected to it. It’s also possible to use a PWM dimmer switch without a load to act as a wall switch to control the power, brightness and color of one or more smart lights with Tasmota with device group support loaded on them.