From a550fe3ac765a2761e153308a7df21865fc4c425 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Mon, 24 Feb 2020 16:34:45 -0600 Subject: [PATCH 1/4] Use newly allocated settings and flag --- Device_Groups.md | 8 +- tasmota/settings.h | 11 +-- tasmota/support_command.ino | 2 +- tasmota/support_device_groups.ino | 129 +++++++++++++++--------------- tasmota/support_udp.ino | 2 +- tasmota/support_wifi.ino | 2 +- tasmota/tasmota.ino | 3 - tasmota/tasmota_post.h | 4 + tasmota/xdrv_01_webserver.ino | 2 +- 9 files changed, 81 insertions(+), 82 deletions(-) diff --git a/Device_Groups.md b/Device_Groups.md index c5990136f..3384132b4 100644 --- a/Device_Groups.md +++ b/Device_Groups.md @@ -2,7 +2,7 @@ The device groups module provides a framework to allow multiple devices to be in a group with values such as power, light color/temperature/brightness, PWM values, sensor values, etc. shared with other devices in the group. For example, with multiple light modules in a device group, the light settings can be changed on one module and the settings will automatically be changed on the other light modules. Dimmer switch modules could be in a device group with light modules and the dimmer switch could control the power, brightness and colors of all the lights in the group. Multiple dimmer switches could be in a device group to form a 3-way/4-way dimmer switch. -UDP broadcasts, followed by UDP unicasts if necessary, are used to send updates to all devices so updates are fast. There is no need for an MQTT server but all the devices in a group must be on the same IP network. +UDP multicasts, followed by UDP unicasts if necessary, are used to send updates to all devices so updates are fast. There is no need for an MQTT server but all the devices in a group must be on the same IP network. To include device groups support in the build, define USE_DEVICE_GROUPS in your user_config_override. This adds 3.5K to the code size. All devices in a group must be running firmware with device group support and have device groups enabled. @@ -13,7 +13,7 @@ To enable device groups, set Option16 to 1 and **restart the device**. The device group name is the MQTT group topic set with the GroupTopic command. All devices in the same IP network with the same group topic are in the same group. Some modules may define additional device groups. For example, if Remote Device Mode is enabled, the PWM Dimmer module defines three devices groups. -The items that are sent to the group and the items that are received from the group are selected with the DevGroupShare command. By default all items are sent and received from the group. An example of when the DevGroupShare command would be used is when you have a group of lights that you control with a dimmer switch and home automation software. You want the dimmer switch to be able to control all items. The home automation software controls each light individually. When it controls the whole group, it actually sends command to each light in the group. If you use the home automation software to turn an individual light on or off or change it’s brightness, color or scheme, you do not want the change to be replicated to the other lights. In this case, you would set the incoming and outgoing item masks to 255 on the dimmer switch (DevGroupShare 255,255) and set the incoming item mask to 255 and outgoing item mask to 0 on all the lights (DevGroupShare 255,0). +The items that are sent to the group and the items that are received from the group are selected with the DevGroupShare command. By default all items are sent and received from the group. An example of when the DevGroupShare command would be used is when you have a group of lights that you control with a dimmer switch and home automation software. You want the dimmer switch to be able to control all items. The home automation software controls each light individually. When it controls the whole group, it actually sends command to each light in the group. If you use the home automation software to turn an individual light on or off or change it’s brightness, color or scheme, you do not want the change to be replicated to the other lights. In this case, you would set the incoming and outgoing item masks to 0xffffffff (all items) on the dimmer switch (DevGroupShare 0xffffffff,0xffffffff) and set the incoming item mask to 0xffffffff and outgoing item mask to 0 on all the lights (DevGroupShare 0xffffffff,0). ### Commands @@ -29,7 +29,7 @@ The items that are sent to the group and the items that are received from the gr DevGroupShare - ,<out> = set incoming and outgoing shared item mask (default = 255,255) + ,<out> = set incoming and outgoing shared item mask (default = 0xffffffff,0xffffffff)

1 = Power, 2 = Light brightness, 4 = Light fade/speed, 8 = Light scheme, 16 = Light color, 32 = Minimum brightness @@ -42,4 +42,4 @@ The items that are sent to the group and the items that are received from the gr = set device group <x> MQTT group topic (32 chars max) and restart - + \ No newline at end of file diff --git a/tasmota/settings.h b/tasmota/settings.h index 972af6974..514cf7bdd 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -104,7 +104,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa uint32_t 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 spare03 : 1; + uint32_t device_groups_enabled : 1; // bit 3 (v8.1.0.10) - SetOption85 - Enable device groups uint32_t spare04 : 1; uint32_t spare05 : 1; uint32_t spare06 : 1; @@ -467,8 +467,10 @@ struct SYSCFG { uint8_t hotplug_scan; // F03 uint8_t reserved1; // F04 - reserved for s-hadinger - uint8_t free_f05[207]; // F05 + uint8_t free_f05[199]; // F05 + uint32_t device_group_share_in; // FCC - Bitmask of device group items imported + uint32_t device_group_share_out; // FD0 - Bitmask of device group items exported uint32_t bootcount_reset_time; // FD4 int adc_param4; // FD8 uint32_t shutter_button[MAX_KEYS]; // FDC @@ -573,9 +575,4 @@ typedef union { ADC_MODE(ADC_VCC); // Set ADC input for Power Supply Voltage usage #endif -// Settings re-purposed for device groups -#define device_groups_enabled ws_clock_reverse // SetOption16 - Enable device groups -#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 - #endif // _SETTINGS_H_ diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 772e22eeb..59d6b11d5 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -1677,7 +1677,7 @@ void CmndDevGroupShare(void) ParseParameters(2, parm); Settings.device_group_share_in = parm[0]; Settings.device_group_share_out = parm[1]; - Response_P(PSTR("{\"" D_CMND_DEVGROUP_SHARE "\":{\"In\":%d,\"Out\":%d}}"), Settings.device_group_share_in, Settings.device_group_share_out); + Response_P(PSTR("{\"" D_CMND_DEVGROUP_SHARE "\":{\"In\":%x,\"Out\":%x}}"), Settings.device_group_share_in, Settings.device_group_share_out); } #endif // USE_DEVICE_GROUPS diff --git a/tasmota/support_device_groups.ino b/tasmota/support_device_groups.ino index 336b13cad..63775d0c3 100644 --- a/tasmota/support_device_groups.ino +++ b/tasmota/support_device_groups.ino @@ -49,7 +49,7 @@ struct device_group { struct device_group * device_groups; uint16_t outgoing_sequence = 0; -bool initialized = false; +bool device_groups_active = false; bool building_status_message = false; bool processing_remote_device_message = false; bool waiting_for_acks; @@ -60,36 +60,33 @@ void DeviceGroupsInit(void) /* Initialize the device information for each device group. The group name is the MQTT group topic. */ - if (Settings.flag.device_groups_enabled) { - device_groups = (struct device_group *)calloc(device_group_count, sizeof(struct device_group)); - if (device_groups == nullptr) { - AddLog_P2(LOG_LEVEL_ERROR, "DGR: error allocating %u-element device group array", device_group_count); - Settings.flag.device_groups_enabled = false; - return; - } - - for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) { - struct device_group * device_group = &device_groups[device_group_index]; - strcpy(device_group->group_name, SettingsText((device_group_index == 0 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + device_group_index - 1))); - device_group->message_header_length = sprintf(device_group->message, "%s%s HTTP/1.1\n\n", kDeviceGroupMessage, device_group->group_name); - device_group->last_full_status_sequence = -1; - } - - device_groups[0].local = true; - - // If both in and out shared items masks are 0, assume they're unitialized and initialize them. - if (!Settings.device_group_share_in && !Settings.device_group_share_out) { - Settings.device_group_share_in = Settings.device_group_share_out = 0xff; - } - - initialized = true; + device_groups = (struct device_group *)calloc(device_group_count, sizeof(struct device_group)); + if (device_groups == nullptr) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating %u-element device group array"), device_group_count); + return; } + + for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) { + struct device_group * device_group = &device_groups[device_group_index]; + strcpy(device_group->group_name, SettingsText((device_group_index == 0 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + device_group_index - 1))); + device_group->message_header_length = sprintf_P(device_group->message, PSTR("%s%s HTTP/1.1\n\n"), kDeviceGroupMessage, device_group->group_name); + device_group->last_full_status_sequence = -1; + } + + device_groups[0].local = true; + + // If both in and out shared items masks are 0, assume they're unitialized and initialize them. + if (!Settings.device_group_share_in && !Settings.device_group_share_out) { + Settings.device_group_share_in = Settings.device_group_share_out = 0xffffffff; + } + + device_groups_active = true; } char * IPAddressToString(const IPAddress& ip_address) { static char buffer[16]; - sprintf(buffer, "%u.%u.%u.%u", ip_address[0], ip_address[1], ip_address[2], ip_address[3]); + sprintf_P(buffer, PSTR("%u.%u.%u.%u"), ip_address[0], ip_address[1], ip_address[2], ip_address[3]); return buffer; } @@ -131,13 +128,13 @@ void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * la } delay(10); } - AddLog_P2(LOG_LEVEL_ERROR, "DGR: error sending %s packet", label); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error sending %s packet"), label); } void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...) { - // If device groups are not enabled, ignore this request. - if (!Settings.flag.device_groups_enabled) return; + // If device groups are not active, ignore this request. + if (!device_groups_active) return; // If UDP is not set up, ignore this request. if (!udp_connected) return; @@ -152,7 +149,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType if (device_group->initial_status_requests_remaining) return; // A full status request is a request from a remote device for the status of every item we - // control. As long as we're building it, we may as well broadcast the status update to all + // control. As long as we're building it, we may as well multicast the status update to all // device group members. char * message_ptr = &device_group->message[device_group->message_header_length]; if (message_type == DGR_MSGTYP_FULL_STATUS) { @@ -164,7 +161,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType // Call the drivers to build the status update. if (!++outgoing_sequence) outgoing_sequence = 1; #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "Building device group %s full status packet", device_group->group_name); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s full status packet"), device_group->group_name); #endif // DEVICE_GROUPS_DEBUG device_group->message_length = 0; _SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); @@ -191,7 +188,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType va_list ap; #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "Building device group %s packet", device_group->group_name); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s packet"), device_group->group_name); #endif // DEVICE_GROUPS_DEBUG uint16_t original_sequence = outgoing_sequence; if (!building_status_message && message_type != DGR_MSGTYP_PARTIAL_UPDATE && !++outgoing_sequence) outgoing_sequence = 1; @@ -204,7 +201,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType else if (message_type == DGR_MSGTYP_UPDATE_DIRECT) value |= DGR_FLAG_DIRECT; #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, ">sequence=%u, flags=%u", outgoing_sequence, value); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">sequence=%u, flags=%u"), outgoing_sequence, value); #endif // DEVICE_GROUPS_DEBUG *message_ptr++ = value & 0xff; *message_ptr++ = value >> 8; @@ -303,7 +300,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType } } #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "%u items carried over from previous update", kept_item_count); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count); #endif // DEVICE_GROUPS_DEBUG } @@ -316,7 +313,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType value = va_arg(ap, int); if (shared) { #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, ">item=%u, value=%u", item, value); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u"), item, value); #endif // DEVICE_GROUPS_DEBUG *message_ptr++ = value & 0xff; if (item > DGR_ITEM_MAX_8BIT) { @@ -335,7 +332,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType if (shared) { value = strlen((const char *)value_ptr); #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, ">item=%u, value=%s", item, value_ptr); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%s"), item, value_ptr); #endif // DEVICE_GROUPS_DEBUG *message_ptr++ = value; memcpy(message_ptr, value_ptr, value); @@ -348,7 +345,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType value_ptr = va_arg(ap, uint8_t *); if (shared) { #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, ">item=%u, value=%u,%u,%u,%u,%u", item, *value_ptr, *(value_ptr + 1), *(value_ptr + 2), *(value_ptr + 3), *(value_ptr + 4)); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u,%u,%u,%u,%u"), item, *value_ptr, *(value_ptr + 1), *(value_ptr + 2), *(value_ptr + 3), *(value_ptr + 4)); #endif // DEVICE_GROUPS_DEBUG memmove(message_ptr, value_ptr, 5); message_ptr += 5; @@ -374,11 +371,11 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType if (building_status_message || message_type == DGR_MSGTYP_PARTIAL_UPDATE) return; } - // Broadcast the packet. + // Multicast the packet. #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "DGR: sending %u-byte device group %s packet via broadcast, sequence=%u", device_group->message_length, device_group->group_name, device_group->message[device_group->message_header_length] | device_group->message[device_group->message_header_length + 1] << 8); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via multicast, sequence=%u"), device_group->message_length, device_group->group_name, device_group->message[device_group->message_header_length] | device_group->message[device_group->message_header_length + 1] << 8); #endif // DEVICE_GROUPS_DEBUG - SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, "Broadcast"); + SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Multicast")); device_group->next_ack_check_time = millis() + 100; if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME) { @@ -393,6 +390,9 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType void ProcessDeviceGroupMessage(char * packet, int packet_length) { + // If device groups are not active, ignore this request. + if (!device_groups_active) return; + // Make the group name a null-terminated string. char * message_group_name = packet + sizeof(DEVICE_GROUP_MESSAGE) - 1; char * message_ptr = strchr(message_group_name, ' '); @@ -418,11 +418,11 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length) if (!device_group_member) { device_group_member = (struct device_group_member *)calloc(1, sizeof(struct device_group_member)); if (device_group_member == nullptr) { - AddLog_P2(LOG_LEVEL_ERROR, "DGR: error allocating device group member block"); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating device group member block")); return; } #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "DGR: adding member %s (%p)", IPAddressToString(remote_ip), device_group_member); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: adding member %s (%p)"), IPAddressToString(remote_ip), device_group_member); #endif // DEVICE_GROUPS_DEBUG device_group_member->ip_address = remote_ip; *flink = device_group_member; @@ -435,7 +435,7 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length) } // Find the start of the actual message (after the http header). - message_ptr = strstr(message_ptr, "\n\n"); + message_ptr = strstr_P(message_ptr, PSTR("\n\n")); if (message_ptr == nullptr) return; message_ptr += 2; @@ -452,7 +452,7 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length) flags = *message_ptr++; flags |= *message_ptr++ << 8; #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "Received device group %s packet from %s: sequence=%u, flags=%u", device_group->group_name, IPAddressToString(remote_ip), message_sequence, flags); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Received device group %s packet from %s: sequence=%u, flags=%u"), device_group->group_name, IPAddressToString(remote_ip), message_sequence, flags); #endif // DEVICE_GROUPS_DEBUG // If this is an ack message, save the message sequence if it's newwer than the last ack we @@ -463,7 +463,7 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length) } device_group_member->timeout_time = 0; #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "received_sequence && device_group_member->received_sequence - message_sequence > 64536) { #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "group_name, IPAddressToString(remote_ip)); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: ********** invalid item=%u received from device group %s member %s"), item, device_group->group_name, IPAddressToString(remote_ip)); } #endif // DEVICE_GROUPS_DEBUG @@ -555,7 +555,7 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length) light_fade = value; } #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "= packet_length - (message_ptr - packet)) goto badmsg; // Malformed message #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "next_ack_check_time <= now) { if (device_group->initial_status_requests_remaining) { #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "DGR: sending initial status request for group %s", device_group->group_name); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending initial status request for group %s"), device_group->group_name); #endif // DEVICE_GROUPS_DEBUG if (--device_group->initial_status_requests_remaining) { - SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, "Initial"); + SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Initial")); device_group->message[device_group->message_header_length + 2] = DGR_FLAG_STATUS_REQUEST; // The reset flag is on only for the first packet - turn it off now device_group->next_ack_check_time = now + 200; waiting_for_acks = true; @@ -671,16 +672,16 @@ void DeviceGroupsLoop(void) if (device_group_member->timeout_time && device_group_member->timeout_time < now) { #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "DGR: removing member %s (%p)", IPAddressToString(device_group_member->ip_address), device_group_member); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: removing member %s (%p)"), IPAddressToString(device_group_member->ip_address), device_group_member); #endif // DEVICE_GROUPS_DEBUG *flink = device_group_member->flink; free(device_group_member); } else { #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, "DGR: sending %u-byte device group %s packet via unicast to %s, sequence %u, last message acked=%u", device_group->message_length, device_group->group_name, IPAddressToString(device_group_member->ip_address), outgoing_sequence, device_group_member->acked_sequence); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via unicast to %s, sequence %u, last message acked=%u"), device_group->message_length, device_group->group_name, IPAddressToString(device_group_member->ip_address), outgoing_sequence, device_group_member->acked_sequence); #endif // DEVICE_GROUPS_DEBUG - SendDeviceGroupPacket(device_group_member->ip_address, device_group->message, device_group->message_length, "Unicast"); + SendDeviceGroupPacket(device_group_member->ip_address, device_group->message, device_group->message_length, PSTR("Unicast")); if (!device_group_member->timeout_time) device_group_member->timeout_time = now + 15000; acked = false; flink = &device_group_member->flink; diff --git a/tasmota/support_udp.ino b/tasmota/support_udp.ino index 6f1d12c2e..9ff745364 100644 --- a/tasmota/support_udp.ino +++ b/tasmota/support_udp.ino @@ -137,7 +137,7 @@ void PollUdp(void) } #ifdef USE_DEVICE_GROUPS - if (Settings.flag.device_groups_enabled && !strncmp_P(packet_buffer, kDeviceGroupMessage, sizeof(DEVICE_GROUP_MESSAGE) - 1)) { + if (Settings.flag4.device_groups_enabled && !strncmp_P(packet_buffer, kDeviceGroupMessage, sizeof(DEVICE_GROUP_MESSAGE) - 1)) { ProcessDeviceGroupMessage(packet_buffer, len); } #endif // USE_DEVICE_GROUPS diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index 6d902e2a9..dd8d2441c 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -560,7 +560,7 @@ void WifiCheck(uint8_t param) } #ifdef USE_EMULATION #ifdef USE_DEVICE_GROUPS - if (Settings.flag2.emulation || Settings.flag.device_groups_enabled) { UdpConnect(); } + if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { UdpConnect(); } #else // USE_DEVICE_GROUPS if (Settings.flag2.emulation) { UdpConnect(); } #endif // USE_DEVICE_GROUPS diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 8d17a0eb4..8e9546ed6 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -290,9 +290,6 @@ void setup(void) #ifdef USE_ARDUINO_OTA ArduinoOTAInit(); #endif // USE_ARDUINO_OTA -#ifdef USE_DEVICE_GROUPS - DeviceGroupsInit(); -#endif // USE_DEVICE_GROUPS XdrvCall(FUNC_INIT); XsnsCall(FUNC_INIT); diff --git a/tasmota/tasmota_post.h b/tasmota/tasmota_post.h index 39ceb8208..e27213b5a 100644 --- a/tasmota/tasmota_post.h +++ b/tasmota/tasmota_post.h @@ -249,6 +249,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef USE_EMULATION // Disable Belkin WeMo and Hue Bridge emulation for Alexa (-16k code, -2k mem) #undef USE_EMULATION_HUE // Disable Hue Bridge emulation for Alexa (+14k code, +2k mem common) #undef USE_EMULATION_WEMO // Disable Belkin WeMo emulation for Alexa (+6k code, +2k mem common) +#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code) #undef DEBUG_THEO // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code #endif // FIRMWARE_KNX_NO_EMULATION @@ -286,6 +287,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code) #undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer #undef USE_HOTPLUG // Disable support for HotPlug +#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code) #undef USE_ENERGY_SENSOR // Disable energy sensors (-14k code) #undef USE_PZEM004T // Disable PZEM004T energy sensor @@ -362,6 +364,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code) #undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer #undef USE_HOTPLUG // Disable support for HotPlug +#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code) // -- Optional light modules ---------------------- //#undef USE_LIGHT // Also disable all Dimmer/Light support @@ -583,6 +586,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code) #undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer #undef USE_HOTPLUG // Disable support for HotPlug +#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code) // -- Optional light modules ---------------------- #undef USE_LIGHT // Disable support for lights diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 3eaa24ef5..e8c728024 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -2971,7 +2971,7 @@ bool Xdrv01(uint8_t function) PollDnsWebserver(); #ifdef USE_EMULATION #ifdef USE_DEVICE_GROUPS - if (Settings.flag2.emulation || Settings.flag.device_groups_enabled) { PollUdp(); } + if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { PollUdp(); } #else // USE_DEVICE_GROUPS if (Settings.flag2.emulation) { PollUdp(); } #endif // USE_DEVICE_GROUPS From 9c5e5b689ee6560a457f8fed53316f99b5d41be3 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Mon, 24 Feb 2020 18:40:44 -0600 Subject: [PATCH 2/4] Improve dgr initialization failure handling --- tasmota/support_device_groups.ino | 35 ++++++++++++++----------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/tasmota/support_device_groups.ino b/tasmota/support_device_groups.ino index 63775d0c3..7036169e8 100644 --- a/tasmota/support_device_groups.ino +++ b/tasmota/support_device_groups.ino @@ -22,7 +22,7 @@ */ #ifdef USE_DEVICE_GROUPS -//#define DEVICE_GROUPS_DEBUG +#define DEVICE_GROUPS_DEBUG extern bool udp_connected; @@ -49,7 +49,8 @@ struct device_group { struct device_group * device_groups; uint16_t outgoing_sequence = 0; -bool device_groups_active = false; +bool device_groups_initialized = false; +bool device_groups_initialization_failed = false; bool building_status_message = false; bool processing_remote_device_message = false; bool waiting_for_acks; @@ -57,12 +58,11 @@ bool udp_was_connected = false; void DeviceGroupsInit(void) { - /* - Initialize the device information for each device group. The group name is the MQTT group topic. - */ + // Initialize the device information for each device group. The group name is the MQTT group topic. device_groups = (struct device_group *)calloc(device_group_count, sizeof(struct device_group)); if (device_groups == nullptr) { AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating %u-element device group array"), device_group_count); + device_groups_initialization_failed = true; return; } @@ -80,7 +80,7 @@ void DeviceGroupsInit(void) Settings.device_group_share_in = Settings.device_group_share_out = 0xffffffff; } - device_groups_active = true; + device_groups_initialized = true; } char * IPAddressToString(const IPAddress& ip_address) @@ -133,8 +133,8 @@ void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * la void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...) { - // If device groups are not active, ignore this request. - if (!device_groups_active) return; + // If device groups are not enabled, ignore this request. + if (!Settings.flag4.device_groups_enabled) return; // If UDP is not set up, ignore this request. if (!udp_connected) return; @@ -164,7 +164,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s full status packet"), device_group->group_name); #endif // DEVICE_GROUPS_DEBUG device_group->message_length = 0; - _SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); + SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); XdrvMailbox.command_code = DGR_ITEM_STATUS; XdrvCall(FUNC_DEVICE_GROUP_REQUEST); building_status_message = false; @@ -300,7 +300,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType } } #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count); #endif // DEVICE_GROUPS_DEBUG } @@ -390,9 +390,6 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType void ProcessDeviceGroupMessage(char * packet, int packet_length) { - // If device groups are not active, ignore this request. - if (!device_groups_active) return; - // Make the group name a null-terminated string. char * message_group_name = packet + sizeof(DEVICE_GROUP_MESSAGE) - 1; char * message_ptr = strchr(message_group_name, ' '); @@ -617,14 +614,12 @@ void DeviceGroupsLoop(void) { if (!Settings.flag4.device_groups_enabled) return; if (udp_connected) { - if (!device_groups_active) { - DeviceGroupsInit(); - if (!device_groups_active) return; - } - if (!udp_was_connected) { udp_was_connected = true; + if (!device_groups_initialized) DeviceGroupsInit(); + if (device_groups_initialization_failed) return; + for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) { device_group * device_group = &device_groups[device_group_index]; char * message_ptr = &device_group->message[device_group->message_header_length]; @@ -635,12 +630,14 @@ void DeviceGroupsLoop(void) *message_ptr++ = 0; device_group->message_length = message_ptr - device_group->message; device_group->initial_status_requests_remaining = 5; - device_group->next_ack_check_time = millis() + 2000; + device_group->next_ack_check_time = millis() + 1000; } waiting_for_acks = true; } + if (device_groups_initialization_failed) return; + if (waiting_for_acks) { uint32_t now = millis(); waiting_for_acks = false; From e1103b248fe09e1f3b44f2ca50797c0e5b6dd53a Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Mon, 24 Feb 2020 19:07:46 -0600 Subject: [PATCH 3/4] Improve dgr initialization failure handling --- tasmota/my_user_config.h | 2 +- tasmota/support_device_groups.ino | 33 +- tasmota/tasmota.ino.cpp | 78659 ++++++++++++++++++++++++++++ 3 files changed, 78675 insertions(+), 19 deletions(-) create mode 100644 tasmota/tasmota.ino.cpp diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 931996bf4..836fbeb10 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -415,7 +415,7 @@ //#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 (+4k code) // -- Optional light modules ---------------------- #define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by // diff --git a/tasmota/support_device_groups.ino b/tasmota/support_device_groups.ino index 63775d0c3..c94ea2f57 100644 --- a/tasmota/support_device_groups.ino +++ b/tasmota/support_device_groups.ino @@ -49,7 +49,8 @@ struct device_group { struct device_group * device_groups; uint16_t outgoing_sequence = 0; -bool device_groups_active = false; +bool device_groups_initialized = false; +bool device_groups_initialization_failed = false; bool building_status_message = false; bool processing_remote_device_message = false; bool waiting_for_acks; @@ -57,12 +58,11 @@ bool udp_was_connected = false; void DeviceGroupsInit(void) { - /* - Initialize the device information for each device group. The group name is the MQTT group topic. - */ + // Initialize the device information for each device group. The group name is the MQTT group topic. device_groups = (struct device_group *)calloc(device_group_count, sizeof(struct device_group)); if (device_groups == nullptr) { AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating %u-element device group array"), device_group_count); + device_groups_initialization_failed = true; return; } @@ -80,7 +80,7 @@ void DeviceGroupsInit(void) Settings.device_group_share_in = Settings.device_group_share_out = 0xffffffff; } - device_groups_active = true; + device_groups_initialized = true; } char * IPAddressToString(const IPAddress& ip_address) @@ -133,8 +133,8 @@ void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * la void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...) { - // If device groups are not active, ignore this request. - if (!device_groups_active) return; + // If device groups are not enabled, ignore this request. + if (!Settings.flag4.device_groups_enabled) return; // If UDP is not set up, ignore this request. if (!udp_connected) return; @@ -164,7 +164,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s full status packet"), device_group->group_name); #endif // DEVICE_GROUPS_DEBUG device_group->message_length = 0; - _SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); + SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); XdrvMailbox.command_code = DGR_ITEM_STATUS; XdrvCall(FUNC_DEVICE_GROUP_REQUEST); building_status_message = false; @@ -300,7 +300,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType } } #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count); #endif // DEVICE_GROUPS_DEBUG } @@ -390,9 +390,6 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType void ProcessDeviceGroupMessage(char * packet, int packet_length) { - // If device groups are not active, ignore this request. - if (!device_groups_active) return; - // Make the group name a null-terminated string. char * message_group_name = packet + sizeof(DEVICE_GROUP_MESSAGE) - 1; char * message_ptr = strchr(message_group_name, ' '); @@ -617,14 +614,12 @@ void DeviceGroupsLoop(void) { if (!Settings.flag4.device_groups_enabled) return; if (udp_connected) { - if (!device_groups_active) { - DeviceGroupsInit(); - if (!device_groups_active) return; - } - if (!udp_was_connected) { udp_was_connected = true; + if (!device_groups_initialized) DeviceGroupsInit(); + if (device_groups_initialization_failed) return; + for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) { device_group * device_group = &device_groups[device_group_index]; char * message_ptr = &device_group->message[device_group->message_header_length]; @@ -635,12 +630,14 @@ void DeviceGroupsLoop(void) *message_ptr++ = 0; device_group->message_length = message_ptr - device_group->message; device_group->initial_status_requests_remaining = 5; - device_group->next_ack_check_time = millis() + 2000; + device_group->next_ack_check_time = millis() + 1000; } waiting_for_acks = true; } + if (device_groups_initialization_failed) return; + if (waiting_for_acks) { uint32_t now = millis(); waiting_for_acks = false; diff --git a/tasmota/tasmota.ino.cpp b/tasmota/tasmota.ino.cpp new file mode 100644 index 000000000..8f8387ba2 --- /dev/null +++ b/tasmota/tasmota.ino.cpp @@ -0,0 +1,78659 @@ +# 1 "C:\\Users\\pcdie\\AppData\\Local\\Temp\\tmpnb0ml5sg" +#include +# 1 "S:/Development/Tasmota/tasmota/tasmota.ino" +# 29 "S:/Development/Tasmota/tasmota/tasmota.ino" +#include +#include "tasmota_version.h" +#include "tasmota.h" +#include "my_user_config.h" +#ifdef USE_MQTT_TLS + #include +#endif +#include "tasmota_post.h" +#include "i18n.h" +#include "tasmota_template.h" + +#ifdef ARDUINO_ESP8266_RELEASE_2_4_0 +#include "lwip/init.h" +#if LWIP_VERSION_MAJOR != 1 + #error Please use stable lwIP v1.4 +#endif +#endif + + +#include +#include +#include +#include +#ifdef USE_ARDUINO_OTA + #include + #ifndef USE_DISCOVERY + #define USE_DISCOVERY + #endif +#endif +#ifdef USE_DISCOVERY + #include +#endif +#ifdef USE_I2C + #include +#endif +#ifdef USE_SPI + #include +#endif + + +#include "settings.h" + + + + + +WiFiUDP PortUdp; + +unsigned long feature_drv1; +unsigned long feature_drv2; +unsigned long feature_sns1; +unsigned long feature_sns2; +unsigned long feature5; +unsigned long feature6; +unsigned long serial_polling_window = 0; +unsigned long state_second = 0; +unsigned long state_50msecond = 0; +unsigned long state_100msecond = 0; +unsigned long state_250msecond = 0; +unsigned long pulse_timer[MAX_PULSETIMERS] = { 0 }; +unsigned long blink_timer = 0; +unsigned long backlog_delay = 0; +power_t power = 0; +power_t last_power = 0; +power_t blink_power; +power_t blink_mask = 0; +power_t blink_powersave; +power_t latching_power = 0; +power_t rel_inverted = 0; +int serial_in_byte_counter = 0; +int ota_state_flag = 0; +int ota_result = 0; +int restart_flag = 0; +int wifi_state_flag = WIFI_RESTART; +int blinks = 201; +uint32_t uptime = 0; +uint32_t loop_load_avg = 0; +uint32_t global_update = 0; +uint32_t web_log_index = 1; +float global_temperature = 9999; +float global_humidity = 0; +float global_pressure = 0; +uint16_t tele_period = 9999; +uint16_t blink_counter = 0; +uint16_t seriallog_timer = 0; +uint16_t syslog_timer = 0; +int16_t save_data_counter; +RulesBitfield rules_flag; +uint8_t mqtt_cmnd_blocked = 0; +uint8_t mqtt_cmnd_blocked_reset = 0; +uint8_t state_250mS = 0; +uint8_t latching_relay_pulse = 0; +uint8_t sleep; +uint8_t blinkspeed = 1; +uint8_t pin[GPIO_MAX]; +uint8_t active_device = 1; +uint8_t leds_present = 0; +uint8_t led_inverted = 0; +uint8_t led_power = 0; +uint8_t ledlnk_inverted = 0; +uint8_t pwm_inverted = 0; +uint8_t energy_flg = 0; +uint8_t light_flg = 0; +uint8_t light_type = 0; +uint8_t serial_in_byte; +uint8_t ota_retry_counter = OTA_ATTEMPTS; +uint8_t devices_present = 0; +uint8_t seriallog_level; +uint8_t syslog_level; +uint8_t my_module_type; +uint8_t my_adc0; +uint8_t last_source = 0; +uint8_t shutters_present = 0; +uint8_t prepped_loglevel = 0; + +bool serial_local = false; +bool fallback_topic_flag = false; +bool backlog_mutex = false; +bool interlock_mutex = false; +bool stop_flash_rotate = false; +bool blinkstate = false; + +bool pwm_present = false; +bool i2c_flg = false; +bool spi_flg = false; +bool soft_spi_flg = false; +bool ntp_force_sync = false; +bool is_8285 = false; +myio my_module; +gpio_flag my_module_flag; +StateBitfield global_state; +char my_version[33]; +char my_image[33]; +char my_hostname[33]; +char mqtt_client[TOPSZ]; +char mqtt_topic[TOPSZ]; +char serial_in_buffer[INPUT_BUFFER_SIZE]; +char mqtt_data[MESSZ]; +char log_data[LOGSZ]; +char web_log[WEB_LOG_SIZE] = {'\0'}; +#ifdef SUPPORT_IF_STATEMENT + #include + LinkedList backlog; + #define BACKLOG_EMPTY (backlog.size() == 0) +#else + uint8_t backlog_index = 0; + uint8_t backlog_pointer = 0; + String backlog[MAX_BACKLOG]; + #define BACKLOG_EMPTY (backlog_pointer == backlog_index) +#endif +void setup(void); +void BacklogLoop(void); +void loop(void); +uint16_t SendMail(char *buffer); +uint32_t GetRtcSettingsCrc(void); +void RtcSettingsSave(void); +void RtcSettingsLoad(void); +bool RtcSettingsValid(void); +uint32_t GetRtcRebootCrc(void); +void RtcRebootSave(void); +void RtcRebootReset(void); +void RtcRebootLoad(void); +bool RtcRebootValid(void); +void SetFlashModeDout(void); +bool VersionCompatible(void); +void SettingsBufferFree(void); +bool SettingsBufferAlloc(void); +uint16_t GetCfgCrc16(uint8_t *bytes, uint32_t size); +uint16_t GetSettingsCrc(void); +uint32_t GetCfgCrc32(uint8_t *bytes, uint32_t size); +uint32_t GetSettingsCrc32(void); +void SettingsSaveAll(void); +void UpdateQuickPowerCycle(bool update); +uint32_t GetSettingsTextLen(void); +bool SettingsUpdateText(uint32_t index, const char* replace_me); +char* SettingsText(uint32_t index); +uint32_t GetSettingsAddress(void); +void SettingsSave(uint8_t rotate); +void SettingsLoad(void); +void EspErase(uint32_t start_sector, uint32_t end_sector); +void SettingsErase(uint8_t type); +void SettingsSdkErase(void); +void SettingsSdkWifiErase(void); +void SettingsDefault(void); +void SettingsDefaultSet1(void); +void SettingsDefaultSet2(void); +void SettingsResetStd(void); +void SettingsResetDst(void); +void SettingsDefaultWebColor(void); +void SettingsEnableAllI2cDrivers(void); +void SettingsDelta(void); +void OsWatchTicker(void); +void OsWatchInit(void); +void OsWatchLoop(void); +bool OsWatchBlockedLoop(void); +uint32_t ResetReason(void); +String GetResetReason(void); +size_t strchrspn(const char *str1, int character); +char* subStr(char* dest, char* str, const char *delim, int index); +float CharToFloat(const char *str); +int TextToInt(char *str); +char* ulltoa(unsigned long long value, char *str, int radix); +char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween); +char* Uint64toHex(uint64_t value, char *str, uint16_t bits); +char* dtostrfd(double number, unsigned char prec, char *s); +char* Unescape(char* buffer, uint32_t* size); +char* RemoveSpace(char* p); +char* ReplaceCommaWithDot(char* p); +char* LowerCase(char* dest, const char* source); +char* UpperCase(char* dest, const char* source); +char* UpperCase_P(char* dest, const char* source); +char* Trim(char* p); +char* RemoveAllSpaces(char* p); +char* NoAlNumToUnderscore(char* dest, const char* source); +char IndexSeparator(void); +void SetShortcutDefault(void); +uint8_t Shortcut(void); +bool ValidIpAddress(const char* str); +bool ParseIp(uint32_t* addr, const char* str); +uint32_t ParseParameters(uint32_t count, uint32_t *params); +bool NewerVersion(char* version_str); +char* GetPowerDevice(char* dest, uint32_t idx, size_t size, uint32_t option); +char* GetPowerDevice(char* dest, uint32_t idx, size_t size); +void GetEspHardwareType(void); +String GetDeviceHardware(void); +float ConvertTemp(float c); +float ConvertTempToCelsius(float c); +char TempUnit(void); +float ConvertHumidity(float h); +float ConvertPressure(float p); +String PressureUnit(void); +void ResetGlobalValues(void); +uint32_t SqrtInt(uint32_t num); +uint32_t RoundSqrtInt(uint32_t num); +char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack); +int GetCommandCode(char* destination, size_t destination_size, const char* needle, const char* haystack); +int GetStateNumber(char *state_text); +String GetSerialConfig(void); +void SetSerialBegin(); +void SetSerialConfig(uint32_t serial_config); +void SetSerialBaudrate(uint32_t baudrate); +void SetSerial(uint32_t baudrate, uint32_t serial_config); +void ClaimSerial(void); +void SerialSendRaw(char *codes); +uint32_t GetHash(const char *buffer, size_t size); +void ShowSource(uint32_t source); +void WebHexCode(uint32_t i, const char* code); +uint32_t WebColor(uint32_t i); +char* ResponseGetTime(uint32_t format, char* time_str); +int Response_P(const char* format, ...); +int ResponseTime_P(const char* format, ...); +int ResponseAppend_P(const char* format, ...); +int ResponseAppendTimeFormat(uint32_t format); +int ResponseAppendTime(void); +int ResponseJsonEnd(void); +int ResponseJsonEndEnd(void); +void DigitalWrite(uint32_t gpio_pin, uint32_t state); +uint8_t ModuleNr(void); +bool ValidTemplateModule(uint32_t index); +bool ValidModule(uint32_t index); +String AnyModuleName(uint32_t index); +String ModuleName(void); +void ModuleGpios(myio *gp); +gpio_flag ModuleFlag(void); +void ModuleDefault(uint32_t module); +void SetModuleType(void); +bool FlashPin(uint32_t pin); +uint8_t ValidPin(uint32_t pin, uint32_t gpio); +bool ValidGPIO(uint32_t pin, uint32_t gpio); +bool ValidAdc(void); +bool GetUsedInModule(uint32_t val, uint8_t *arr); +bool JsonTemplate(const char* dataBuf); +void TemplateJson(void); +inline int32_t TimeDifference(uint32_t prev, uint32_t next); +int32_t TimePassedSince(uint32_t timestamp); +bool TimeReached(uint32_t timer); +void SetNextTimeInterval(unsigned long& timer, const unsigned long step); +int32_t TimePassedSinceUsec(uint32_t timestamp); +bool TimeReachedUsec(uint32_t timer); +bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size); +bool I2cValidRead8(uint8_t *data, uint8_t addr, uint8_t reg); +bool I2cValidRead16(uint16_t *data, uint8_t addr, uint8_t reg); +bool I2cValidReadS16(int16_t *data, uint8_t addr, uint8_t reg); +bool I2cValidRead16LE(uint16_t *data, uint8_t addr, uint8_t reg); +bool I2cValidReadS16_LE(int16_t *data, uint8_t addr, uint8_t reg); +bool I2cValidRead24(int32_t *data, uint8_t addr, uint8_t reg); +uint8_t I2cRead8(uint8_t addr, uint8_t reg); +uint16_t I2cRead16(uint8_t addr, uint8_t reg); +int16_t I2cReadS16(uint8_t addr, uint8_t reg); +uint16_t I2cRead16LE(uint8_t addr, uint8_t reg); +int16_t I2cReadS16_LE(uint8_t addr, uint8_t reg); +int32_t I2cRead24(uint8_t addr, uint8_t reg); +bool I2cWrite(uint8_t addr, uint8_t reg, uint32_t val, uint8_t size); +bool I2cWrite8(uint8_t addr, uint8_t reg, uint16_t val); +bool I2cWrite16(uint8_t addr, uint8_t reg, uint16_t val); +int8_t I2cReadBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len); +int8_t I2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len); +void I2cScan(char *devs, unsigned int devs_len); +void I2cSetActiveFound(uint32_t addr, const char *types); +bool I2cActive(uint32_t addr); +bool I2cSetDevice(uint32_t addr); +void SetSeriallog(uint32_t loglevel); +void SetSyslog(uint32_t loglevel); +void GetLog(uint32_t idx, char** entry_pp, size_t* len_p); +void Syslog(void); +void AddLog(uint32_t loglevel); +void AddLog_P(uint32_t loglevel, const char *formatP); +void AddLog_P(uint32_t loglevel, const char *formatP, const char *formatP2); +void PrepLog_P2(uint32_t loglevel, PGM_P formatP, ...); +void AddLog_P2(uint32_t loglevel, PGM_P formatP, ...); +void AddLog_Debug(PGM_P formatP, ...); +void AddLogBuffer(uint32_t loglevel, uint8_t *buffer, uint32_t count); +void AddLogSerial(uint32_t loglevel); +void AddLogMissed(char *sensor, uint32_t misses); +void ButtonPullupFlag(uint8 button_bit); +void ButtonInvertFlag(uint8 button_bit); +void ButtonInit(void); +uint8_t ButtonSerial(uint8_t serial_in_byte); +void ButtonHandler(void); +void ButtonLoop(void); +void ResponseCmndNumber(int value); +void ResponseCmndFloat(float value, uint32_t decimals); +void ResponseCmndIdxNumber(int value); +void ResponseCmndChar(const char* value); +void ResponseCmndStateText(uint32_t value); +void ResponseCmndDone(void); +void ResponseCmndIdxChar(const char* value); +void ResponseCmndAll(uint32_t text_index, uint32_t count); +void ExecuteCommand(const char *cmnd, uint32_t source); +void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len); +void CmndBacklog(void); +void CmndDelay(void); +void CmndPower(void); +void CmndStatus(void); +void CmndState(void); +void CmndTempOffset(void); +void CmndSleep(void); +void CmndUpgrade(void); +void CmndOtaUrl(void); +void CmndSeriallog(void); +void CmndRestart(void); +void CmndPowerOnState(void); +void CmndPulsetime(void); +void CmndBlinktime(void); +void CmndBlinkcount(void); +void CmndSavedata(void); +void CmndSetoption(void); +void CmndTemperatureResolution(void); +void CmndHumidityResolution(void); +void CmndPressureResolution(void); +void CmndPowerResolution(void); +void CmndVoltageResolution(void); +void CmndFrequencyResolution(void); +void CmndCurrentResolution(void); +void CmndEnergyResolution(void); +void CmndWeightResolution(void); +void CmndModule(void); +void CmndModules(void); +void CmndGpio(void); +void CmndGpios(void); +void CmndTemplate(void); +void CmndPwm(void); +void CmndPwmfrequency(void); +void CmndPwmrange(void); +void CmndButtonDebounce(void); +void CmndSwitchDebounce(void); +void CmndBaudrate(void); +void CmndSerialConfig(void); +void CmndSerialSend(void); +void CmndSerialDelimiter(void); +void CmndSyslog(void); +void CmndLoghost(void); +void CmndLogport(void); +void CmndIpAddress(void); +void CmndNtpServer(void); +void CmndAp(void); +void CmndSsid(void); +void CmndPassword(void); +void CmndHostname(void); +void CmndWifiConfig(void); +void CmndFriendlyname(void); +void CmndSwitchMode(void); +void CmndInterlock(void); +void CmndTeleperiod(void); +void CmndReset(void); +void CmndTime(void); +void CmndTimezone(void); +void CmndTimeStdDst(uint32_t ts); +void CmndTimeStd(void); +void CmndTimeDst(void); +void CmndAltitude(void); +void CmndLedPower(void); +void CmndLedState(void); +void CmndLedMask(void); +void CmndWifiPower(void); +void CmndI2cScan(void); +void CmndI2cDriver(void); +void CmndDevGroupShare(void); +void CmndSensor(void); +void CmndDriver(void); +void CmndCrash(void); +void CmndWDT(void); +void CmndBlockedLoop(void); +void CrashDumpClear(void); +bool CrashFlag(void); +void CrashDump(void); +void DeviceGroupsInit(void); +bool DeviceGroupItemShared(bool incoming, uint8_t item); +void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * label); +void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...); +void ProcessDeviceGroupMessage(char * packet, int packet_length); +void DeviceGroupsLoop(void); +static bool spiflash_is_ready(void); +static void spi_write_enable(void); +bool EsptoolEraseSector(uint32_t sector); +void EsptoolErase(uint32_t start_sector, uint32_t end_sector); +void GetFeatures(void); +float fmodf(float x, float y); +double FastPrecisePow(double a, double b); +float FastPrecisePowf(const float x, const float y); +double TaylorLog(double x); +inline float sinf(float x); +inline float cosf(float x); +inline float tanf(float x); +inline float atanf(float x); +inline float asinf(float x); +inline float acosf(float x); +inline float sqrtf(float x); +inline float powf(float x, float y); +float cos_52s(float x); +float cos_52(float x); +float sin_52(float x); +float tan_56s(float x); +float tan_56(float x); +float atan_66s(float x); +float atan_66(float x); +float asinf1(float x); +float acosf1(float x); +float sqrt1(const float x); +uint16_t changeUIntScale(uint16_t inum, uint16_t ifrom_min, uint16_t ifrom_max, + uint16_t ito_min, uint16_t ito_max); +void* memchr(const void* ptr, int value, size_t num); +size_t strcspn(const char *str1, const char *str2); +char* strpbrk(const char *s1, const char *s2); +void* memmove_P(void *dest, const void *src, size_t n); +void update_rotary(void); +bool RotaryButtonPressed(void); +void RotaryInit(void); +void RotaryHandler(void); +void RotaryLoop(void); +uint32_t UtcTime(void); +uint32_t LocalTime(void); +uint32_t Midnight(void); +bool MidnightNow(void); +bool IsDst(void); +String GetBuildDateAndTime(void); +String GetMinuteTime(uint32_t minutes); +String GetTimeZone(void); +String GetDuration(uint32_t time); +String GetDT(uint32_t time); +String GetDateAndTime(uint8_t time_type); +uint32_t UpTime(void); +uint32_t MinutesUptime(void); +String GetUptime(void); +uint32_t MinutesPastMidnight(void); +void BreakTime(uint32_t time_input, TIME_T &tm); +uint32_t MakeTime(TIME_T &tm); +uint32_t RuleToTime(TimeRule r, int yr); +void RtcSecond(void); +void RtcSetTime(uint32_t epoch); +void RtcInit(void); +String GetStatistics(void); +String GetStatistics(void); +void SwitchPullupFlag(uint16 switch_bit); +void SwitchSetVirtual(uint32_t index, uint8_t state); +uint8_t SwitchGetVirtual(uint32_t index); +uint8_t SwitchLastState(uint32_t index); +bool SwitchState(uint32_t index); +void SwitchProbe(void); +void SwitchInit(void); +void SwitchHandler(uint8_t mode); +void SwitchLoop(void); +char* Format(char* output, const char* input, int size); +char* GetOtaUrl(char *otaurl, size_t otaurl_size); +char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopic); +char* GetGroupTopic_P(char *stopic, const char* subtopic); +char* GetFallbackTopic_P(char *stopic, const char* subtopic); +char* GetStateText(uint32_t state); +void SetLatchingRelay(power_t lpower, uint32_t state); +void SetDevicePower(power_t rpower, uint32_t source); +void RestorePower(bool publish_power, uint32_t source); +void SetAllPower(uint32_t state, uint32_t source); +void SetPowerOnState(void); +void SetLedPowerIdx(uint32_t led, uint32_t state); +void SetLedPower(uint32_t state); +void SetLedPowerAll(uint32_t state); +void SetLedLink(uint32_t state); +void SetPulseTimer(uint32_t index, uint32_t time); +uint32_t GetPulseTimer(uint32_t index); +bool SendKey(uint32_t key, uint32_t device, uint32_t state); +void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source); +void StopAllPowerBlink(void); +void MqttShowPWMState(void); +void MqttShowState(void); +void MqttPublishTeleState(void); +bool MqttShowSensor(void); +void MqttPublishSensor(void); +void PerformEverySecond(void); +void Every100mSeconds(void); +void Every250mSeconds(void); +void ArduinoOTAInit(void); +void ArduinoOtaLoop(void); +void SerialInput(void); +void GpioInit(void); +bool UdpDisconnect(void); +bool UdpConnect(void); +void PollUdp(void); +int WifiGetRssiAsQuality(int rssi); +bool WifiConfigCounter(void); +void WifiConfig(uint8_t type); +void WifiSetMode(WiFiMode_t wifi_mode); +void WiFiSetSleepMode(void); +void WifiBegin(uint8_t flag, uint8_t channel); +void WifiBeginAfterScan(void); +uint16_t WifiLinkCount(void); +String WifiDowntime(void); +void WifiSetState(uint8_t state); +bool WifiCheckIPv6(void); +String WifiGetIPv6(void); +bool WifiCheckIPAddrStatus(void); +void WifiCheckIp(void); +void WifiCheck(uint8_t param); +int WifiState(void); +String WifiGetOutputPower(void); +void WifiSetOutputPower(void); +void WifiConnect(void); +void WifiDisconnect(void); +void WifiShutdown(void); +void EspRestart(void); +static void WebGetArg(const char* arg, char* out, size_t max); +static bool WifiIsInManagerMode(); +void ShowWebSource(uint32_t source); +void ExecuteWebCommand(char* svalue, uint32_t source); +void StartWebserver(int type, IPAddress ipweb); +void StopWebserver(void); +void WifiManagerBegin(bool reset_only); +void PollDnsWebserver(void); +bool WebAuthenticate(void); +void HttpHeaderCors(void); +void WSHeaderSend(void); +void WSSend(int code, int ctype, const String& content); +void WSContentBegin(int code, int ctype); +void _WSContentSend(const String& content); +void WSContentFlush(void); +void _WSContentSendBuffer(void); +void WSContentSend_P(const char* formatP, ...); +void WSContentSend_PD(const char* formatP, ...); +void WSContentStart_P(const char* title, bool auth); +void WSContentStart_P(const char* title); +void WSContentSendStyle_P(const char* formatP, ...); +void WSContentSendStyle(void); +void WSContentButton(uint32_t title_index); +void WSContentSpaceButton(uint32_t title_index); +void WSContentEnd(void); +void WSContentStop(void); +void WebRestart(uint32_t type); +void HandleWifiLogin(void); +void WebSliderColdWarm(void); +void HandleRoot(void); +bool HandleRootStatusRefresh(void); +int32_t IsShutterWebButton(uint32_t idx); +void HandleConfiguration(void); +void HandleTemplateConfiguration(void); +void TemplateSaveSettings(void); +void HandleModuleConfiguration(void); +void ModuleSaveSettings(void); +String HtmlEscape(const String unescaped); +void HandleWifiConfiguration(void); +void WifiSaveSettings(void); +void HandleLoggingConfiguration(void); +void LoggingSaveSettings(void); +void HandleOtherConfiguration(void); +void OtherSaveSettings(void); +void HandleBackupConfiguration(void); +void HandleResetConfiguration(void); +void HandleRestoreConfiguration(void); +void HandleInformation(void); +void HandleUpgradeFirmware(void); +void HandleUpgradeFirmwareStart(void); +void HandleUploadDone(void); +void HandleUploadLoop(void); +void HandlePreflightRequest(void); +void HandleHttpCommand(void); +void HandleConsole(void); +void HandleConsoleRefresh(void); +void HandleNotFound(void); +bool CaptivePortal(void); +String UrlEncode(const String& text); +int WebSend(char *buffer); +bool JsonWebColor(const char* dataBuf); +void CmndEmulation(void); +void CmndSendmail(void); +void CmndWebServer(void); +void CmndWebPassword(void); +void CmndWeblog(void); +void CmndWebRefresh(void); +void CmndWebSend(void); +void CmndWebColor(void); +void CmndWebSensor(void); +void CmndWebButton(void); +void CmndCors(void); +bool Xdrv01(uint8_t function); +bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value); +void MakeValidMqtt(uint32_t option, char* str); +void MqttDiscoverServer(void); +void MqttInit(void); +bool MqttIsConnected(void); +void MqttDisconnect(void); +void MqttSubscribeLib(const char *topic); +void MqttUnsubscribeLib(const char *topic); +bool MqttPublishLib(const char* topic, bool retained); +void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len); +void MqttRetryCounter(uint8_t value); +void MqttSubscribe(const char *topic); +void MqttUnsubscribe(const char *topic); +void MqttPublishLogging(const char *mxtime); +void MqttPublish(const char* topic, bool retained); +void MqttPublish(const char* topic); +void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retained); +void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic); +void MqttPublishTeleSensor(void); +void MqttPublishPowerState(uint32_t device); +void MqttPublishAllPowerState(void); +void MqttPublishPowerBlinkState(uint32_t device); +uint16_t MqttConnectCount(void); +void MqttDisconnected(int state); +void MqttConnected(void); +void MqttReconnect(void); +void MqttCheck(void); +void CmndMqttFingerprint(void); +void CmndMqttUser(void); +void CmndMqttPassword(void); +void CmndMqttlog(void); +void CmndMqttHost(void); +void CmndMqttPort(void); +void CmndMqttRetry(void); +void CmndStateText(void); +void CmndMqttClient(void); +void CmndFullTopic(void); +void CmndPrefix(void); +void CmndPublish(void); +void CmndGroupTopic(void); +void CmndTopic(void); +void CmndButtonTopic(void); +void CmndSwitchTopic(void); +void CmndButtonRetain(void); +void CmndSwitchRetain(void); +void CmndPowerRetain(void); +void CmndSensorRetain(void); +inline void TlsEraseBuffer(uint8_t *buffer); +void loadTlsDir(void); +void CmndTlsKey(void); +uint32_t bswap32(uint32_t x); +void CmndTlsDump(void); +void HandleMqttConfiguration(void); +void MqttSaveSettings(void); +bool Xdrv02(uint8_t function); +bool EnergyTariff1Active(); +void EnergyUpdateToday(void); +void EnergyUpdateTotal(float value, bool kwh); +void Energy200ms(void); +void EnergySaveState(void); +bool EnergyMargin(bool type, uint16_t margin, uint16_t value, bool &flag, bool &save_flag); +void EnergyMarginCheck(void); +void EnergyMqttShow(void); +void EnergyEverySecond(void); +void EnergyCommandCalResponse(uint32_t nvalue); +void CmndEnergyReset(void); +void CmndTariff(void); +void CmndPowerCal(void); +void CmndVoltageCal(void); +void CmndCurrentCal(void); +void CmndPowerSet(void); +void CmndVoltageSet(void); +void CmndCurrentSet(void); +void CmndFrequencySet(void); +void CmndModuleAddress(void); +void CmndPowerDelta(void); +void CmndPowerLow(void); +void CmndPowerHigh(void); +void CmndVoltageLow(void); +void CmndVoltageHigh(void); +void CmndCurrentLow(void); +void CmndCurrentHigh(void); +void CmndMaxPower(void); +void CmndMaxPowerHold(void); +void CmndMaxPowerWindow(void); +void CmndSafePower(void); +void CmndSafePowerHold(void); +void CmndSafePowerWindow(void); +void CmndMaxEnergy(void); +void CmndMaxEnergyStart(void); +void EnergySnsInit(void); +void EnergyShow(bool json); +bool Xdrv03(uint8_t function); +bool Xsns03(uint8_t function); +power_t LightPower(void); +power_t LightPowerIRAM(void); +uint8_t LightDevice(void); +static uint32_t min3(uint32_t a, uint32_t b, uint32_t c); +uint16_t change8to10(uint8_t v); +uint8_t change10to8(uint16_t v); +uint16_t ledGamma_internal(uint16_t v, const struct gamma_table_t *gt_ptr); +uint16_t ledGammaReverse_internal(uint16_t vg, const struct gamma_table_t *gt_ptr); +uint16_t ledGamma10_10(uint16_t v); +uint16_t ledGamma10(uint8_t v); +uint8_t ledGamma(uint8_t v); +void LightPwmOffset(uint32_t offset); +bool LightModuleInit(void); +void LightInit(void); +void LightUpdateColorMapping(void); +uint8_t LightGetDimmer(uint8_t dimmer); +void LightSetDimmer(uint8_t dimmer); +void LightGetHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri); +void LightHsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b); +uint8_t LightGetBri(uint8_t device); +void LightSetBri(uint8_t device, uint8_t bri); +void LightSetColorTemp(uint16_t ct); +uint16_t LightGetColorTemp(void); +void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value); +void LightPowerOn(void); +void LightState(uint8_t append); +void LightCycleColor(int8_t direction); +void LightSetPower(void); +void LightAnimate(void); +bool isChannelGammaCorrected(uint32_t channel); +uint16_t fadeGamma(uint32_t channel, uint16_t v); +uint16_t fadeGammaReverse(uint32_t channel, uint16_t vg); +bool LightApplyFade(void); +void LightApplyPower(uint8_t new_color[LST_MAX], power_t power); +void LightSetOutputs(const uint16_t *cur_col_10); +void calcGammaMultiChannels(uint16_t cur_col_10[5]); +void calcGammaBulbs(uint16_t cur_col_10[5]); +void LightSendDeviceGroupStatus(); +void LightHandleDeviceGroupRequest(); +bool LightColorEntry(char *buffer, uint32_t buffer_length); +void CmndSupportColor(void); +void CmndColor(void); +void CmndWhite(void); +void CmndChannel(void); +void CmndHsbColor(void); +void CmndScheme(void); +void CmndWakeup(void); +void CmndColorTemperature(void); +void CmndDimmer(void); +void CmndDimmerRange(void); +void CmndLedTable(void); +void CmndRgbwwTable(void); +void CmndFade(void); +void CmndSpeed(void); +void CmndWakeupDuration(void); +void CmndUndocA(void); +bool Xdrv04(uint8_t function); +void IrSendInit(void); +void IrReceiveUpdateThreshold(void); +void IrReceiveInit(void); +void IrReceiveCheck(void); +uint32_t IrRemoteCmndIrSendJson(void); +void CmndIrSend(void); +void IrRemoteCmndResponse(uint32_t error); +bool Xdrv05(uint8_t function); +void IrSendInit(void); +uint8_t reverseBitsInByte(uint8_t b); +uint64_t reverseBitsInBytes64(uint64_t b); +void IrReceiveUpdateThreshold(void); +void IrReceiveInit(void); +String sendIRJsonState(const struct decode_results &results); +void IrReceiveCheck(void); +String listSupportedProtocols(bool hvac); +uint32_t IrRemoteCmndIrHvacJson(void); +void CmndIrHvac(void); +uint32_t IrRemoteCmndIrSendJson(void); +uint32_t IrRemoteCmndIrSendRaw(void); +void CmndIrSend(void); +void IrRemoteCmndResponse(uint32_t error); +bool Xdrv05(uint8_t function); +ssize_t rf_find_hex_record_start(uint8_t *buf, size_t size); +ssize_t rf_find_hex_record_end(uint8_t *buf, size_t size); +ssize_t rf_glue_remnant_with_new_data_and_write(const uint8_t *remnant_data, uint8_t *new_data, size_t new_data_len); +ssize_t rf_decode_and_write(uint8_t *record, size_t size); +ssize_t rf_search_and_write(uint8_t *buf, size_t size); +uint8_t rf_erase_flash(void); +uint8_t SnfBrUpdateInit(void); +void SonoffBridgeReceivedRaw(void); +void SonoffBridgeLearnFailed(void); +void SonoffBridgeReceived(void); +bool SonoffBridgeSerialInput(void); +void SonoffBridgeSendCommand(uint8_t code); +void SonoffBridgeSendAck(void); +void SonoffBridgeSendCode(uint32_t code); +void SonoffBridgeSend(uint8_t idx, uint8_t key); +void SonoffBridgeLearn(uint8_t key); +void CmndRfBridge(void); +void CmndRfKey(void); +void CmndRfRaw(void); +bool Xdrv06(uint8_t function); +int DomoticzBatteryQuality(void); +int DomoticzRssiQuality(void); +void MqttPublishDomoticzFanState(void); +void DomoticzUpdateFanState(void); +void MqttPublishDomoticzPowerState(uint8_t device); +void DomoticzUpdatePowerState(uint8_t device); +void DomoticzMqttUpdate(void); +void DomoticzMqttSubscribe(void); +bool DomoticzMqttData(void); +bool DomoticzSendKey(uint8_t key, uint8_t device, uint8_t state, uint8_t svalflg); +uint8_t DomoticzHumidityState(char *hum); +void DomoticzSensor(uint8_t idx, char *data); +void DomoticzSensor(uint8_t idx, uint32_t value); +void DomoticzTempHumSensor(char *temp, char *hum); +void DomoticzTempHumPressureSensor(char *temp, char *hum, char *baro); +void DomoticzSensorPowerEnergy(int power, char *energy); +void DomoticzSensorP1SmartMeter(char *usage1, char *usage2, char *return1, char *return2, int power); +void CmndDomoticzIdx(void); +void CmndDomoticzKeyIdx(void); +void CmndDomoticzSwitchIdx(void); +void CmndDomoticzSensorIdx(void); +void CmndDomoticzUpdateTimer(void); +void HandleDomoticzConfiguration(void); +void DomoticzSaveSettings(void); +bool Xdrv07(uint8_t function); +void SerialBridgeInput(void); +void SerialBridgeInit(void); +void CmndSSerialSend(void); +void CmndSBaudrate(void); +bool Xdrv08(uint8_t function); +float JulianischesDatum(void); +float InPi(float x); +float eps(float T); +float BerechneZeitgleichung(float *DK,float T); +void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8_t *minute_down); +void ApplyTimerOffsets(Timer *duskdawn); +String GetSun(uint32_t dawn); +uint16_t SunMinutes(uint32_t dawn); +void TimerSetRandomWindow(uint32_t index); +void TimerSetRandomWindows(void); +void TimerEverySecond(void); +void PrepShowTimer(uint32_t index); +void CmndTimer(void); +void CmndTimers(void); +void CmndLongitude(void); +void CmndLatitude(void); +void HandleTimerConfiguration(void); +void TimerSaveSettings(void); +bool Xdrv09(uint8_t function); +bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule); +int8_t parseCompareExpression(String &expr, String &leftExpr, String &rightExpr); +void RulesVarReplace(String &commands, const String &sfind, const String &replace); +bool RuleSetProcess(uint8_t rule_set, String &event_saved); +bool RulesProcessEvent(char *json_event); +bool RulesProcess(void); +void RulesInit(void); +void RulesEvery50ms(void); +void RulesEvery100ms(void); +void RulesEverySecond(void); +void RulesSaveBeforeRestart(void); +void RulesSetPower(void); +void RulesTeleperiod(void); +bool RulesMqttData(void); +void CmndSubscribe(void); +void CmndUnsubscribe(void); +bool findNextNumber(char * &pNumber, float &value); +bool findNextVariableValue(char * &pVarname, float &value); +bool findNextObjectValue(char * &pointer, float &value); +bool findNextOperator(char * &pointer, int8_t &op); +float calculateTwoValues(float v1, float v2, uint8_t op); +float evaluateExpression(const char * expression, unsigned int len); +void CmndIf(void); +bool evaluateComparisonExpression(const char *expression, int len); +bool findNextLogicOperator(char * &pointer, int8_t &op); +bool findNextLogicObjectValue(char * &pointer, bool &value); +bool evaluateLogicalExpression(const char * expression, int len); +int8_t findIfBlock(char * &pointer, int &lenWord, int8_t block_type); +void ExecuteCommandBlock(const char * commands, int len); +void ProcessIfStatement(const char* statements); +void RulesPreprocessCommand(char *pCommands); +void CmndRule(void); +void CmndRuleTimer(void); +void CmndEvent(void); +void CmndVariable(void); +void CmndMemory(void); +void CmndCalcResolution(void); +void CmndAddition(void); +void CmndSubtract(void); +void CmndMultiply(void); +void CmndScale(void); +float map_double(float x, float in_min, float in_max, float out_min, float out_max); +bool Xdrv10(uint8_t function); +void ScriptEverySecond(void); +void RulesTeleperiod(void); +int16_t Init_Scripter(void); +void ws2812_set_array(float *array ,uint8_t len); +float median_array(float *array,uint8_t len); +float Get_MFVal(uint8_t index,uint8_t bind); +void Set_MFVal(uint8_t index,uint8_t bind,float val); +float Get_MFilter(uint8_t index); +void Set_MFilter(uint8_t index, float invar); +float DoMedian5(uint8_t index, float in); +uint32_t HSVToRGB(uint16_t hue, uint8_t saturation, uint8_t value); +uint16_t GetStack(void); +uint16_t GetStack(void); +void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize); +void toLog(const char *str); +void toLogN(const char *cp,uint8_t len); +void toLogEOL(const char *s1,const char *str); +void toSLog(const char *str); +int16_t Run_Scripter(const char *type, int8_t tlen, char *js); +void ScripterEvery100ms(void); +void Scripter_save_pvars(void); +void ListDir(char *path, uint8_t depth); +void Script_FileUploadConfiguration(void); +void ScriptFileUploadSuccess(void); +void script_upload(void); +uint8_t DownloadFile(char *file); +void HandleScriptTextareaConfiguration(void); +void HandleScriptConfiguration(void); +void ScriptSaveSettings(void); +void Script_HueStatus(String *response, uint16_t hue_devs); +void Script_Check_Hue(String *response); +void Script_Handle_Hue(String *path); +bool Script_SubCmd(void); +void execute_script(char *script); +bool ScriptCommand(void); +uint16_t xFAT_DATE(uint16_t year, uint8_t month, uint8_t day); +uint16_t xFAT_TIME(uint8_t hour, uint8_t minute, uint8_t second); +void dateTime(uint16_t* date, uint16_t* time); +bool ScriptMqttData(void); +String ScriptSubscribe(const char *data, int data_len); +String ScriptUnsubscribe(const char * data, int data_len); +void Script_Check_HTML_Setvars(void); +void ScriptGetVarname(char *nbuf,char *sp, uint32_t blen); +void ScriptWebShow(void); +void ScriptJsonAppend(void); +bool Xdrv10(uint8_t function); +void KNX_ADD_GA( uint8_t GAop, uint8_t GA_FNUM, uint8_t GA_AREA, uint8_t GA_FDEF ); +void KNX_DEL_GA( uint8_t GAnum ); +void KNX_ADD_CB( uint8_t CBop, uint8_t CB_FNUM, uint8_t CB_AREA, uint8_t CB_FDEF ); +void KNX_DEL_CB( uint8_t CBnum ); +bool KNX_CONFIG_NOT_MATCH(void); +void KNXStart(void); +void KNX_INIT(void); +void KNX_CB_Action(message_t const &msg, void *arg); +void KnxUpdatePowerState(uint8_t device, power_t state); +void KnxSendButtonPower(void); +void KnxSensor(uint8_t sensor_type, float value); +void HandleKNXConfiguration(void); +void KNX_Save_Settings(void); +void CmndKnxTxCmnd(void); +void CmndKnxTxVal(void); +void CmndKnxEnabled(void); +void CmndKnxEnhanced(void); +void CmndKnxPa(void); +void CmndKnxGa(void); +void CmndKnxCb(void); +bool Xdrv11(uint8_t function); +void TryResponseAppend_P(const char *format, ...); +void HAssAnnounceRelayLight(void); +void HAssAnnounceButtonSwitch(uint8_t device, char *topic, uint8_t present, uint8_t key, uint8_t toggle); +void HAssAnnounceSwitches(void); +void HAssAnnounceButtons(void); +void HAssAnnounceSensor(const char *sensorname, const char *subsensortype, const char *MultiSubName, uint8_t subqty, uint8_t subidx); +void HAssAnnounceSensors(void); +void HAssAnnounceStatusSensor(void); +void HAssPublishStatus(void); +void HAssDiscovery(void); +void HAssDiscover(void); +void HAssAnyKey(void); +bool Xdrv12(uint8_t function); +void DisplayInit(uint8_t mode); +void DisplayClear(void); +void DisplayDrawHLine(uint16_t x, uint16_t y, int16_t len, uint16_t color); +void DisplayDrawVLine(uint16_t x, uint16_t y, int16_t len, uint16_t color); +void DisplayDrawLine(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color); +void DisplayDrawCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color); +void DisplayDrawFilledCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color); +void DisplayDrawRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color); +void DisplayDrawFilledRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color); +void DisplayDrawFrame(void); +void DisplaySetSize(uint8_t size); +void DisplaySetFont(uint8_t font); +void DisplaySetRotation(uint8_t rotation); +void DisplayDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag); +void DisplayOnOff(uint8_t on); +uint8_t fatoiv(char *cp,float *res); +uint8_t atoiv(char *cp, int16_t *res); +uint8_t atoiV(char *cp, uint16_t *res); +void alignright(char *string); +uint32_t decode_te(char *line); +void DisplayText(void); +void DisplayClearScreenBuffer(void); +void DisplayFreeScreenBuffer(void); +void DisplayAllocScreenBuffer(void); +void DisplayReAllocScreenBuffer(void); +void DisplayFillScreen(uint32_t line); +void DisplayClearLogBuffer(void); +void DisplayFreeLogBuffer(void); +void DisplayAllocLogBuffer(void); +void DisplayReAllocLogBuffer(void); +void DisplayLogBufferAdd(char* txt); +char* DisplayLogBuffer(char temp_code); +void DisplayLogBufferInit(void); +void DisplayJsonValue(const char* topic, const char* device, const char* mkey, const char* value); +void DisplayAnalyzeJson(char *topic, char *json); +void DisplayMqttSubscribe(void); +bool DisplayMqttData(void); +void DisplayLocalSensor(void); +void DisplayInitDriver(void); +void DisplaySetPower(void); +void CmndDisplay(void); +void CmndDisplayModel(void); +void CmndDisplayWidth(void); +void CmndDisplayHeight(void); +void CmndDisplayMode(void); +void CmndDisplayDimmer(void); +void CmndDisplaySize(void); +void CmndDisplayFont(void); +void CmndDisplayRotate(void); +void CmndDisplayText(void); +void CmndDisplayAddress(void); +void CmndDisplayRefresh(void); +void CmndDisplayColumns(void); +void CmndDisplayRows(void); +void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp); +void DrawAClock(uint16_t rad); +void ClrGraph(uint16_t num); +void DefineGraph(uint16_t num,uint16_t xp,uint16_t yp,int16_t xs,uint16_t ys,int16_t dec,float ymin, float ymax,uint8_t icol); +void DisplayCheckGraph(); +void Save_graph(uint8_t num, char *path); +void Restore_graph(uint8_t num, char *path); +void RedrawGraph(uint8_t num, uint8_t flags); +void AddGraph(uint8_t num,uint8_t val); +void AddValue(uint8_t num,float fval); +bool Xdrv13(uint8_t function); +uint16_t MP3_Checksum(uint8_t *array); +void MP3PlayerInit(void); +void MP3_CMD(uint8_t mp3cmd,uint16_t val); +bool MP3PlayerCmd(void); +bool Xdrv14(uint8_t function); +void PCA9685_Detect(void); +void PCA9685_Reset(void); +void PCA9685_SetPWMfreq(double freq); +void PCA9685_SetPWM_Reg(uint8_t pin, uint16_t on, uint16_t off); +void PCA9685_SetPWM(uint8_t pin, uint16_t pwm, bool inverted); +bool PCA9685_Command(void); +void PCA9685_OutputTelemetry(bool telemetry); +bool Xdrv15(uint8_t function); +void CmndTuyaSend(void); +void CmndTuyaMcu(void); +void TuyaAddMcuFunc(uint8_t fnId, uint8_t dpId); +void UpdateDevices(); +inline bool TuyaFuncIdValid(uint8_t fnId); +uint8_t TuyaGetFuncId(uint8_t dpid); +uint8_t TuyaGetDpId(uint8_t fnId); +void TuyaSendState(uint8_t id, uint8_t type, uint8_t* value); +void TuyaSendBool(uint8_t id, bool value); +void TuyaSendValue(uint8_t id, uint32_t value); +void TuyaSendEnum(uint8_t id, uint32_t value); +void TuyaSendString(uint8_t id, char data[]); +bool TuyaSetPower(void); +bool TuyaSetChannels(void); +void LightSerialDuty(uint16_t duty); +void TuyaRequestState(void); +void TuyaResetWifi(void); +void TuyaProcessStatePacket(void); +void TuyaLowPowerModePacketProcess(void); +void TuyaHandleProductInfoPacket(void); +void TuyaSendLowPowerSuccessIfNeeded(void); +void TuyaNormalPowerModePacketProcess(void); +bool TuyaModuleSelected(void); +void TuyaInit(void); +void TuyaSerialInput(void); +bool TuyaButtonPressed(void); +uint8_t TuyaGetTuyaWifiState(void); +void TuyaSetWifiLed(void); +bool Xnrg16(uint8_t function); +bool Xdrv16(uint8_t function); +void RfReceiveCheck(void); +void RfInit(void); +void CmndRfSend(void); +bool Xdrv17(uint8_t function); +bool ArmtronixSetChannels(void); +void LightSerial2Duty(uint8_t duty1, uint8_t duty2); +void ArmtronixRequestState(void); +bool ArmtronixModuleSelected(void); +void ArmtronixInit(void); +void ArmtronixSerialInput(void); +void ArmtronixSetWifiLed(void); +bool Xdrv18(uint8_t function); +void PS16DZSerialSend(const char *tx_buffer); +void PS16DZSerialSendOk(void); +void PS16DZSerialSendUpdateCommand(void); +void PS16DZSerialInput(void); +bool PS16DZSerialSendUpdateCommandIfRequired(void); +void PS16DZInit(void); +bool PS16DZModuleSelected(void); +bool Xdrv19(uint8_t function); +String HueBridgeId(void); +String HueSerialnumber(void); +String HueUuid(void); +void HueRespondToMSearch(void); +String GetHueDeviceId(uint8_t id); +String GetHueUserId(void); +void HandleUpnpSetupHue(void); +void HueNotImplemented(String *path); +void HueConfigResponse(String *response); +void HueConfig(String *path); +uint8_t getLocalLightSubtype(uint8_t device); +void HueLightStatus1(uint8_t device, String *response); +bool HueActive(uint8_t device); +void HueLightStatus2(uint8_t device, String *response); +uint32_t EncodeLightId(uint8_t relay_id); +uint32_t DecodeLightId(uint32_t hue_id); +uint32_t findEchoGeneration(void); +void HueGlobalConfig(String *path); +void HueAuthentication(String *path); +void HueLights(String *path); +void HueGroups(String *path); +void HandleHueApi(String *path); +bool Xdrv20(uint8_t function); +String WemoSerialnumber(void); +String WemoUuid(void); +void WemoRespondToMSearch(int echo_type); +void HandleUpnpEvent(void); +void HandleUpnpService(void); +void HandleUpnpMetaService(void); +void HandleUpnpSetupWemo(void); +bool Xdrv21(uint8_t function); +bool IsModuleIfan(void); +uint8_t MaxFanspeed(void); +uint8_t GetFanspeed(void); +void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence); +void SonoffIfanReceived(void); +bool SonoffIfanSerialInput(void); +void CmndFanspeed(void); +bool SonoffIfanInit(void); +void SonoffIfanUpdate(void); +bool Xdrv22(uint8_t function); +void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val); +void CopyJsonArray(JsonArray &to, const JsonArray &arr); +void CopyJsonObject(JsonObject &to, const JsonObject &from); +uint16_t fromClusterCode(uint8_t c); +uint8_t toClusterCode(uint16_t c); +class SBuffer hibernateDevice(const struct Z_Device &device); +class SBuffer hibernateDevices(void); +void hidrateDevices(const SBuffer &buf); +void loadZigbeeDevices(void); +void saveZigbeeDevices(void); +void eraseZigbeeDevices(void); +uint8_t toPercentageCR2032(uint32_t voltage); +uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf, + uint32_t offset, uint32_t len); +int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); +int32_t Z_AqaraCube(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); +void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint); +inline bool isXYZ(char c); +inline int8_t hexValue(char c); +void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz); +void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, const SBuffer &payload); +const __FlashStringHelper* zigbeeFindCommand(const char *command, uint16_t *cluster, uint16_t *cmd); +inline char hexDigit(uint32_t h); +String zigbeeCmdAddParams(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z); +uint32_t ZigbeeAliasOrNumber(const char *state_text); +uint8_t ZigbeeGetInstructionSize(uint8_t instr); +void ZigbeeGotoLabel(uint8_t label); +void ZigbeeStateMachine_Run(void); +int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf); +int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf); +int32_t Z_Reboot(int32_t res, class SBuffer &buf); +int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf); +bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match); +int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf); +void Z_SendIEEEAddrReq(uint16_t shortaddr); +void Z_SendActiveEpReq(uint16_t shortaddr); +void Z_SendSimpleDescReq(uint16_t shortaddr, uint8_t endpoint); +int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf); +int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf); +void Z_SendAFInfoRequest(uint16_t shortaddr, uint8_t endpoint, uint16_t clusterid, uint8_t transacid); +int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf); +int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf); +int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf); +int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf); +void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, const JsonObject *json); +int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); +int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf); +int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf); +int32_t Z_Load_Devices(uint8_t value); +int32_t Z_State_Ready(uint8_t value); +int32_t ZigbeeProcessInput(class SBuffer &buf); +void ZigbeeInput(void); +void ZigbeeInit(void); +uint32_t strToUInt(const JsonVariant &val); +void CmndZbReset(void); +void CmndZbZNPSendOrReceive(bool send); +void CmndZbZNPReceive(void); +void CmndZbZNPSend(void); +void ZigbeeZNPSend(const uint8_t *msg, size_t len); +void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, bool clusterSpecific, + uint16_t cluster, uint8_t cmd, const char *param); +void CmndZbSend(void); +void CmndZbBind(void); +void CmndZbProbe(void); +void CmndZbProbeOrPing(boolean probe); +void CmndZbPing(void); +void CmndZbName(void); +void CmndZbForget(void); +void CmndZbSave(void); +void CmndZbRead(void); +void CmndZbPermitJoin(void); +void CmndZbStatus(void); +bool Xdrv23(uint8_t function); +void BuzzerOff(void); +void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune, uint32_t mode); +void BuzzerSetStateToLed(uint32_t state); +void BuzzerBeep(uint32_t count); +void BuzzerEnabledBeep(uint32_t count, uint32_t duration); +bool BuzzerPinState(void); +void BuzzerInit(void); +void BuzzerEvery100mSec(void); +void CmndBuzzer(void); +bool Xdrv24(uint8_t function); +void A4988Init(void); +void CmndDoMove(void); +void CmndDoRotate(void); +void CmndDoTurn(void); +void CmndSetMIS(void); +void CmndSetSPR(void); +void CmndSetRPM(void); +bool Xdrv25(uint8_t function); +void AriluxRfInterrupt(void); +void AriluxRfHandler(void); +void AriluxRfInit(void); +void AriluxRfDisable(void); +bool Xdrv26(uint8_t function); +void ShutterLogPos(uint32_t i); +void ShutterRtc50mS(void); +int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index); +uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index); +void ShutterInit(void); +void ShutterReportPosition(bool always); +void ShutterLimitRealAndTargetPositions(uint32_t i); +void ShutterUpdatePosition(void); +bool ShutterState(uint32_t device); +void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos); +void ShutterWaitForMotorStop(uint32_t i); +int32_t ShutterCounterBasedPosition(uint32_t i); +void ShutterRelayChanged(void); +bool ShutterButtonIsSimultaneousHold(uint32_t button_index, uint32_t shutter_index); +void ShutterButtonHandler(void); +void ShutterSetPosition(uint32_t device, uint32_t position); +void CmndShutterOpen(void); +void CmndShutterClose(void); +void CmndShutterStop(void); +void CmndShutterPosition(void); +void CmndShutterOpenTime(void); +void CmndShutterCloseTime(void); +void CmndShutterMotorDelay(void); +void CmndShutterRelay(void); +void CmndShutterButton(void); +void CmndShutterSetHalfway(void); +void CmndShutterFrequency(void); +void CmndShutterSetClose(void); +void CmndShutterInvert(void); +void CmndShutterCalibration(void); +void CmndShutterLock(void); +void CmndShutterEnableEndStopTime(void); +bool Xdrv27(uint8_t function); +void Pcf8574SwitchRelay(void); +void Pcf8574Init(void); +void HandlePcf8574(void); +void Pcf8574SaveSettings(void); +bool Xdrv28(uint8_t function); +bool DeepSleepEnabled(void); +void DeepSleepReInit(void); +void DeepSleepPrepare(void); +void DeepSleepStart(void); +void DeepSleepEverySecond(void); +void CmndDeepsleepTime(void); +bool Xdrv29(uint8_t function); +uint8_t crc8(const uint8_t *p, uint8_t len); +void ExsSendCmd(uint8_t cmd, uint8_t value); +uint8_t ExsSetPower(uint8_t device, uint8_t power); +uint8_t ExsSetBri(uint8_t device, uint8_t bri); +uint8_t ExsSyncState(uint8_t device); +bool ExsSyncState(); +void ExsDebugState(); +void ExsPacketProcess(void); +bool ExsModuleSelected(void); +bool ExsSetChannels(void); +bool ExsSetPower(void); +void EsxMcuStart(void); +void ExsInit(void); +void ExsSerialInput(void); +void CmndExsDimm(void); +void CmndExsDimmTbl(void); +void CmndExsDimmVal(void); +void CmndExsDimms(void); +void CmndExsChLock(void); +void CmndExsState(void); +bool Xdrv30(uint8_t function); +uint32_t TasmotaSlave_FlashStart(void); +uint8_t TasmotaSlave_UpdateInit(void); +void TasmotaSlave_Reset(void); +uint8_t TasmotaSlave_waitForSerialData(int dataCount, int timeout); +uint8_t TasmotaSlave_sendBytes(uint8_t* bytes, int count); +uint8_t TasmotaSlave_execCmd(uint8_t cmd); +uint8_t TasmotaSlave_execParam(uint8_t cmd, uint8_t* params, int count); +uint8_t TasmotaSlave_exitProgMode(void); +uint8_t TasmotaSlave_SetupFlash(void); +uint8_t TasmotaSlave_loadAddress(uint8_t adrHi, uint8_t adrLo); +void TasmotaSlave_FlashPage(uint8_t addr_h, uint8_t addr_l, uint8_t* data); +void TasmotaSlave_Flash(void); +void TasmotaSlave_SetFlagFlashing(bool value); +bool TasmotaSlave_GetFlagFlashing(void); +void TasmotaSlave_WriteBuffer(uint8_t *buf, size_t size); +void TasmotaSlave_Init(void); +void TasmotaSlave_Show(void); +void TasmotaSlave_sendCmnd(uint8_t cmnd, uint8_t param); +void CmndTasmotaSlaveReset(void); +void CmndTasmotaSlaveSend(void); +void TasmotaSlave_ProcessIn(void); +bool Xdrv31(uint8_t function); +void HotPlugInit(void); +void HotPlugEverySecond(void); +void CmndHotPlugTime(void); +bool Xdrv32(uint8_t function); +bool NRF24initRadio(); +bool NRF24Detect(void); +bool Xdrv33(uint8_t function); +void WMotorV1Detect(void); +void WMotorV1Reset(void); +void WMotorV1SetFrequency(uint32_t freq); +void WMotorV1SetMotor(uint8_t motor, uint8_t dir, float pwm_val); +bool WMotorV1Command(void); +bool Xdrv34(uint8_t function); +void ExceptionTest(uint8_t type); +void CpuLoadLoop(void); +void DebugFreeMem(void); +void DebugFreeMem(void); +void DebugRtcDump(char* parms); +void DebugCfgDump(char* parms); +void DebugCfgPeek(char* parms); +void DebugCfgPoke(char* parms); +void SetFlashMode(uint8_t mode); +void CmndHelp(void); +void CmndRtcDump(void); +void CmndCfgDump(void); +void CmndCfgPeek(void); +void CmndCfgPoke(void); +void CmndCfgXor(void); +void CmndException(void); +void CmndCpuCheck(void); +void CmndFreemem(void); +void CmndSetSensor(void); +void CmndFlashMode(void); +uint32_t DebugSwap32(uint32_t x); +void CmndFlashDump(void); +void CmndI2cWrite(void); +void CmndI2cRead(void); +void CmndI2cStretch(void); +void CmndI2cClock(void); +bool Xdrv99(uint8_t function); +void XsnsDriverState(void); +bool XdrvRulesProcess(void); +void ShowFreeMem(const char *where); +bool XdrvCallDriver(uint32_t driver, uint8_t Function); +bool XdrvCall(uint8_t Function); +void LcdInitMode(void); +void LcdInit(uint8_t mode); +void LcdInitDriver(void); +void LcdDrawStringAt(void); +void LcdDisplayOnOff(uint8_t on); +void LcdCenter(uint8_t row, char* txt); +bool LcdPrintLog(void); +void LcdTime(void); +void LcdRefresh(void); +bool Xdsp01(uint8_t function); +void SSD1306InitDriver(void); +void Ssd1306PrintLog(void); +void Ssd1306Time(void); +void Ssd1306Refresh(void); +bool Xdsp02(byte function); +void MatrixWrite(void); +void MatrixClear(void); +void MatrixFixed(char* txt); +void MatrixCenter(char* txt); +void MatrixScrollLeft(char* txt, int loop); +void MatrixScrollUp(char* txt, int loop); +void MatrixInitMode(void); +void MatrixInit(uint8_t mode); +void MatrixInitDriver(void); +void MatrixOnOff(void); +void MatrixDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag); +void MatrixPrintLog(uint8_t direction); +void MatrixRefresh(void); +bool Xdsp03(uint8_t function); +void Ili9341InitMode(void); +void Ili9341Init(uint8_t mode); +void Ili9341InitDriver(void); +void Ili9341Clear(void); +void Ili9341DrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag); +void Ili9341DisplayOnOff(uint8_t on); +void Ili9341OnOff(void); +void Ili9341PrintLog(void); +void Ili9341Refresh(void); +bool Xdsp04(uint8_t function); +void EpdInitDriver29(); +void EpdPrintLog29(void); +void EpdRefresh29(void); +bool Xdsp05(uint8_t function); +void EpdInitDriver42(); +void EpdRefresh42(); +bool Xdsp06(uint8_t function); +void SH1106InitDriver(); +void SH1106PrintLog(void); +void SH1106Time(void); +void SH1106Refresh(void); +bool Xdsp07(uint8_t function); +void ILI9488_InitDriver(); +void ILI9488_MQTT(uint8_t count,const char *cp); +void ILI9488_RDW_BUTT(uint32_t count,uint32_t pwr); +void FT6236Check(); +bool Xdsp08(uint8_t function); +void SSD1351_InitDriver(); +void SSD1351PrintLog(void); +void SSD1351Time(void); +void SSD1351Refresh(void); +bool Xdsp09(uint8_t function); +void RA8876_InitDriver(); +void RA8876_MQTT(uint8_t count,const char *cp); +void RA8876_RDW_BUTT(uint32_t count,uint32_t pwr); +void FT5316Check(); +bool Xdsp10(uint8_t function); +uint8_t XdspPresent(void); +bool XdspCall(uint8_t Function); +void Ws2812StripShow(void); +int mod(int a, int b); +void Ws2812UpdatePixelColor(int position, struct WsColor hand_color, float offset); +void Ws2812UpdateHand(int position, uint32_t index); +void Ws2812Clock(void); +void Ws2812GradientColor(uint32_t schemenr, struct WsColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i); +void Ws2812Gradient(uint32_t schemenr); +void Ws2812Bars(uint32_t schemenr); +void Ws2812Clear(void); +void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white); +char* Ws2812GetColor(uint32_t led, char* scolor); +void Ws2812ForceSuspend (void); +void Ws2812ForceUpdate (void); +bool Ws2812SetChannels(void); +void Ws2812ShowScheme(void); +void Ws2812ModuleSelected(void); +void CmndLed(void); +void CmndPixels(void); +void CmndRotation(void); +void CmndWidth(void); +bool Xlgt01(uint8_t function); +void LightDiPulse(uint8_t times); +void LightDckiPulse(uint8_t times); +void LightMy92x1Write(uint8_t data); +void LightMy92x1Init(void); +void LightMy92x1Duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c); +bool My92x1SetChannels(void); +void My92x1ModuleSelected(void); +bool Xlgt02(uint8_t function); +void SM16716_SendBit(uint8_t v); +void SM16716_SendByte(uint8_t v); +void SM16716_Update(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b); +void SM16716_Init(void); +bool Sm16716SetChannels(void); +void Sm16716ModuleSelected(void); +bool Xlgt03(uint8_t function); +uint8_t Sm2135Write(uint8_t data); +void Sm2135Send(uint8_t *buffer, uint8_t size); +bool Sm2135SetChannels(void); +void Sm2135ModuleSelected(void); +bool Xlgt04(uint8_t function); +void SnfL1Send(const char *buffer); +void SnfL1SerialSendOk(void); +bool SnfL1SerialInput(void); +bool SnfL1SetChannels(void); +void SnfL1ModuleSelected(void); +bool Xlgt05(uint8_t function); +bool XlgtCall(uint8_t function); +void HlwCfInterrupt(void); +void HlwCf1Interrupt(void); +void HlwEvery200ms(void); +void HlwEverySecond(void); +void HlwSnsInit(void); +void HlwDrvInit(void); +bool HlwCommand(void); +bool Xnrg01(uint8_t function); +void CseReceived(void); +bool CseSerialInput(void); +void CseEverySecond(void); +void CseSnsInit(void); +void CseDrvInit(void); +bool CseCommand(void); +bool Xnrg02(uint8_t function); +uint8_t PzemCrc(uint8_t *data); +void PzemSend(uint8_t cmd); +bool PzemReceiveReady(void); +bool PzemRecieve(uint8_t resp, float *data); +void PzemEvery250ms(void); +void PzemSnsInit(void); +void PzemDrvInit(void); +bool PzemCommand(void); +bool Xnrg03(uint8_t function); +uint8_t McpChecksum(uint8_t *data); +unsigned long McpExtractInt(char *data, uint8_t offset, uint8_t size); +void McpSetInt(unsigned long value, uint8_t *data, uint8_t offset, size_t size); +void McpSend(uint8_t *data); +void McpGetAddress(void); +void McpAddressReceive(void); +void McpGetCalibration(void); +void McpParseCalibration(void); +bool McpCalibrationCalc(struct mcp_cal_registers_type *cal_registers, uint8_t range_shift); +void McpSetCalibration(struct mcp_cal_registers_type *cal_registers); +void McpSetSystemConfiguration(uint16 interval); +void McpGetFrequency(void); +void McpParseFrequency(void); +void McpSetFrequency(uint16_t line_frequency_ref, uint16_t gain_line_frequency); +void McpGetData(void); +void McpParseData(void); +void McpSerialInput(void); +void McpEverySecond(void); +void McpSnsInit(void); +void McpDrvInit(void); +bool McpCommand(void); +bool Xnrg04(uint8_t function); +void PzemAcEverySecond(void); +void PzemAcSnsInit(void); +void PzemAcDrvInit(void); +bool PzemAcCommand(void); +bool Xnrg05(uint8_t function); +void PzemDcEverySecond(void); +void PzemDcSnsInit(void); +void PzemDcDrvInit(void); +bool PzemDcCommand(void); +bool Xnrg06(uint8_t function); +int Ade7953RegSize(uint16_t reg); +void Ade7953Write(uint16_t reg, uint32_t val); +int32_t Ade7953Read(uint16_t reg); +void Ade7953Init(void); +void Ade7953GetData(void); +void Ade7953EnergyEverySecond(void); +void Ade7953DrvInit(void); +bool Ade7953Command(void); +bool Xnrg07(uint8_t function); +void SDM120Every250ms(void); +void Sdm120SnsInit(void); +void Sdm120DrvInit(void); +void Sdm220Reset(void); +void Sdm220Show(bool json); +bool Xnrg08(uint8_t function); +void Dds2382EverySecond(void); +void Dds2382SnsInit(void); +void Dds2382DrvInit(void); +bool Xnrg09(uint8_t function); +void SDM630Every250ms(void); +void Sdm630SnsInit(void); +void Sdm630DrvInit(void); +bool Xnrg10(uint8_t function); +void DDSU666Every250ms(void); +void Ddsu666SnsInit(void); +void Ddsu666DrvInit(void); +bool Xnrg11(uint8_t function); +bool solaxX1_RS485ReceiveReady(void); +void solaxX1_RS485Send(uint16_t msgLen); +uint8_t solaxX1_RS485Receive(uint8_t *value); +uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen); +void solaxX1_SendInverterAddress(void); +void solaxX1_QueryLiveData(void); +uint8_t solaxX1_ParseErrorCode(uint32_t code); +void solaxX1250MSecond(void); +void solaxX1SnsInit(void); +void solaxX1DrvInit(void); +void solaxX1Show(bool json); +bool Xnrg12(uint8_t function); +void FifLEEvery250ms(void); +void FifLESnsInit(void); +void FifLEDrvInit(void); +void FifLEReset(void); +void FifLEShow(bool json); +bool Xnrg13(uint8_t function); +bool XnrgCall(uint8_t function); +void CounterUpdate(uint8_t index); +void CounterUpdate1(void); +void CounterUpdate2(void); +void CounterUpdate3(void); +void CounterUpdate4(void); +bool CounterPinState(void); +void CounterInit(void); +void CounterEverySecond(void); +void CounterSaveState(void); +void CounterShow(bool json); +void CmndCounter(void); +void CmndCounterType(void); +void CmndCounterDebounce(void); +bool Xsns01(uint8_t function); +void AdcInit(void); +uint16_t AdcRead(uint8_t factor); +void AdcEvery250ms(void); +uint16_t AdcGetLux(void); +uint16_t AdcGetRange(void); +void AdcGetCurrentPower(uint8_t factor); +void AdcEverySecond(void); +void AdcShow(bool json); +void CmndAdc(void); +void CmndAdcs(void); +void CmndAdcParam(void); +bool Xsns02(uint8_t function); +void SonoffScSend(const char *data); +void SonoffScInit(void); +void SonoffScSerialInput(char *rcvstat); +void SonoffScShow(bool json); +bool Xsns04(uint8_t function); +uint8_t OneWireReset(void); +void OneWireWriteBit(uint8_t v); +void OneWireReadBit1(void); +void OneWireReadBit2(void); +uint8_t OneWireReadBit(void); +void OneWireWrite(uint8_t v); +uint8_t OneWireRead(void); +void OneWireSelect(const uint8_t rom[8]); +void OneWireResetSearch(void); +uint8_t OneWireSearch(uint8_t *newAddr); +bool OneWireCrc8(uint8_t *addr); +void Ds18x20Init(void); +void Ds18x20Convert(void); +bool Ds18x20Read(uint8_t sensor); +void Ds18x20Name(uint8_t sensor); +void Ds18x20EverySecond(void); +void Ds18x20Show(bool json); +bool Xsns05(uint8_t function); +void DhtReadPrep(void); +int32_t DhtExpectPulse(uint8_t sensor, bool level); +bool DhtRead(uint8_t sensor); +void DhtReadTempHum(uint8_t sensor); +bool DhtPinState(); +void DhtInit(void); +void DhtEverySecond(void); +void DhtShow(bool json); +bool Xsns06(uint8_t function); +void DhtReadPrep(void); +int32_t DhtExpectPulse(uint8_t sensor, bool level); +bool DhtRead(uint8_t sensor); +void DhtReadTempHum(uint8_t sensor); +bool DhtPinState(); +void DhtInit(void); +void DhtEverySecond(void); +void DhtShow(bool json); +bool Xsns06(uint8_t function); +bool DhtExpectPulse(uint8_t sensor, int level); +int DhtReadDat(uint8_t sensor); +bool DhtRead(uint8_t sensor); +void DhtReadTempHum(uint8_t sensor); +bool DhtPinState(); +void DhtInit(void); +void DhtEverySecond(void); +void DhtShow(bool json); +bool Xsns06(uint8_t function); +bool DhtExpectPulse(uint32_t sensor, uint32_t level); +bool DhtRead(uint32_t sensor); +void DhtReadTempHum(uint32_t sensor); +bool DhtPinState(); +void DhtInit(void); +void DhtEverySecond(void); +void DhtShow(bool json); +bool Xsns06(uint8_t function); +bool DhtWaitState(uint32_t sensor, uint32_t level); +bool DhtRead(uint32_t sensor); +bool DhtPinState(); +void DhtInit(void); +void DhtEverySecond(void); +void DhtShow(bool json); +bool Xsns06(uint8_t function); +bool ShtReset(void); +bool ShtSendCommand(const uint8_t cmd); +bool ShtAwaitResult(void); +int ShtReadData(void); +bool ShtRead(void); +void ShtDetect(void); +void ShtEverySecond(void); +void ShtShow(bool json); +bool Xsns07(uint8_t function); +uint8_t HtuCheckCrc8(uint16_t data); +uint8_t HtuReadDeviceId(void); +void HtuSetResolution(uint8_t resolution); +void HtuReset(void); +void HtuHeater(uint8_t heater); +void HtuInit(void); +bool HtuRead(void); +void HtuDetect(void); +void HtuEverySecond(void); +void HtuShow(bool json); +bool Xsns08(uint8_t function); +bool Bmp180Calibration(uint8_t bmp_idx); +void Bmp180Read(uint8_t bmp_idx); +bool Bmx280Calibrate(uint8_t bmp_idx); +void Bme280Read(uint8_t bmp_idx); +static void BmeDelayMs(uint32_t ms); +bool Bme680Init(uint8_t bmp_idx); +void Bme680Read(uint8_t bmp_idx); +void BmpDetect(void); +void BmpRead(void); +void BmpShow(bool json); +void BMP_EnterSleep(void); +bool Xsns09(uint8_t function); +bool Bh1750Read(void); +void Bh1750Detect(void); +void Bh1750EverySecond(void); +void Bh1750Show(bool json); +bool Xsns10(uint8_t function); +void Veml6070Detect(void); +void Veml6070UvTableInit(void); +void Veml6070EverySecond(void); +void Veml6070ModeCmd(bool mode_cmd); +uint16_t Veml6070ReadUv(void); +double Veml6070UvRiskLevel(uint16_t uv_level); +double Veml6070UvPower(double uvrisk); +void Veml6070Show(bool json); +bool Xsns11(uint8_t function); +void Ads1115StartComparator(uint8_t channel, uint16_t mode); +int16_t Ads1115GetConversion(uint8_t channel); +void Ads1115Detect(void); +void Ads1115Show(bool json); +bool Xsns12(uint8_t function); +bool Ina219SetCalibration(uint8_t mode, uint16_t addr); +float Ina219GetShuntVoltage_mV(uint16_t addr); +float Ina219GetBusVoltage_V(uint16_t addr); +float Ina219GetCurrent_mA(uint16_t addr); +bool Ina219Read(void); +bool Ina219CommandSensor(void); +void Ina219Detect(void); +void Ina219EverySecond(void); +void Ina219Show(bool json); +bool Xsns13(uint8_t function); +bool Sht3xRead(float &t, float &h, uint8_t sht3x_address); +void Sht3xDetect(void); +void Sht3xShow(bool json); +bool Xsns14(uint8_t function); +uint8_t MhzCalculateChecksum(uint8_t *array); +size_t MhzSendCmd(uint8_t command_id); +bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s); +void MhzEverySecond(void); +bool MhzCommandSensor(void); +void MhzInit(void); +void MhzShow(bool json); +bool Xsns15(uint8_t function); +bool Tsl2561Read(void); +void Tsl2561Detect(void); +void Tsl2561EverySecond(void); +void Tsl2561Show(bool json); +bool Xsns16(uint8_t function); +void Senseair250ms(void); +void SenseairInit(void); +void SenseairShow(bool json); +bool Xsns17(uint8_t function); +bool PmsReadData(void); +void PmsSecond(void); +void PmsInit(void); +void PmsShow(bool json); +bool Xsns18(uint8_t function); +void MGSInit(void); +void MGSPrepare(void); +char* measure_gas(int gas_type, char* buffer); +void MGSShow(bool json); +bool Xsns19(uint8_t function); +bool NovaSdsCommand(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint16_t sensorid, uint8_t *buffer); +void NovaSdsSetWorkPeriod(void); +bool NovaSdsReadData(void); +void NovaSdsSecond(void); +bool NovaSdsCommandSensor(void); +void NovaSdsInit(void); +void NovaSdsShow(bool json); +bool Xsns20(uint8_t function); +void sgp30_Init(void); +float sgp30_AbsoluteHumidity(float temperature, float humidity,char tempUnit); +void Sgp30Update(void); +void Sgp30Show(bool json); +bool Xsns21(uint8_t function); +uint8_t Sr04TModeDetect(void); +uint16_t Sr04TMiddleValue(uint16_t first, uint16_t second, uint16_t third); +uint16_t Sr04TMode3Distance(); +uint16_t Sr04TMode2Distance(void); +void Sr04TReading(void); +void Sr04Show(bool json); +bool Xsns22(uint8_t function); +uint8_t Si1145ReadByte(uint8_t reg); +uint16_t Si1145ReadHalfWord(uint8_t reg); +bool Si1145WriteByte(uint8_t reg, uint16_t val); +uint8_t Si1145WriteParamData(uint8_t p, uint8_t v); +bool Si1145Present(void); +void Si1145Reset(void); +void Si1145DeInit(void); +bool Si1145Begin(void); +uint16_t Si1145ReadUV(void); +uint16_t Si1145ReadVisible(void); +uint16_t Si1145ReadIR(void); +bool Si1145Read(void); +void Si1145Detect(void); +void Si1145Update(void); +void Si1145Show(bool json); +bool Xsns24(uint8_t function); +void LM75ADDetect(void); +float LM75ADGetTemp(void); +void LM75ADShow(bool json); +bool Xsns26(uint8_t function); +int8_t wireReadDataBlock( uint8_t reg, + uint8_t *val, + uint16_t len); +void calculateColorTemperature(void); +bool APDS9960_init(void); +uint8_t getMode(void); +void setMode(uint8_t mode, uint8_t enable); +void enableLightSensor(void); +void disableLightSensor(void); +void enableProximitySensor(void); +void disableProximitySensor(void); +void enableGestureSensor(void); +void disableGestureSensor(void); +bool isGestureAvailable(void); +int16_t readGesture(void); +void enablePower(void); +void disablePower(void); +void readAllColorAndProximityData(void); +void resetGestureParameters(void); +bool processGestureData(void); +bool decodeGesture(void); +void handleGesture(void); +void APDS9960_adjustATime(void); +void APDS9960_loop(void); +void APDS9960_detect(void); +void APDS9960_show(bool json); +bool APDS9960CommandSensor(void); +bool Xsns27(uint8_t function); +void Tm16XXSend(uint8_t data); +void Tm16XXSendCommand(uint8_t cmd); +void TM16XXSendData(uint8_t address, uint8_t data); +uint8_t Tm16XXReceive(void); +void Tm16XXClearDisplay(void); +void Tm1638SetLED(uint8_t color, uint8_t pos); +void Tm1638SetLEDs(word leds); +uint8_t Tm1638GetButtons(void); +void TmInit(void); +void TmLoop(void); +bool Xsns28(uint8_t function); +void MCP230xx_CheckForIntCounter(void); +void MCP230xx_CheckForIntRetainer(void); +const char* IntModeTxt(uint8_t intmo); +uint8_t MCP230xx_readGPIO(uint8_t port); +void MCP230xx_ApplySettings(void); +void MCP230xx_Detect(void); +void MCP230xx_CheckForInterrupt(void); +void MCP230xx_Show(bool json); +void MCP230xx_SetOutPin(uint8_t pin,uint8_t pinstate); +void MCP230xx_Reset(uint8_t pinmode); +bool MCP230xx_Command(void); +void MCP230xx_UpdateWebData(void); +void MCP230xx_OutputTelemetry(void); +void MCP230xx_Interrupt_Counter_Report(void); +void MCP230xx_Interrupt_Retain_Report(void); +bool Xsns29(uint8_t function); +void Mpr121Init(struct mpr121 *pS, bool initial); +void Mpr121Show(struct mpr121 *pS, uint8_t function); +bool Xsns30(uint8_t function); +void CCS811Detect(void); +void CCS811Update(void); +void CCS811Show(bool json); +bool Xsns31(uint8_t function); +void MPU_6050PerformReading(void); +void MPU_6050Detect(void); +void MPU_6050Show(bool json); +bool Xsns32(uint8_t function); +void DS3231Detect(void); +uint8_t bcd2dec(uint8_t n); +uint8_t dec2bcd(uint8_t n); +uint32_t ReadFromDS3231(void); +void SetDS3231Time (uint32_t epoch_time); +void DS3231EverySecond(void); +bool Xsns33(uint8_t function); +bool HxIsReady(uint16_t timeout); +long HxRead(void); +void HxResetPart(void); +void HxReset(void); +void HxCalibrationStateTextJson(uint8_t msg_id); +void SetWeightDelta(); +bool HxCommand(void); +long HxWeight(void); +void HxInit(void); +void HxEvery100mSecond(void); +void HxSaveBeforeRestart(void); +void HxShow(bool json); +void HandleHxAction(void); +void HxSaveSettings(void); +void HxLogUpdates(void); +bool Xsns34(uint8_t function); +void TX2xStartRead(void); +void Tx2xReset(void); +void Tx2xRead(void); +void Tx2xInit(void); +void Tx2xShow(bool json); +bool Xsns35(uint8_t function); +void MGC3130_handleSensorData(); +void MGC3130_sendMessage(uint8_t data[], uint8_t length); +void MGC3130_handleGesture(); +bool MGC3130_handleTouch(); +void MGC3130_handleAirWheel(); +void MGC3130_handleSystemStatus(); +bool MGC3130_receiveMessage(); +bool MGC3130_readData(); +void MGC3130_nextMode(); +void MGC3130_loop(); +void MGC3130_detect(void); +void MGC3130_show(bool json); +bool MGC3130CommandSensor(); +bool Xsns36(uint8_t function); +bool RfSnsFetchSignal(uint8_t DataPin, bool StateSignal); +void RfSnsInitTheoV2(void); +void RfSnsAnalyzeTheov2(void); +void RfSnsTheoV2Show(bool json); +void RfSnsInitAlectoV2(void); +void RfSnsAnalyzeAlectov2(); +void RfSnsAlectoResetRain(void); +uint8_t RfSnsAlectoCRC8(uint8_t *addr, uint8_t len); +void RfSnsAlectoV2Show(bool json); +void RfSnsInit(void); +void RfSnsAnalyzeRawSignal(void); +void RfSnsEverySecond(void); +void RfSnsShow(bool json); +bool Xsns37(uint8_t function); +void AzEverySecond(void); +void AzInit(void); +void AzShow(bool json); +bool Xsns38(uint8_t function); +void MAX31855_Init(void); +void MAX31855_GetResult(void); +float MAX31855_GetProbeTemperature(int32_t RawData); +float MAX31855_GetReferenceTemperature(int32_t RawData); +int32_t MAX31855_ShiftIn(uint8_t Length); +void MAX31855_Show(bool Json); +bool Xsns39(uint8_t function); +void PN532_Init(void); +int8_t PN532_receive(uint8_t *buf, int len, uint16_t timeout); +int8_t PN532_readAckFrame(void); +uint32_t PN532_getFirmwareVersion(void); +void PN532_wakeup(void); +bool PN532_setPassiveActivationRetries(uint8_t maxRetries); +bool PN532_SAMConfig(void); +uint8_t mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData); +uint8_t mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data); +uint8_t mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data); +void PN532_ScanForTag(void); +bool PN532_Command(void); +bool Xsns40(uint8_t function); +bool Max4409Read_lum(void); +void Max4409Detect(void); +void Max4409EverySecond(void); +void Max4409Show(bool json); +bool Xsns41(uint8_t function); +void Scd30Detect(void); +void Scd30Update(void); +int Scd30GetCommand(int command_code, uint16_t *pvalue); +int Scd30SetCommand(int command_code, uint16_t value); +bool Scd30CommandSensor(); +void Scd30Show(bool json); +bool Xsns42(byte function); +int hreReadBit(); +char hreReadChar(int &parity_errors); +void hreInit(void); +void hreEvery50ms(void); +void hreShow(boolean json); +bool Xsns43(byte function); +uint8_t sps30_calc_CRC(uint8_t *data); +void sps30_get_data(uint16_t cmd, uint8_t *data, uint8_t dlen); +void sps30_cmd(uint16_t cmd); +void SPS30_Detect(void); +void SPS30_Every_Second(); +void SPS30_Show(bool json); +bool SPS30_cmd(void); +bool Xsns44(byte function); +void Vl53l0Detect(void); +void Vl53l0Every_250MSecond(void); +void Vl53l0Show(boolean json); +bool Xsns45(byte function); +void MLX90614_Init(void); +void MLX90614_Every_Second(void); +void MLX90614_Show(uint8_t json); +bool Xsns46(byte function); +void MAX31865_Init(void); +void MAX31865_GetResult(void); +void MAX31865_Show(bool Json); +bool Xsns47(uint8_t function); +void ChirpWriteI2CRegister(uint8_t addr, uint8_t reg); +uint16_t ChirpFinishReadI2CRegister16bit(uint8_t addr); +void ChirpReset(uint8_t addr); +void ChirpResetAll(void); +void ChirpClockSet(); +void ChirpSleep(uint8_t addr); +void ChirpSelect(uint8_t sensor); +uint8_t ChirpReadVersion(uint8_t addr); +bool ChirpSet(uint8_t addr); +bool ChirpScan(); +void ChirpDetect(void); +void ChirpServiceAllSensors(uint8_t job); +void ChirpEvery100MSecond(void); +void ChirpShow(bool json); +bool ChirpCmd(void); +bool Xsns48(uint8_t function); +void PAJ7620SelectBank(uint8_t bank); +void PAJ7620DecodeGesture(void); +void PAJ7620ReadGesture(void); +void PAJ7620Detect(void); +void PAJ7620Init(void); +void PAJ7620Loop(void); +void PAJ7620Show(bool json); +bool PAJ7620CommandSensor(void); +bool Xsns50(uint8_t function); +void RDM6300_Init(); +void RDM6300_ScanForTag(); +uint8_t rm6300_hexnibble(char chr); +void rm6300_hstring_to_array(uint8_t array[], uint8_t len, char buffer[]); +void RDM6300_Show(void); +bool Xsns51(byte function); +void IBEACON_Init(); +void hm17_every_second(void); +void hm17_sbclr(void); +void hm17_sendcmd(uint8_t cmd); +uint32_t ibeacon_add(struct IBEACON *ib); +void hm17_decode(void); +void IBEACON_loop(); +void IBEACON_Show(void); +bool xsns52_cmd(void); +bool ibeacon_cmd(void); +void ib_sendbeep(void); +void ibeacon_mqtt(const char *mac,const char *rssi); +bool Xsns52(byte function); +double sml_median_array(double *array,uint8_t len); +double sml_median(struct SML_MEDIAN_FILTER* mf, double in); +void ADS1115_init(void); +bool Serial_available(); +uint8_t Serial_read(); +uint8_t Serial_peek(); +void Dump2log(void); +double sml_getvalue(unsigned char *cp,uint8_t index); +uint8_t hexnibble(char chr); +double CharToDouble(const char *str); +void ebus_esc(uint8_t *ebus_buffer, unsigned char len); +uint8_t ebus_crc8(uint8_t data, uint8_t crc_init); +uint8_t ebus_CalculateCRC( uint8_t *Data, uint16_t DataLen ); +void sml_empty_receiver(uint32_t meters); +void sml_shift_in(uint32_t meters,uint32_t shard); +void SML_Poll(void); +void SML_Decode(uint8_t index); +void SML_Immediate_MQTT(const char *mp,uint8_t index,uint8_t mindex); +void SML_Show(boolean json); +void SML_CounterUpd(uint8_t index); +void SML_CounterUpd1(void); +void SML_CounterUpd2(void); +void SML_CounterUpd3(void); +void SML_CounterUpd4(void); +bool Gpio_used(uint8_t gpiopin); +void SML_Init(void); +uint32_t SML_SetBaud(uint32_t meter, uint32_t br); +uint32_t SML_Write(uint32_t meter,char *hstr); +void SetDBGLed(uint8_t srcpin, uint8_t ledpin); +void SML_Counter_Poll(void); +void SML_Check_Send(void); +uint8_t sml_hexnibble(char chr); +void SML_Send_Seq(uint32_t meter,char *seq); +uint16_t MBUS_calculateCRC(uint8_t *frame, uint8_t num); +uint8_t SML_PzemCrc(uint8_t *data, uint8_t len); +uint8_t CalcEvenParity(uint8_t data); +bool XSNS_53_cmd(void); +void InjektCounterValue(uint8_t meter,uint32_t counter); +void SML_CounterSaveState(void); +bool Xsns53(byte function); +static uint32_t _expand_r_shunt(uint16_t compact_r_shunt); +void Ina226SetCalibration(uint8_t slaveIndex); +bool Ina226TestPresence(uint8_t device); +void Ina226ResetActive(void); +void Ina226Init(); +float Ina226ReadBus_v(uint8_t device); +float Ina226ReadShunt_i(uint8_t device); +float Ina226ReadPower_w(uint8_t device); +void Ina226Read(uint8_t device); +void Ina226EverySecond(); +bool Ina226CommandSensor(); +void Ina226Show(bool json); +bool Xsns54(byte callback_id); +bool Hih6Read(void); +void Hih6Detect(void); +void Hih6EverySecond(void); +void Hih6Show(bool json); +bool Xsns55(uint8_t function); +void HpmaSecond(void); +void HpmaInit(void); +void HpmaShow(bool json); +bool Xsns56(uint8_t function); +void Tsl2591Init(void); +bool Tsl2591Read(void); +void Tsl2591EverySecond(void); +void Tsl2591Show(bool json); +bool Xsns57(uint8_t function); +bool Dht12Read(void); +void Dht12Detect(void); +void Dht12EverySecond(void); +void Dht12Show(bool json); +bool Xsns58(uint8_t function); +uint32_t DS1624_Idx2Addr(uint32_t idx); +int DS1624_Restart(uint8_t config, uint32_t idx); +void DS1624_HotPlugUp(uint32_t idx); +void DS1624_HotPlugDown(int idx); +bool DS1624GetTemp(float *value, int idx); +void DS1624HotPlugScan(void); +void DS1624EverySecond(void); +void DS1624Show(bool json); +bool Xsns59(uint8_t function); +void UBXcalcChecksum(char* CK, size_t msgSize); +bool UBXcompareMsgHeader(const char* msgHeader); +void UBXinitCFG(void); +void UBXTriggerTele(void); +void UBXDetect(void); +uint32_t UBXprocessGPS(); +void UBXsendHeader(void); +void UBXsendRecord(uint8_t *buf); +void UBXsendFooter(void); +void UBXsendFile(void); +void UBXSetRate(uint16_t interval); +void UBXSelectMode(uint16_t mode); +bool UBXHandlePOSLLH(); +void UBXHandleSTATUS(); +void UBXHandleTIME(); +void UBXHandleOther(void); +void UBXLoop50msec(void); +void UBXLoop(void); +void UBXShow(bool json); +bool UBXCmd(void); +bool Xsns60(uint8_t function); +bool MINRFinitBLE(uint8_t _mode); +void MINRFhopChannel(); +bool MINRFreceivePacket(void); +void MINRFswapbuf(uint8_t len); +void MINRFwhiten(uint8_t *buf, uint8_t len, uint8_t lfsr); +void MINRFreverseMAC(uint8_t _mac[]); +void MINRFchangePacketModeTo(uint8_t _mode); +void MINRFpurgeFakeSensors(void); +void MINRFhandleFloraPacket(void); +void MINRFhandleMJ_HT_V1Packet(void); +void MINRFhandleLYWSD02Packet(void); +void MINRFhandleLYWSD03Packet(void); +void MINRFhandleCGG1Packet(void); +void MINRF_EVERY_50_MSECOND(); +void MINRFShow(bool json); +bool Xsns61(uint8_t function); +void HM10_Launchtask(uint8_t task, uint8_t slot, uint8_t delay); +void HM10_TaskReplaceInSlot(uint8_t task, uint8_t slot); +void HM10_Reset(void); +void HM10_Discovery_Scan(void); +void HM10_Read_LYWSD03(void); +void HM10_Read_LYWSD02(void); +void HM10_Time_LYWSD02(void); +void HM10SerialInit(void); +void HM10MACStringToBytes(const char* string, uint8_t _mac[]); +void HM10ParseResponse(char *buf); +void HM10readTempHum(char *_buf); +bool HM10readBat(char *_buf); +bool HM10SerialHandleFeedback(); +void HM10_TaskEvery100ms(); +void HM10EverySecond(); +bool HM10Cmd(void); +void HM10Show(bool json); +bool Xsns62(uint8_t function); +bool AHT10Read(void); +bool AHT10Init(void); +uint8_t AHT10ReadStatus(void); +void AHT10Reset(void); +void AHT10Detect(void); +void AHT10EverySecond(void); +void AHT10Show(bool json); +bool Xsns63(uint8_t function); +void HandleMetrics(void); +bool Xsns91(uint8_t function); +bool XsnsEnabled(uint32_t sns_index); +void XsnsSensorState(void); +bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index); +bool XsnsCall(uint8_t Function); +bool I2cEnabled(uint32_t i2c_index); +void I2cDriverState(void); +#line 184 "S:/Development/Tasmota/tasmota/tasmota.ino" +void setup(void) +{ + global_state.data = 3; + + RtcRebootLoad(); + if (!RtcRebootValid()) { + RtcReboot.fast_reboot_count = 0; + } + RtcReboot.fast_reboot_count++; + RtcRebootSave(); + + Serial.begin(APP_BAUDRATE); + seriallog_level = LOG_LEVEL_INFO; + + snprintf_P(my_version, sizeof(my_version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff); + if (VERSION & 0xff) { + snprintf_P(my_version, sizeof(my_version), PSTR("%s.%d"), my_version, VERSION & 0xff); + } + + snprintf_P(my_image, sizeof(my_image), PSTR("(%s)"), CODE_IMAGE_STR); + + SettingsLoad(); + SettingsDelta(); + + OsWatchInit(); + + GetFeatures(); + + if (1 == RtcReboot.fast_reboot_count) { + UpdateQuickPowerCycle(true); + XdrvCall(FUNC_SETTINGS_OVERRIDE); + } + + + seriallog_level = Settings.seriallog_level; + seriallog_timer = SERIALLOG_TIMER; + syslog_level = Settings.syslog_level; + stop_flash_rotate = Settings.flag.stop_flash_rotate; + save_data_counter = Settings.save_data; + sleep = Settings.sleep; +#ifndef USE_EMULATION + Settings.flag2.emulation = 0; +#else +#ifndef USE_EMULATION_WEMO + if (EMUL_WEMO == Settings.flag2.emulation) { Settings.flag2.emulation = 0; } +#endif +#ifndef USE_EMULATION_HUE + if (EMUL_HUE == Settings.flag2.emulation) { Settings.flag2.emulation = 0; } +#endif +#endif + + if (Settings.param[P_BOOT_LOOP_OFFSET]) { + + if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET]) { + Settings.flag3.user_esp8285_enable = 0; + if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +1) { + for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { + if (bitRead(Settings.rule_stop, i)) { + bitWrite(Settings.rule_enabled, i, 0); + } + } + } + if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +2) { + Settings.rule_enabled = 0; + } + if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +3) { + for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { + Settings.my_gp.io[i] = GPIO_NONE; + } + Settings.my_adc0 = ADC0_NONE; + } + if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +4) { + Settings.module = SONOFF_BASIC; + + } + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcReboot.fast_reboot_count); + } + } + + Format(mqtt_client, SettingsText(SET_MQTT_CLIENT), sizeof(mqtt_client)); + Format(mqtt_topic, SettingsText(SET_MQTT_TOPIC), sizeof(mqtt_topic)); + if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) { + SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME); + snprintf_P(my_hostname, sizeof(my_hostname)-1, SettingsText(SET_HOSTNAME), mqtt_topic, ESP.getChipId() & 0x1FFF); + } else { + snprintf_P(my_hostname, sizeof(my_hostname)-1, SettingsText(SET_HOSTNAME)); + } + + GetEspHardwareType(); + GpioInit(); + + + + WifiConnect(); + + SetPowerOnState(); + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_ESP8266_RELEASE), PROJECT, SettingsText(SET_FRIENDLYNAME1), my_version, my_image); +#ifdef FIRMWARE_MINIMAL + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_WARNING_MINIMAL_VERSION)); +#endif + + memcpy_P(log_data, VERSION_MARKER, 1); + + RtcInit(); + +#ifdef USE_ARDUINO_OTA + ArduinoOTAInit(); +#endif + + XdrvCall(FUNC_INIT); + XsnsCall(FUNC_INIT); +} + +void BacklogLoop(void) +{ + if (TimeReached(backlog_delay)) { + if (!BACKLOG_EMPTY && !backlog_mutex) { +#ifdef SUPPORT_IF_STATEMENT + backlog_mutex = true; + String cmd = backlog.shift(); + backlog_mutex = false; + ExecuteCommand((char*)cmd.c_str(), SRC_BACKLOG); +#else + backlog_mutex = true; + ExecuteCommand((char*)backlog[backlog_pointer].c_str(), SRC_BACKLOG); + backlog_pointer++; + if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; } + backlog_mutex = false; +#endif + } + } +} + +void loop(void) +{ + uint32_t my_sleep = millis(); + + XdrvCall(FUNC_LOOP); + XsnsCall(FUNC_LOOP); + + OsWatchLoop(); + + ButtonLoop(); + SwitchLoop(); +#ifdef ROTARY_V1 + RotaryLoop(); +#endif +#ifdef USE_DEVICE_GROUPS + DeviceGroupsLoop(); +#endif + BacklogLoop(); + + if (TimeReached(state_50msecond)) { + SetNextTimeInterval(state_50msecond, 50); + XdrvCall(FUNC_EVERY_50_MSECOND); + XsnsCall(FUNC_EVERY_50_MSECOND); + } + if (TimeReached(state_100msecond)) { + SetNextTimeInterval(state_100msecond, 100); + Every100mSeconds(); + XdrvCall(FUNC_EVERY_100_MSECOND); + XsnsCall(FUNC_EVERY_100_MSECOND); + } + if (TimeReached(state_250msecond)) { + SetNextTimeInterval(state_250msecond, 250); + Every250mSeconds(); + XdrvCall(FUNC_EVERY_250_MSECOND); + XsnsCall(FUNC_EVERY_250_MSECOND); + } + if (TimeReached(state_second)) { + SetNextTimeInterval(state_second, 1000); + PerformEverySecond(); + XdrvCall(FUNC_EVERY_SECOND); + XsnsCall(FUNC_EVERY_SECOND); + } + + if (!serial_local) { SerialInput(); } + +#ifdef USE_ARDUINO_OTA + ArduinoOtaLoop(); +#endif + + uint32_t my_activity = millis() - my_sleep; + + if (Settings.flag3.sleep_normal) { + + delay(sleep); + } else { + if (my_activity < (uint32_t)sleep) { + delay((uint32_t)sleep - my_activity); + } else { + if (global_state.wifi_down) { + delay(my_activity /2); + } + } + } + + if (!my_activity) { my_activity++; } + uint32_t loop_delay = sleep; + if (!loop_delay) { loop_delay++; } + uint32_t loops_per_second = 1000 / loop_delay; + uint32_t this_cycle_ratio = 100 * my_activity / loop_delay; + loop_load_avg = loop_load_avg - (loop_load_avg / loops_per_second) + (this_cycle_ratio / loops_per_second); +} +# 1 "S:/Development/Tasmota/tasmota/sendemail.ino" +#ifdef USE_SENDMAIL + +#include "sendemail.h" +# 25 "S:/Development/Tasmota/tasmota/sendemail.ino" +#ifndef SEND_MAIL_MINRAM +#define SEND_MAIL_MINRAM 12*1024 +#endif + +#define xPSTR(a) a + +uint16_t SendMail(char *buffer) { + char *params,*oparams; + const char *mserv; + uint16_t port; + const char *user; + const char *pstr; + const char *passwd; + const char *from; + const char *to; + const char *subject; + const char *cmd; + char auth=0; + uint16_t status=1; + SendEmail *mail=0; + uint16_t blen; + char *endcmd; + + + + uint16_t mem=ESP.getFreeHeap(); + if (memsend(from,to,subject,cmd); + delete mail; + if (result==true) status=0; + } + +exit: + if (oparams) free(oparams); + return status; +} + +void script_send_email_body(BearSSL::WiFiClientSecure_light *client); + + +SendEmail::SendEmail(const String& host, const int port, const String& user, const String& passwd, const int timeout, const int auth_used) : + host(host), port(port), user(user), passwd(passwd), timeout(timeout), ssl(ssl), auth_used(auth_used), client(new BearSSL::WiFiClientSecure_light(1024,1024)) { +} + +String SendEmail::readClient() { + delay(0); + String r = client->readStringUntil('\n'); + + r.trim(); + while (client->available()) { + delay(0); + r += client->readString(); + } + return r; +} + +bool SendEmail::send(const String& from, const String& to, const String& subject, const char *msg) { +bool status=false; +String buffer; + + if (!host.length()) { + return status; + } + + client->setTimeout(timeout); + +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("Connecting: %s on port %d"),host.c_str(),port); +#endif + + if (!client->connect(host.c_str(), port)) { +#ifdef DEBUG_EMAIL_PORT + AddLog_P(LOG_LEVEL_INFO, PSTR("Connection failed")); +#endif + goto exit; + } + + buffer = readClient(); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + if (!buffer.startsWith(F("220"))) { + goto exit; + } + + buffer = F("EHLO "); + buffer += client->localIP().toString(); + + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = readClient(); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + if (!buffer.startsWith(F("250"))) { + goto exit; + } + if (user.length()>0 && passwd.length()>0 ) { + + buffer = F("AUTH LOGIN"); + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = readClient(); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + if (!buffer.startsWith(F("334"))) + { + goto exit; + } + base64 b; + buffer = b.encode(user); + + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = readClient(); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + if (!buffer.startsWith(F("334"))) { + goto exit; + } + buffer = b.encode(passwd); + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = readClient(); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + if (!buffer.startsWith(F("235"))) { + goto exit; + } + } + + + buffer = F("MAIL FROM:"); + buffer += from; + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = readClient(); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + if (!buffer.startsWith(F("250"))) { + goto exit; + } + buffer = F("RCPT TO:"); + buffer += to; + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = readClient(); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + if (!buffer.startsWith(F("250"))) { + goto exit; + } + + buffer = F("DATA"); + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = readClient(); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + if (!buffer.startsWith(F("354"))) { + goto exit; + } + buffer = F("From: "); + buffer += from; + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = F("To: "); + buffer += to; + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + buffer = F("Subject: "); + buffer += subject; + buffer += F("\r\n"); + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + +#ifdef USE_SCRIPT + if (*msg=='*' && *(msg+1)==0) { + script_send_email_body(client); + } else { + client->println(msg); + } +#else + client->println(msg); +#endif + client->println('.'); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + + buffer = F("QUIT"); + client->println(buffer); +#ifdef DEBUG_EMAIL_PORT + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); +#endif + + status=true; +exit: + + return status; +} + + +#endif +# 1 "S:/Development/Tasmota/tasmota/settings.ino" +# 20 "S:/Development/Tasmota/tasmota/settings.ino" +#ifndef DOMOTICZ_UPDATE_TIMER +#define DOMOTICZ_UPDATE_TIMER 0 +#endif + +#ifndef EMULATION +#define EMULATION EMUL_NONE +#endif + +#ifndef MTX_ADDRESS1 +#define MTX_ADDRESS1 0 +#endif +#ifndef MTX_ADDRESS2 +#define MTX_ADDRESS2 0 +#endif +#ifndef MTX_ADDRESS3 +#define MTX_ADDRESS3 0 +#endif +#ifndef MTX_ADDRESS4 +#define MTX_ADDRESS4 0 +#endif +#ifndef MTX_ADDRESS5 +#define MTX_ADDRESS5 0 +#endif +#ifndef MTX_ADDRESS6 +#define MTX_ADDRESS6 0 +#endif +#ifndef MTX_ADDRESS7 +#define MTX_ADDRESS7 0 +#endif +#ifndef MTX_ADDRESS8 +#define MTX_ADDRESS8 0 +#endif + +#ifndef HOME_ASSISTANT_DISCOVERY_ENABLE +#define HOME_ASSISTANT_DISCOVERY_ENABLE 0 +#endif + +#ifndef LATITUDE +#define LATITUDE 48.858360 +#endif +#ifndef LONGITUDE +#define LONGITUDE 2.294442 +#endif + +#ifndef WORKING_PERIOD +#define WORKING_PERIOD 5 +#endif + +#ifndef COLOR_TEXT +#define COLOR_TEXT "#000" +#endif +#ifndef COLOR_BACKGROUND +#define COLOR_BACKGROUND "#fff" +#endif +#ifndef COLOR_FORM +#define COLOR_FORM "#f2f2f2" +#endif +#ifndef COLOR_INPUT_TEXT +#define COLOR_INPUT_TEXT "#000" +#endif +#ifndef COLOR_INPUT +#define COLOR_INPUT "#fff" +#endif +#ifndef COLOR_CONSOLE_TEXT +#define COLOR_CONSOLE_TEXT "#000" +#endif +#ifndef COLOR_CONSOLE +#define COLOR_CONSOLE "#fff" +#endif +#ifndef COLOR_TEXT_WARNING +#define COLOR_TEXT_WARNING "#f00" +#endif +#ifndef COLOR_TEXT_SUCCESS +#define COLOR_TEXT_SUCCESS "#008000" +#endif +#ifndef COLOR_BUTTON_TEXT +#define COLOR_BUTTON_TEXT "#fff" +#endif +#ifndef COLOR_BUTTON +#define COLOR_BUTTON "#1fa3ec" +#endif +#ifndef COLOR_BUTTON_HOVER +#define COLOR_BUTTON_HOVER "#0e70a4" +#endif +#ifndef COLOR_BUTTON_RESET +#define COLOR_BUTTON_RESET "#d43535" +#endif +#ifndef COLOR_BUTTON_RESET_HOVER +#define COLOR_BUTTON_RESET_HOVER "#931f1f" +#endif +#ifndef COLOR_BUTTON_SAVE +#define COLOR_BUTTON_SAVE "#47c266" +#endif +#ifndef COLOR_BUTTON_SAVE_HOVER +#define COLOR_BUTTON_SAVE_HOVER "#5aaf6f" +#endif +#ifndef COLOR_TIMER_TAB_TEXT +#define COLOR_TIMER_TAB_TEXT "#fff" +#endif +#ifndef COLOR_TIMER_TAB_BACKGROUND +#define COLOR_TIMER_TAB_BACKGROUND "#999" +#endif +#ifndef COLOR_TITLE_TEXT +#define COLOR_TITLE_TEXT COLOR_TEXT +#endif +#ifndef IR_RCV_MIN_UNKNOWN_SIZE +#define IR_RCV_MIN_UNKNOWN_SIZE 6 +#endif +#ifndef ENERGY_OVERTEMP +#define ENERGY_OVERTEMP 90 +#endif +#ifndef DEFAULT_DIMMER_MAX +#define DEFAULT_DIMMER_MAX 100 +#endif +#ifndef DEFAULT_DIMMER_MIN +#define DEFAULT_DIMMER_MIN 0 +#endif +#ifndef DEFAULT_LIGHT_DIMMER +#define DEFAULT_LIGHT_DIMMER 10 +#endif +#ifndef DEFAULT_LIGHT_COMPONENT +#define DEFAULT_LIGHT_COMPONENT 255 +#endif +#ifndef CORS_ENABLED_ALL +#define CORS_ENABLED_ALL "*" +#endif + + +enum WebColors { + COL_TEXT, COL_BACKGROUND, COL_FORM, + COL_INPUT_TEXT, COL_INPUT, COL_CONSOLE_TEXT, COL_CONSOLE, + COL_TEXT_WARNING, COL_TEXT_SUCCESS, + COL_BUTTON_TEXT, COL_BUTTON, COL_BUTTON_HOVER, COL_BUTTON_RESET, COL_BUTTON_RESET_HOVER, COL_BUTTON_SAVE, COL_BUTTON_SAVE_HOVER, + COL_TIMER_TAB_TEXT, COL_TIMER_TAB_BACKGROUND, COL_TITLE, + COL_LAST }; + +const char kWebColors[] PROGMEM = + COLOR_TEXT "|" COLOR_BACKGROUND "|" COLOR_FORM "|" + COLOR_INPUT_TEXT "|" COLOR_INPUT "|" COLOR_CONSOLE_TEXT "|" COLOR_CONSOLE "|" + COLOR_TEXT_WARNING "|" COLOR_TEXT_SUCCESS "|" + COLOR_BUTTON_TEXT "|" COLOR_BUTTON "|" COLOR_BUTTON_HOVER "|" COLOR_BUTTON_RESET "|" COLOR_BUTTON_RESET_HOVER "|" COLOR_BUTTON_SAVE "|" COLOR_BUTTON_SAVE_HOVER "|" + COLOR_TIMER_TAB_TEXT "|" COLOR_TIMER_TAB_BACKGROUND "|" COLOR_TITLE_TEXT; + +enum TasmotaSerialConfig { + TS_SERIAL_5N1, TS_SERIAL_6N1, TS_SERIAL_7N1, TS_SERIAL_8N1, + TS_SERIAL_5N2, TS_SERIAL_6N2, TS_SERIAL_7N2, TS_SERIAL_8N2, + TS_SERIAL_5E1, TS_SERIAL_6E1, TS_SERIAL_7E1, TS_SERIAL_8E1, + TS_SERIAL_5E2, TS_SERIAL_6E2, TS_SERIAL_7E2, TS_SERIAL_8E2, + TS_SERIAL_5O1, TS_SERIAL_6O1, TS_SERIAL_7O1, TS_SERIAL_8O1, + TS_SERIAL_5O2, TS_SERIAL_6O2, TS_SERIAL_7O2, TS_SERIAL_8O2 }; + +const uint8_t kTasmotaSerialConfig[] PROGMEM = { + SERIAL_5N1, SERIAL_6N1, SERIAL_7N1, SERIAL_8N1, + SERIAL_5N2, SERIAL_6N2, SERIAL_7N2, SERIAL_8N2, + SERIAL_5E1, SERIAL_6E1, SERIAL_7E1, SERIAL_8E1, + SERIAL_5E2, SERIAL_6E2, SERIAL_7E2, SERIAL_8E2, + SERIAL_5O1, SERIAL_6O1, SERIAL_7O1, SERIAL_8O1, + SERIAL_5O2, SERIAL_6O2, SERIAL_7O2, SERIAL_8O2 +}; + + + + + +const uint16_t RTC_MEM_VALID = 0xA55A; + +uint32_t rtc_settings_crc = 0; + +uint32_t GetRtcSettingsCrc(void) +{ + uint32_t crc = 0; + uint8_t *bytes = (uint8_t*)&RtcSettings; + + for (uint32_t i = 0; i < sizeof(RTCMEM); i++) { + crc += bytes[i]*(i+1); + } + return crc; +} + +void RtcSettingsSave(void) +{ + if (GetRtcSettingsCrc() != rtc_settings_crc) { + RtcSettings.valid = RTC_MEM_VALID; + ESP.rtcUserMemoryWrite(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM)); + rtc_settings_crc = GetRtcSettingsCrc(); + } +} + +void RtcSettingsLoad(void) +{ + ESP.rtcUserMemoryRead(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM)); + if (RtcSettings.valid != RTC_MEM_VALID) { + memset(&RtcSettings, 0, sizeof(RTCMEM)); + RtcSettings.valid = RTC_MEM_VALID; + RtcSettings.energy_kWhtoday = Settings.energy_kWhtoday; + RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal; + RtcSettings.energy_usage = Settings.energy_usage; + for (uint32_t i = 0; i < MAX_COUNTERS; i++) { + RtcSettings.pulse_counter[i] = Settings.pulse_counter[i]; + } + RtcSettings.power = Settings.power; + RtcSettingsSave(); + } + rtc_settings_crc = GetRtcSettingsCrc(); +} + +bool RtcSettingsValid(void) +{ + return (RTC_MEM_VALID == RtcSettings.valid); +} + + + +uint32_t rtc_reboot_crc = 0; + +uint32_t GetRtcRebootCrc(void) +{ + uint32_t crc = 0; + uint8_t *bytes = (uint8_t*)&RtcReboot; + + for (uint32_t i = 0; i < sizeof(RTCRBT); i++) { + crc += bytes[i]*(i+1); + } + return crc; +} + +void RtcRebootSave(void) +{ + if (GetRtcRebootCrc() != rtc_reboot_crc) { + RtcReboot.valid = RTC_MEM_VALID; + ESP.rtcUserMemoryWrite(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT)); + rtc_reboot_crc = GetRtcRebootCrc(); + } +} + +void RtcRebootReset(void) +{ + RtcReboot.fast_reboot_count = 0; + RtcRebootSave(); +} + +void RtcRebootLoad(void) +{ + ESP.rtcUserMemoryRead(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT)); + if (RtcReboot.valid != RTC_MEM_VALID) { + memset(&RtcReboot, 0, sizeof(RTCRBT)); + RtcReboot.valid = RTC_MEM_VALID; + + RtcRebootSave(); + } + rtc_reboot_crc = GetRtcRebootCrc(); +} + +bool RtcRebootValid(void) +{ + return (RTC_MEM_VALID == RtcReboot.valid); +} +# 299 "S:/Development/Tasmota/tasmota/settings.ino" +extern "C" { +#include "spi_flash.h" +} +#include "eboot_command.h" + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2) + +extern "C" uint32_t _SPIFFS_end; + +const uint32_t SPIFFS_END = ((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE; + +#else + +#if AUTOFLASHSIZE + +#include "flash_hal.h" + + +const uint32_t SPIFFS_END = (FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE; + +#else + +extern "C" uint32_t _FS_end; + +const uint32_t SPIFFS_END = ((uint32_t)&_FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE; + +#endif + +#endif + + +const uint32_t SETTINGS_LOCATION = SPIFFS_END; + +const uint8_t CFG_ROTATES = 8; + +uint32_t settings_location = SETTINGS_LOCATION; +uint32_t settings_crc32 = 0; +uint8_t *settings_buffer = nullptr; + + + + + +void SetFlashModeDout(void) +{ + uint8_t *_buffer; + uint32_t address; + + eboot_command ebcmd; + eboot_command_read(&ebcmd); + address = ebcmd.args[0]; + _buffer = new uint8_t[FLASH_SECTOR_SIZE]; + + if (ESP.flashRead(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE)) { + if (_buffer[2] != 3) { + _buffer[2] = 3; + if (ESP.flashEraseSector(address / FLASH_SECTOR_SIZE)) { + ESP.flashWrite(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE); + } + } + } + delete[] _buffer; +} + +bool VersionCompatible(void) +{ + if (Settings.flag3.compatibility_check) { + return true; + } + + eboot_command ebcmd; + eboot_command_read(&ebcmd); + uint32_t start_address = ebcmd.args[0]; + uint32_t end_address = start_address + (ebcmd.args[2] & 0xFFFFF000) + FLASH_SECTOR_SIZE; + uint32_t* buffer = new uint32_t[FLASH_SECTOR_SIZE / 4]; + + uint32_t version[3] = { 0 }; + bool found = false; + for (uint32_t address = start_address; address < end_address; address = address + FLASH_SECTOR_SIZE) { + ESP.flashRead(address, (uint32_t*)buffer, FLASH_SECTOR_SIZE); + if ((address == start_address) && (0x1F == (buffer[0] & 0xFF))) { + version[1] = 0xFFFFFFFF; + found = true; + } else { + for (uint32_t i = 0; i < (FLASH_SECTOR_SIZE / 4); i++) { + version[0] = version[1]; + version[1] = version[2]; + version[2] = buffer[i]; + if ((MARKER_START == version[0]) && (MARKER_END == version[2])) { + found = true; + break; + } + } + } + if (found) { break; } + } + delete[] buffer; + + if (!found) { version[1] = 0; } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("OTA: Version 0x%08X, Compatible 0x%08X"), version[1], VERSION_COMPATIBLE); + + if (version[1] < VERSION_COMPATIBLE) { + uint32_t eboot_magic = 0; + ESP.rtcUserMemoryWrite(0, (uint32_t*)&eboot_magic, sizeof(eboot_magic)); + return false; + } + + return true; +} + +void SettingsBufferFree(void) +{ + if (settings_buffer != nullptr) { + free(settings_buffer); + settings_buffer = nullptr; + } +} + +bool SettingsBufferAlloc(void) +{ + SettingsBufferFree(); + if (!(settings_buffer = (uint8_t *)malloc(sizeof(Settings)))) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_UPLOAD_ERR_2)); + return false; + } + return true; +} + +uint16_t GetCfgCrc16(uint8_t *bytes, uint32_t size) +{ + uint16_t crc = 0; + + for (uint32_t i = 0; i < size; i++) { + if ((i < 14) || (i > 15)) { crc += bytes[i]*(i+1); } + } + return crc; +} + +uint16_t GetSettingsCrc(void) +{ + + uint32_t size = ((Settings.version < 0x06060007) || (Settings.version > 0x0606000A)) ? 3584 : sizeof(SYSCFG); + return GetCfgCrc16((uint8_t*)&Settings, size); +} + +uint32_t GetCfgCrc32(uint8_t *bytes, uint32_t size) +{ + + uint32_t crc = 0; + + while (size--) { + crc ^= *bytes++; + for (uint32_t j = 0; j < 8; j++) { + crc = (crc >> 1) ^ (-int(crc & 1) & 0xEDB88320); + } + } + return ~crc; +} + +uint32_t GetSettingsCrc32(void) +{ + return GetCfgCrc32((uint8_t*)&Settings, sizeof(SYSCFG) -4); +} + +void SettingsSaveAll(void) +{ + if (Settings.flag.save_state) { + Settings.power = power; + } else { + Settings.power = 0; + } + XsnsCall(FUNC_SAVE_BEFORE_RESTART); + XdrvCall(FUNC_SAVE_BEFORE_RESTART); + SettingsSave(0); +} + + + + + +void UpdateQuickPowerCycle(bool update) +{ + if (Settings.flag3.fast_power_cycle_disable) { return; } + + uint32_t pc_register; + uint32_t pc_location = SETTINGS_LOCATION - CFG_ROTATES; + + ESP.flashRead(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register)); + if (update && ((pc_register & 0xFFFFFFF0) == 0xFFA55AB0)) { + uint32_t counter = ((pc_register & 0xF) << 1) & 0xF; + if (0 == counter) { + SettingsErase(3); + EspRestart(); + } else { + pc_register = 0xFFA55AB0 | counter; + ESP.flashWrite(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register)); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Flag %02X"), counter); + } + } + else if (pc_register != 0xFFA55ABF) { + pc_register = 0xFFA55ABF; + + if (ESP.flashEraseSector(pc_location)) { + ESP.flashWrite(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register)); + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Reset")); + } +} + + + + + +uint32_t GetSettingsTextLen(void) +{ + char* position = Settings.text_pool; + for (uint32_t size = 0; size < SET_MAX; size++) { + while (*position++ != '\0') { } + } + return position - Settings.text_pool; +} + +bool SettingsUpdateText(uint32_t index, const char* replace_me) +{ + if (index >= SET_MAX) { + return false; + } + + + uint32_t replace_len = strlen(replace_me); + char replace[replace_len +1]; + memcpy(replace, replace_me, sizeof(replace)); + + uint32_t start_pos = 0; + uint32_t end_pos = 0; + char* position = Settings.text_pool; + for (uint32_t size = 0; size < SET_MAX; size++) { + while (*position++ != '\0') { } + if (1 == index) { + start_pos = position - Settings.text_pool; + } + else if (0 == index) { + end_pos = position - Settings.text_pool -1; + } + index--; + } + uint32_t char_len = position - Settings.text_pool; + + uint32_t current_len = end_pos - start_pos; + int diff = replace_len - current_len; + + + + + int too_long = (char_len + diff) - settings_text_size; + if (too_long > 0) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_CONFIG "Text overflow by %d char(s)"), too_long); + return false; + } + + if (diff != 0) { + + memmove_P(Settings.text_pool + start_pos + replace_len, Settings.text_pool + end_pos, char_len - end_pos); + } + + memmove_P(Settings.text_pool + start_pos, replace, replace_len); + + memset(Settings.text_pool + char_len + diff, 0x00, settings_text_size - char_len - diff); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG "CR %d/%d"), GetSettingsTextLen(), settings_text_size); + + return true; +} + +char* SettingsText(uint32_t index) +{ + char* position = Settings.text_pool; + + if (index >= SET_MAX) { + position += settings_text_size -1; + } else { + for (;index > 0; index--) { + while (*position++ != '\0') { } + } + } + return position; +} + + + + + +uint32_t GetSettingsAddress(void) +{ + return settings_location * SPI_FLASH_SEC_SIZE; +} + +void SettingsSave(uint8_t rotate) +{ +# 607 "S:/Development/Tasmota/tasmota/settings.ino" +#ifndef FIRMWARE_MINIMAL + if ((GetSettingsCrc32() != settings_crc32) || rotate) { + if (1 == rotate) { + stop_flash_rotate = 1; + } + if (2 == rotate) { + settings_location = SETTINGS_LOCATION +1; + } + if (stop_flash_rotate) { + settings_location = SETTINGS_LOCATION; + } else { + settings_location--; + if (settings_location <= (SETTINGS_LOCATION - CFG_ROTATES)) { + settings_location = SETTINGS_LOCATION; + } + } + + Settings.save_flag++; + if (UtcTime() > START_VALID_TIME) { + Settings.cfg_timestamp = UtcTime(); + } else { + Settings.cfg_timestamp++; + } + Settings.cfg_size = sizeof(SYSCFG); + Settings.cfg_crc = GetSettingsCrc(); + Settings.cfg_crc32 = GetSettingsCrc32(); + + if (ESP.flashEraseSector(settings_location)) { + ESP.flashWrite(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG)); + } + + if (!stop_flash_rotate && rotate) { + for (uint32_t i = 1; i < CFG_ROTATES; i++) { + ESP.flashEraseSector(settings_location -i); + delay(1); + } + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG D_SAVED_TO_FLASH_AT " %X, " D_COUNT " %d, " D_BYTES " %d"), settings_location, Settings.save_flag, sizeof(SYSCFG)); + + settings_crc32 = Settings.cfg_crc32; + } +#endif + RtcSettingsSave(); +} + +void SettingsLoad(void) +{ + + struct SYSCFGH { + uint16_t cfg_holder; + uint16_t cfg_size; + unsigned long save_flag; + } _SettingsH; + unsigned long save_flag = 0; + + settings_location = 0; + uint32_t flash_location = SETTINGS_LOCATION +1; + uint16_t cfg_holder = 0; + for (uint32_t i = 0; i < CFG_ROTATES; i++) { + flash_location--; + ESP.flashRead(flash_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG)); + + bool valid = false; + if (Settings.version > 0x06000000) { + bool almost_valid = (Settings.cfg_crc32 == GetSettingsCrc32()); + if (Settings.version < 0x0606000B) { + almost_valid = (Settings.cfg_crc == GetSettingsCrc()); + } + + if (almost_valid && (0 == cfg_holder)) { cfg_holder = Settings.cfg_holder; } + valid = (cfg_holder == Settings.cfg_holder); + } else { + ESP.flashRead((flash_location -1) * SPI_FLASH_SEC_SIZE, (uint32*)&_SettingsH, sizeof(SYSCFGH)); + valid = (Settings.cfg_holder == _SettingsH.cfg_holder); + } + if (valid) { + if (Settings.save_flag > save_flag) { + save_flag = Settings.save_flag; + settings_location = flash_location; + if (Settings.flag.stop_flash_rotate && (0 == i)) { + break; + } + } + } + + delay(1); + } + if (settings_location > 0) { + ESP.flashRead(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG)); + AddLog_P2(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_LOADED_FROM_FLASH_AT " %X, " D_COUNT " %lu"), settings_location, Settings.save_flag); + } + +#ifndef FIRMWARE_MINIMAL + if (!settings_location || (Settings.cfg_holder != (uint16_t)CFG_HOLDER)) { + SettingsDefault(); + } + settings_crc32 = GetSettingsCrc32(); +#endif + + RtcSettingsLoad(); +} + +void EspErase(uint32_t start_sector, uint32_t end_sector) +{ + bool serial_output = (LOG_LEVEL_DEBUG_MORE <= seriallog_level); + for (uint32_t sector = start_sector; sector < end_sector; sector++) { + + bool result = ESP.flashEraseSector(sector); + + + + if (serial_output) { +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + Serial.printf(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n", sector, (result) ? D_OK : D_ERROR); +#else + Serial.printf_P(PSTR(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n"), sector, (result) ? D_OK : D_ERROR); +#endif + delay(10); + } else { + yield(); + } + OsWatchLoop(); + } +} + +void SettingsErase(uint8_t type) +{ +# 750 "S:/Development/Tasmota/tasmota/settings.ino" +#ifndef FIRMWARE_MINIMAL + uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1; + uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE; + if (1 == type) { + + _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4; + } + else if (2 == type) { + _sectorStart = SETTINGS_LOCATION - CFG_ROTATES; + _sectorEnd = SETTINGS_LOCATION +1; + } + else if (3 == type) { + _sectorStart = SETTINGS_LOCATION - CFG_ROTATES; + _sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; + } +#ifdef USE_WIFI_SDK_ERASE + else if (4 == type) { + _sectorStart = SETTINGS_LOCATION +1; + _sectorEnd = _sectorStart +1; + } + else if (5 == type) { + _sectorStart = (ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE) -4; + _sectorEnd = _sectorStart +1; + } +#endif + 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); + + + EsptoolErase(_sectorStart, _sectorEnd); +#endif +} + +void SettingsSdkErase(void) +{ + WiFi.disconnect(false); + SettingsErase(1); + delay(1000); +} + +#ifdef USE_WIFI_SDK_ERASE +void SettingsSdkWifiErase(void) +{ + WiFi.disconnect(false); + SettingsErase(4); + SettingsErase(5); + delay(200); +} +#endif + + + +void SettingsDefault(void) +{ + AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_USE_DEFAULTS)); + SettingsDefaultSet1(); + SettingsDefaultSet2(); + SettingsSave(2); +} + +void SettingsDefaultSet1(void) +{ + memset(&Settings, 0x00, sizeof(SYSCFG)); + + Settings.cfg_holder = (uint16_t)CFG_HOLDER; + Settings.cfg_size = sizeof(SYSCFG); + + Settings.version = VERSION; + + +} + +void SettingsDefaultSet2(void) +{ + memset((char*)&Settings +16, 0x00, sizeof(SYSCFG) -16); + + Settings.flag.stop_flash_rotate = APP_FLASH_CYCLE; + Settings.flag.global_state = APP_ENABLE_LEDLINK; + Settings.flag3.sleep_normal = APP_NORMAL_SLEEP; + Settings.flag3.no_power_feedback = APP_NO_RELAY_SCAN; + Settings.flag3.fast_power_cycle_disable = APP_DISABLE_POWERCYCLE; + Settings.flag3.bootcount_update = DEEPSLEEP_BOOTCOUNT; + Settings.flag3.compatibility_check = OTA_COMPATIBILITY; + Settings.save_data = SAVE_DATA; + Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY; + Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET; + Settings.param[P_RGB_REMAP] = RGB_REMAP_RGBW; + Settings.sleep = APP_SLEEP; + if (Settings.sleep < 50) { + Settings.sleep = 50; + } + + + + Settings.interlock[0] = 0xFF; + Settings.module = MODULE; + ModuleDefault(WEMOS); + + SettingsUpdateText(SET_FRIENDLYNAME1, FRIENDLY_NAME); + SettingsUpdateText(SET_FRIENDLYNAME2, FRIENDLY_NAME"2"); + SettingsUpdateText(SET_FRIENDLYNAME3, FRIENDLY_NAME"3"); + SettingsUpdateText(SET_FRIENDLYNAME4, FRIENDLY_NAME"4"); + SettingsUpdateText(SET_OTAURL, OTA_URL); + + + Settings.flag.save_state = SAVE_STATE; + Settings.power = APP_POWER; + Settings.poweronstate = APP_POWERON_STATE; + Settings.blinktime = APP_BLINKTIME; + Settings.blinkcount = APP_BLINKCOUNT; + Settings.ledstate = APP_LEDSTATE; + Settings.ledmask = APP_LEDMASK; + Settings.pulse_timer[0] = APP_PULSETIME; + + + + Settings.serial_config = TS_SERIAL_8N1; + Settings.baudrate = APP_BAUDRATE / 300; + Settings.sbaudrate = SOFT_BAUDRATE / 300; + Settings.serial_delimiter = 0xff; + Settings.seriallog_level = SERIAL_LOG_LEVEL; + + + Settings.flag3.use_wifi_scan = WIFI_SCAN_AT_RESTART; + Settings.flag3.use_wifi_rescan = WIFI_SCAN_REGULARLY; + Settings.wifi_output_power = 170; + ParseIp(&Settings.ip_address[0], WIFI_IP_ADDRESS); + ParseIp(&Settings.ip_address[1], WIFI_GATEWAY); + ParseIp(&Settings.ip_address[2], WIFI_SUBNETMASK); + ParseIp(&Settings.ip_address[3], WIFI_DNS); + Settings.sta_config = WIFI_CONFIG_TOOL; + + SettingsUpdateText(SET_STASSID1, STA_SSID1); + SettingsUpdateText(SET_STASSID2, STA_SSID2); + SettingsUpdateText(SET_STAPWD1, STA_PASS1); + SettingsUpdateText(SET_STAPWD2, STA_PASS2); + SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME); + + + SettingsUpdateText(SET_SYSLOG_HOST, SYS_LOG_HOST); + Settings.syslog_port = SYS_LOG_PORT; + Settings.syslog_level = SYS_LOG_LEVEL; + + + Settings.flag2.emulation = EMULATION; + Settings.flag3.gui_hostname_ip = GUI_SHOW_HOSTNAME; + Settings.flag3.mdns_enabled = MDNS_ENABLED; + Settings.webserver = WEB_SERVER; + Settings.weblog_level = WEB_LOG_LEVEL; + SettingsUpdateText(SET_WEBPWD, WEB_PASSWORD); + SettingsUpdateText(SET_CORS, CORS_DOMAIN); + + + Settings.flag.button_restrict = KEY_DISABLE_MULTIPRESS; + Settings.flag.button_swap = KEY_SWAP_DOUBLE_PRESS; + Settings.flag.button_single = KEY_ONLY_SINGLE_PRESS; + Settings.param[P_HOLD_TIME] = KEY_HOLD_TIME; + + + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { Settings.switchmode[i] = SWITCH_MODE; } + + + Settings.flag.mqtt_enabled = MQTT_USE; + Settings.flag.mqtt_response = MQTT_RESULT_COMMAND; + Settings.flag.mqtt_offline = MQTT_LWT_MESSAGE; + Settings.flag.mqtt_power_retain = MQTT_POWER_RETAIN; + Settings.flag.mqtt_button_retain = MQTT_BUTTON_RETAIN; + Settings.flag.mqtt_switch_retain = MQTT_SWITCH_RETAIN; + Settings.flag.mqtt_sensor_retain = MQTT_SENSOR_RETAIN; + + Settings.flag.device_index_enable = MQTT_POWER_FORMAT; + Settings.flag3.time_append_timezone = MQTT_APPEND_TIMEZONE; + Settings.flag3.button_switch_force_local = MQTT_BUTTON_SWITCH_FORCE_LOCAL; + Settings.flag3.no_hold_retain = MQTT_NO_HOLD_RETAIN; + Settings.flag3.use_underscore = MQTT_INDEX_SEPARATOR; + Settings.flag3.grouptopic_mode = MQTT_GROUPTOPIC_FORMAT; + SettingsUpdateText(SET_MQTT_HOST, MQTT_HOST); + Settings.mqtt_port = MQTT_PORT; + SettingsUpdateText(SET_MQTT_CLIENT, MQTT_CLIENT_ID); + SettingsUpdateText(SET_MQTT_USER, MQTT_USER); + SettingsUpdateText(SET_MQTT_PWD, MQTT_PASS); + SettingsUpdateText(SET_MQTT_TOPIC, MQTT_TOPIC); + SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC); + SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC); + SettingsUpdateText(SET_MQTT_GRP_TOPIC, MQTT_GRPTOPIC); + SettingsUpdateText(SET_MQTT_FULLTOPIC, MQTT_FULLTOPIC); + Settings.mqtt_retry = MQTT_RETRY_SECS; + SettingsUpdateText(SET_MQTTPREFIX1, SUB_PREFIX); + SettingsUpdateText(SET_MQTTPREFIX2, PUB_PREFIX); + SettingsUpdateText(SET_MQTTPREFIX3, PUB_PREFIX2); + SettingsUpdateText(SET_STATE_TXT1, MQTT_STATUS_OFF); + SettingsUpdateText(SET_STATE_TXT2, MQTT_STATUS_ON); + SettingsUpdateText(SET_STATE_TXT3, MQTT_CMND_TOGGLE); + SettingsUpdateText(SET_STATE_TXT4, MQTT_CMND_HOLD); + char fingerprint[60]; + strlcpy(fingerprint, MQTT_FINGERPRINT1, sizeof(fingerprint)); + char *p = fingerprint; + for (uint32_t i = 0; i < 20; i++) { + Settings.mqtt_fingerprint[0][i] = strtol(p, &p, 16); + } + strlcpy(fingerprint, MQTT_FINGERPRINT2, sizeof(fingerprint)); + p = fingerprint; + for (uint32_t i = 0; i < 20; i++) { + Settings.mqtt_fingerprint[1][i] = strtol(p, &p, 16); + } + Settings.tele_period = TELE_PERIOD; + Settings.mqttlog_level = MQTT_LOG_LEVEL; + + + Settings.flag.no_power_on_check = ENERGY_VOLTAGE_ALWAYS; + Settings.flag2.current_resolution = 3; + + + Settings.flag2.energy_resolution = ENERGY_RESOLUTION; + Settings.flag3.dds2382_model = ENERGY_DDS2382_MODE; + Settings.flag3.hardware_energy_total = ENERGY_HARDWARE_TOTALS; + Settings.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY; + + Settings.energy_power_calibration = HLW_PREF_PULSE; + Settings.energy_voltage_calibration = HLW_UREF_PULSE; + Settings.energy_current_calibration = HLW_IREF_PULSE; +# 980 "S:/Development/Tasmota/tasmota/settings.ino" + Settings.energy_max_power_limit_hold = MAX_POWER_HOLD; + Settings.energy_max_power_limit_window = MAX_POWER_WINDOW; + + Settings.energy_max_power_safe_limit_hold = SAFE_POWER_HOLD; + Settings.energy_max_power_safe_limit_window = SAFE_POWER_WINDOW; + + + + RtcSettings.energy_kWhtotal = 0; + + memset((char*)&RtcSettings.energy_usage, 0x00, sizeof(RtcSettings.energy_usage)); + Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP; + + + Settings.flag.ir_receive_decimal = IR_DATA_RADIX; + Settings.flag3.receive_raw = IR_ADD_RAW_DATA; + Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE; + + + Settings.flag.rf_receive_decimal = RF_DATA_RADIX; + + memcpy_P(Settings.rf_code[0], kDefaultRfCode, 9); + + + Settings.domoticz_update_timer = DOMOTICZ_UPDATE_TIMER; +# 1015 "S:/Development/Tasmota/tasmota/settings.ino" + Settings.flag.temperature_conversion = TEMP_CONVERSION; + Settings.flag.pressure_conversion = PRESSURE_CONVERSION; + Settings.flag2.pressure_resolution = PRESSURE_RESOLUTION; + Settings.flag2.humidity_resolution = HUMIDITY_RESOLUTION; + Settings.flag2.temperature_resolution = TEMP_RESOLUTION; + Settings.flag3.ds18x20_internal_pullup = DS18X20_PULL_UP; + Settings.flag3.counter_reset_on_tele = COUNTER_RESET; + + + + + + + Settings.flag2.calc_resolution = CALC_RESOLUTION; + + + Settings.flag3.timers_enable = TIMERS_ENABLED; + + + Settings.flag.hass_light = HASS_AS_LIGHT; + Settings.flag.hass_discovery = HOME_ASSISTANT_DISCOVERY_ENABLE; + Settings.flag3.hass_tele_on_power = TELE_ON_POWER; + + + Settings.flag.knx_enabled = KNX_ENABLED; + Settings.flag.knx_enable_enhancement = KNX_ENHANCED; + + + Settings.flag.pwm_control = LIGHT_MODE; + Settings.flag.ws_clock_reverse = LIGHT_CLOCK_DIRECTION; + Settings.flag.light_signal = LIGHT_PAIRS_CO2; + Settings.flag.not_power_linked = LIGHT_POWER_CONTROL; + Settings.flag.decimal_text = LIGHT_COLOR_RADIX; + Settings.flag3.pwm_multi_channels = LIGHT_CHANNEL_MODE; + Settings.flag3.slider_dimmer_stay_on = LIGHT_SLIDER_POWER; + Settings.flag4.alexa_ct_range = LIGHT_ALEXA_CT_RANGE; + + Settings.pwm_frequency = PWM_FREQ; + Settings.pwm_range = PWM_RANGE; + for (uint32_t i = 0; i < MAX_PWMS; i++) { + Settings.light_color[i] = DEFAULT_LIGHT_COMPONENT; + + } + Settings.light_correction = 1; + Settings.light_dimmer = DEFAULT_LIGHT_DIMMER; + + Settings.light_speed = 1; + + Settings.light_width = 1; + + Settings.light_pixels = WS2812_LEDS; + + Settings.ws_width[WS_SECOND] = 1; + Settings.ws_color[WS_SECOND][WS_RED] = 255; + + Settings.ws_color[WS_SECOND][WS_BLUE] = 255; + Settings.ws_width[WS_MINUTE] = 3; + + Settings.ws_color[WS_MINUTE][WS_GREEN] = 255; + + Settings.ws_width[WS_HOUR] = 5; + Settings.ws_color[WS_HOUR][WS_RED] = 255; + + + + Settings.dimmer_hw_max = DEFAULT_DIMMER_MAX; + Settings.dimmer_hw_min = DEFAULT_DIMMER_MIN; + + + + Settings.display_mode = 1; + Settings.display_refresh = 2; + Settings.display_rows = 2; + Settings.display_cols[0] = 16; + Settings.display_cols[1] = 8; + Settings.display_dimmer = 1; + Settings.display_size = 1; + Settings.display_font = 1; + + Settings.display_address[0] = MTX_ADDRESS1; + Settings.display_address[1] = MTX_ADDRESS2; + Settings.display_address[2] = MTX_ADDRESS3; + Settings.display_address[3] = MTX_ADDRESS4; + Settings.display_address[4] = MTX_ADDRESS5; + Settings.display_address[5] = MTX_ADDRESS6; + Settings.display_address[6] = MTX_ADDRESS7; + Settings.display_address[7] = MTX_ADDRESS8; + + + if (((APP_TIMEZONE > -14) && (APP_TIMEZONE < 15)) || (99 == APP_TIMEZONE)) { + Settings.timezone = APP_TIMEZONE; + Settings.timezone_minutes = 0; + } else { + Settings.timezone = APP_TIMEZONE / 60; + Settings.timezone_minutes = abs(APP_TIMEZONE % 60); + } + SettingsUpdateText(SET_NTPSERVER1, NTP_SERVER1); + SettingsUpdateText(SET_NTPSERVER2, NTP_SERVER2); + SettingsUpdateText(SET_NTPSERVER3, NTP_SERVER3); + for (uint32_t i = 0; i < MAX_NTP_SERVERS; i++) { + SettingsUpdateText(SET_NTPSERVER1 +i, ReplaceCommaWithDot(SettingsText(SET_NTPSERVER1 +i))); + } + Settings.latitude = (int)((double)LATITUDE * 1000000); + Settings.longitude = (int)((double)LONGITUDE * 1000000); + SettingsResetStd(); + SettingsResetDst(); + + Settings.button_debounce = KEY_DEBOUNCE_TIME; + Settings.switch_debounce = SWITCH_DEBOUNCE_TIME; + + for (uint32_t j = 0; j < 5; j++) { + Settings.rgbwwTable[j] = 255; + } + + Settings.novasds_startingoffset = STARTING_OFFSET; + + SettingsDefaultWebColor(); + + memset(&Settings.monitors, 0xFF, 20); + SettingsEnableAllI2cDrivers(); + + + Settings.flag3.tuya_apply_o20 = TUYA_SETOPTION_20; + Settings.flag3.tuya_serial_mqtt_publish = MQTT_TUYA_RECEIVED; + + Settings.flag3.buzzer_enable = BUZZER_ENABLE; + Settings.flag3.shutter_mode = SHUTTER_SUPPORT; + Settings.flag3.pcf8574_ports_inverted = PCF8574_INVERT_PORTS; + Settings.flag4.zigbee_use_names = ZIGBEE_FRIENDLY_NAMES; +} + + + +void SettingsResetStd(void) +{ + Settings.tflag[0].hemis = TIME_STD_HEMISPHERE; + Settings.tflag[0].week = TIME_STD_WEEK; + Settings.tflag[0].dow = TIME_STD_DAY; + Settings.tflag[0].month = TIME_STD_MONTH; + Settings.tflag[0].hour = TIME_STD_HOUR; + Settings.toffset[0] = TIME_STD_OFFSET; +} + +void SettingsResetDst(void) +{ + Settings.tflag[1].hemis = TIME_DST_HEMISPHERE; + Settings.tflag[1].week = TIME_DST_WEEK; + Settings.tflag[1].dow = TIME_DST_DAY; + Settings.tflag[1].month = TIME_DST_MONTH; + Settings.tflag[1].hour = TIME_DST_HOUR; + Settings.toffset[1] = TIME_DST_OFFSET; +} + +void SettingsDefaultWebColor(void) +{ + char scolor[10]; + for (uint32_t i = 0; i < COL_LAST; i++) { + WebHexCode(i, GetTextIndexed(scolor, sizeof(scolor), i, kWebColors)); + } +} + +void SettingsEnableAllI2cDrivers(void) +{ + Settings.i2c_drivers[0] = 0xFFFFFFFF; + Settings.i2c_drivers[1] = 0xFFFFFFFF; + Settings.i2c_drivers[2] = 0xFFFFFFFF; +} + + + +void SettingsDelta(void) +{ + if (Settings.version != VERSION) { + if (Settings.version < 0x06000000) { + Settings.cfg_size = sizeof(SYSCFG); + Settings.cfg_crc = GetSettingsCrc(); + } + if (Settings.version < 0x06000002) { + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { + if (i < 4) { + Settings.switchmode[i] = Settings.interlock[i]; + } else { + Settings.switchmode[i] = SWITCH_MODE; + } + } + for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { + if (Settings.my_gp.io[i] >= GPIO_SWT5) { + Settings.my_gp.io[i] += 4; + } + } + } + if (Settings.version < 0x06000003) { + Settings.flag.mqtt_serial_raw = 0; + Settings.flag.pressure_conversion = 0; + Settings.flag3.data = 0; + } + if (Settings.version < 0x06010103) { + Settings.flag3.timers_enable = 1; + } + if (Settings.version < 0x0601010C) { + Settings.button_debounce = KEY_DEBOUNCE_TIME; + Settings.switch_debounce = SWITCH_DEBOUNCE_TIME; + } + if (Settings.version < 0x0602010A) { + for (uint32_t j = 0; j < 5; j++) { + Settings.rgbwwTable[j] = 255; + } + } + if (Settings.version < 0x06030002) { + Settings.timezone_minutes = 0; + } + if (Settings.version < 0x06030004) { + memset(&Settings.monitors, 0xFF, 20); + } + if (Settings.version < 0x0603000E) { + Settings.flag2.calc_resolution = CALC_RESOLUTION; + } + if (Settings.version < 0x0603000F) { + if (Settings.sleep < 50) { + Settings.sleep = 50; + } + } + if (Settings.version < 0x06040105) { + Settings.flag3.mdns_enabled = MDNS_ENABLED; + Settings.param[P_MDNS_DELAYED_START] = 0; + } + if (Settings.version < 0x0604010B) { + Settings.interlock[0] = 0xFF; + for (uint32_t i = 1; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; } + } + if (Settings.version < 0x0604010D) { + Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET; + } + if (Settings.version < 0x06040110) { + ModuleDefault(WEMOS); + } + if (Settings.version < 0x06040113) { + Settings.param[P_RGB_REMAP] = RGB_REMAP_RGBW; + } + if (Settings.version < 0x06050003) { + Settings.novasds_startingoffset = STARTING_OFFSET; + } + if (Settings.version < 0x06050006) { + SettingsDefaultWebColor(); + } + if (Settings.version < 0x06050007) { + Settings.ledmask = APP_LEDMASK; + } + if (Settings.version < 0x0605000A) { + Settings.my_adc0 = ADC0_NONE; + } + if (Settings.version < 0x0605000D) { + Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE; + } + if (Settings.version < 0x06060001) { + Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP; + } + if (Settings.version < 0x06060007) { + memset((char*)&Settings +0xE00, 0x00, sizeof(SYSCFG) -0xE00); + } + if (Settings.version < 0x06060008) { + + if (Settings.flag3.tuya_serial_mqtt_publish) { + Settings.param[P_ex_DIMMER_MAX] = 100; + } else { + Settings.param[P_ex_DIMMER_MAX] = 255; + } + } + if (Settings.version < 0x06060009) { + Settings.baudrate = APP_BAUDRATE / 300; + Settings.sbaudrate = SOFT_BAUDRATE / 300; + } + if (Settings.version < 0x0606000A) { + uint8_t tuyaindex = 0; + if (Settings.param[P_BACKLOG_DELAY] > 0) { + Settings.tuya_fnid_map[tuyaindex].fnid = 21; + Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_BACKLOG_DELAY]; + tuyaindex++; + } else if (Settings.flag3.fast_power_cycle_disable == 1) { + Settings.tuya_fnid_map[tuyaindex].fnid = 11; + Settings.tuya_fnid_map[tuyaindex].dpid = 1; + tuyaindex++; + } + if (Settings.param[P_ex_TUYA_RELAYS] > 0) { + for (uint8_t i = 0 ; i < Settings.param[P_ex_TUYA_RELAYS]; i++) { + Settings.tuya_fnid_map[tuyaindex].fnid = 12 + i; + Settings.tuya_fnid_map[tuyaindex].dpid = i + 2; + tuyaindex++; + } + } + if (Settings.param[P_ex_TUYA_POWER_ID] > 0) { + Settings.tuya_fnid_map[tuyaindex].fnid = 31; + Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_POWER_ID]; + tuyaindex++; + } + if (Settings.param[P_ex_TUYA_VOLTAGE_ID] > 0) { + Settings.tuya_fnid_map[tuyaindex].fnid = 33; + Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_VOLTAGE_ID]; + tuyaindex++; + } + if (Settings.param[P_ex_TUYA_CURRENT_ID] > 0) { + Settings.tuya_fnid_map[tuyaindex].fnid = 32; + Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_CURRENT_ID]; + } + } + if (Settings.version < 0x0606000C) { + memset((char*)&Settings +0x1D6, 0x00, 16); + } + if (Settings.version < 0x0606000F) { + Settings.ex_shutter_accuracy = 0; + Settings.ex_mqttlog_level = MQTT_LOG_LEVEL; + } + if (Settings.version < 0x06060011) { + Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY; + } + if (Settings.version < 0x06060012) { + Settings.dimmer_hw_min = DEFAULT_DIMMER_MIN; + Settings.dimmer_hw_max = DEFAULT_DIMMER_MAX; + if (TUYA_DIMMER == Settings.module) { + if (Settings.flag3.ex_tuya_dimmer_min_limit) { + Settings.dimmer_hw_min = 25; + } else { + Settings.dimmer_hw_min = 1; + } + Settings.dimmer_hw_max = Settings.param[P_ex_DIMMER_MAX]; + } + else if (PS_16_DZ == Settings.module) { + Settings.dimmer_hw_min = 10; + Settings.dimmer_hw_max = Settings.param[P_ex_DIMMER_MAX]; + } + } + if (Settings.version < 0x06060014) { +# 1359 "S:/Development/Tasmota/tasmota/settings.ino" + Settings.flag3.fast_power_cycle_disable = 0; + Settings.energy_power_delta = Settings.ex_energy_power_delta; + Settings.ex_energy_power_delta = 0; + } + if (Settings.version < 0x06060015) { + if ((EX_WIFI_SMARTCONFIG == Settings.ex_sta_config) || (EX_WIFI_WPSCONFIG == Settings.ex_sta_config)) { + Settings.ex_sta_config = WIFI_MANAGER; + } + } + + if (Settings.version < 0x07000002) { + Settings.web_color2[0][0] = Settings.web_color[0][0]; + Settings.web_color2[0][1] = Settings.web_color[0][1]; + Settings.web_color2[0][2] = Settings.web_color[0][2]; + } + if (Settings.version < 0x07000003) { + SettingsEnableAllI2cDrivers(); + } + if (Settings.version < 0x07000004) { + Settings.ex_wifi_output_power = 170; + } + if (Settings.version < 0x07010202) { + Settings.ex_serial_config = TS_SERIAL_8N1; + } + if (Settings.version < 0x07010204) { + if (Settings.flag3.ex_cors_enabled == 1) { + strlcpy(Settings.ex_cors_domain, CORS_ENABLED_ALL, sizeof(Settings.ex_cors_domain)); + } else { + Settings.ex_cors_domain[0] = 0; + } + } + if (Settings.version < 0x07010205) { + Settings.seriallog_level = Settings.ex_seriallog_level; + Settings.sta_config = Settings.ex_sta_config; + Settings.sta_active = Settings.ex_sta_active; + memcpy((char*)&Settings.rule_stop, (char*)&Settings.ex_rule_stop, 47); + } + if (Settings.version < 0x07010206) { + Settings.flag4 = Settings.ex_flag4; + Settings.mqtt_port = Settings.ex_mqtt_port; + memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5); + } + + if (Settings.version < 0x08000000) { + char temp[strlen(Settings.text_pool) +1]; strncpy(temp, Settings.text_pool, sizeof(temp)); + char temp21[strlen(Settings.ex_mqtt_prefix[0]) +1]; strncpy(temp21, Settings.ex_mqtt_prefix[0], sizeof(temp21)); + char temp22[strlen(Settings.ex_mqtt_prefix[1]) +1]; strncpy(temp22, Settings.ex_mqtt_prefix[1], sizeof(temp22)); + char temp23[strlen(Settings.ex_mqtt_prefix[2]) +1]; strncpy(temp23, Settings.ex_mqtt_prefix[2], sizeof(temp23)); + char temp31[strlen(Settings.ex_sta_ssid[0]) +1]; strncpy(temp31, Settings.ex_sta_ssid[0], sizeof(temp31)); + char temp32[strlen(Settings.ex_sta_ssid[1]) +1]; strncpy(temp32, Settings.ex_sta_ssid[1], sizeof(temp32)); + char temp41[strlen(Settings.ex_sta_pwd[0]) +1]; strncpy(temp41, Settings.ex_sta_pwd[0], sizeof(temp41)); + char temp42[strlen(Settings.ex_sta_pwd[1]) +1]; strncpy(temp42, Settings.ex_sta_pwd[1], sizeof(temp42)); + char temp5[strlen(Settings.ex_hostname) +1]; strncpy(temp5, Settings.ex_hostname, sizeof(temp5)); + char temp6[strlen(Settings.ex_syslog_host) +1]; strncpy(temp6, Settings.ex_syslog_host, sizeof(temp6)); + char temp7[strlen(Settings.ex_mqtt_host) +1]; strncpy(temp7, Settings.ex_mqtt_host, sizeof(temp7)); + char temp8[strlen(Settings.ex_mqtt_client) +1]; strncpy(temp8, Settings.ex_mqtt_client, sizeof(temp8)); + char temp9[strlen(Settings.ex_mqtt_user) +1]; strncpy(temp9, Settings.ex_mqtt_user, sizeof(temp9)); + char temp10[strlen(Settings.ex_mqtt_pwd) +1]; strncpy(temp10, Settings.ex_mqtt_pwd, sizeof(temp10)); + char temp11[strlen(Settings.ex_mqtt_topic) +1]; strncpy(temp11, Settings.ex_mqtt_topic, sizeof(temp11)); + char temp12[strlen(Settings.ex_button_topic) +1]; strncpy(temp12, Settings.ex_button_topic, sizeof(temp12)); + char temp13[strlen(Settings.ex_mqtt_grptopic) +1]; strncpy(temp13, Settings.ex_mqtt_grptopic, sizeof(temp13)); + + memset(Settings.text_pool, 0x00, settings_text_size); + SettingsUpdateText(SET_OTAURL, temp); + SettingsUpdateText(SET_MQTTPREFIX1, temp21); + SettingsUpdateText(SET_MQTTPREFIX2, temp22); + SettingsUpdateText(SET_MQTTPREFIX3, temp23); + SettingsUpdateText(SET_STASSID1, temp31); + SettingsUpdateText(SET_STASSID2, temp32); + SettingsUpdateText(SET_STAPWD1, temp41); + SettingsUpdateText(SET_STAPWD2, temp42); + SettingsUpdateText(SET_HOSTNAME, temp5); + SettingsUpdateText(SET_SYSLOG_HOST, temp6); +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + if (!strlen(Settings.ex_mqtt_user)) { + SettingsUpdateText(SET_MQTT_HOST, temp7); + SettingsUpdateText(SET_MQTT_USER, temp9); + } else { + char aws_mqtt_host[66]; + snprintf_P(aws_mqtt_host, sizeof(aws_mqtt_host), PSTR("%s%s"), temp9, temp7); + SettingsUpdateText(SET_MQTT_HOST, aws_mqtt_host); + SettingsUpdateText(SET_MQTT_USER, ""); + } +#else + SettingsUpdateText(SET_MQTT_HOST, temp7); + SettingsUpdateText(SET_MQTT_USER, temp9); +#endif + SettingsUpdateText(SET_MQTT_CLIENT, temp8); + SettingsUpdateText(SET_MQTT_PWD, temp10); + SettingsUpdateText(SET_MQTT_TOPIC, temp11); + SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, temp12); + SettingsUpdateText(SET_MQTT_GRP_TOPIC, temp13); + + SettingsUpdateText(SET_WEBPWD, Settings.ex_web_password); + SettingsUpdateText(SET_CORS, Settings.ex_cors_domain); + SettingsUpdateText(SET_MQTT_FULLTOPIC, Settings.ex_mqtt_fulltopic); + SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, Settings.ex_switch_topic); + SettingsUpdateText(SET_STATE_TXT1, Settings.ex_state_text[0]); + SettingsUpdateText(SET_STATE_TXT2, Settings.ex_state_text[1]); + SettingsUpdateText(SET_STATE_TXT3, Settings.ex_state_text[2]); + SettingsUpdateText(SET_STATE_TXT4, Settings.ex_state_text[3]); + SettingsUpdateText(SET_NTPSERVER1, Settings.ex_ntp_server[0]); + SettingsUpdateText(SET_NTPSERVER2, Settings.ex_ntp_server[1]); + SettingsUpdateText(SET_NTPSERVER3, Settings.ex_ntp_server[2]); + SettingsUpdateText(SET_MEM1, Settings.script_pram[0]); + SettingsUpdateText(SET_MEM2, Settings.script_pram[1]); + SettingsUpdateText(SET_MEM3, Settings.script_pram[2]); + SettingsUpdateText(SET_MEM4, Settings.script_pram[3]); + SettingsUpdateText(SET_MEM5, Settings.script_pram[4]); + SettingsUpdateText(SET_FRIENDLYNAME1, Settings.ex_friendlyname[0]); + SettingsUpdateText(SET_FRIENDLYNAME2, Settings.ex_friendlyname[1]); + SettingsUpdateText(SET_FRIENDLYNAME3, Settings.ex_friendlyname[2]); + SettingsUpdateText(SET_FRIENDLYNAME4, Settings.ex_friendlyname[3]); + } + + Settings.version = VERSION; + SettingsSave(1); + } +} +# 1 "S:/Development/Tasmota/tasmota/support.ino" +# 20 "S:/Development/Tasmota/tasmota/support.ino" +IPAddress syslog_host_addr; +uint32_t syslog_host_hash = 0; + +extern "C" { +extern struct rst_info resetInfo; +} + + + + + +#include + +Ticker tickerOSWatch; + +const uint32_t OSWATCH_RESET_TIME = 120; + +static unsigned long oswatch_last_loop_time; +uint8_t oswatch_blocked_loop = 0; + +#ifndef USE_WS2812_DMA + +#endif + +#ifdef USE_KNX +bool knx_started = false; +#endif + +void OsWatchTicker(void) +{ + uint32_t t = millis(); + uint32_t last_run = abs(t - oswatch_last_loop_time); + +#ifdef DEBUG_THEO + int32_t rssi = WiFi.RSSI(); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_OSWATCH " FreeRam %d, rssi %d %% (%d dBm), last_run %d"), ESP.getFreeHeap(), WifiGetRssiAsQuality(rssi), rssi, last_run); +#endif + if (last_run >= (OSWATCH_RESET_TIME * 1000)) { + + RtcSettings.oswatch_blocked_loop = 1; + RtcSettingsSave(); + + + + + + volatile uint32_t dummy; + dummy = *((uint32_t*) 0x00000000); + } +} + +void OsWatchInit(void) +{ + oswatch_blocked_loop = RtcSettings.oswatch_blocked_loop; + RtcSettings.oswatch_blocked_loop = 0; + oswatch_last_loop_time = millis(); + tickerOSWatch.attach_ms(((OSWATCH_RESET_TIME / 3) * 1000), OsWatchTicker); +} + +void OsWatchLoop(void) +{ + oswatch_last_loop_time = millis(); + +} + +bool OsWatchBlockedLoop(void) +{ + return oswatch_blocked_loop; +} + +uint32_t ResetReason(void) +{ +# 102 "S:/Development/Tasmota/tasmota/support.ino" + return resetInfo.reason; +} + +String GetResetReason(void) +{ + if (oswatch_blocked_loop) { + char buff[32]; + strncpy_P(buff, PSTR(D_JSON_BLOCKED_LOOP), sizeof(buff)); + return String(buff); + } else { + return ESP.getResetReason(); + } +} + + + + + + +size_t strchrspn(const char *str1, int character) +{ + size_t ret = 0; + char *start = (char*)str1; + char *end = strchr(str1, character); + if (end) ret = end - start; + return ret; +} + + +char* subStr(char* dest, char* str, const char *delim, int index) +{ + char *act; + char *sub = nullptr; + char *ptr; + int i; + + + strncpy(dest, str, strlen(str)+1); + for (i = 1, act = dest; i <= index; i++, act = nullptr) { + sub = strtok_r(act, delim, &ptr); + if (sub == nullptr) break; + } + sub = Trim(sub); + return sub; +} + +float CharToFloat(const char *str) +{ + + char strbuf[24]; + + strlcpy(strbuf, str, sizeof(strbuf)); + char *pt = strbuf; + while ((*pt != '\0') && isblank(*pt)) { pt++; } + + signed char sign = 1; + if (*pt == '-') { sign = -1; } + if (*pt == '-' || *pt=='+') { pt++; } + + float left = 0; + if (*pt != '.') { + left = atoi(pt); + while (isdigit(*pt)) { pt++; } + } + + float right = 0; + if (*pt == '.') { + pt++; + right = atoi(pt); + while (isdigit(*pt)) { + pt++; + right /= 10.0f; + } + } + + float result = left + right; + if (sign < 0) { + return -result; + } + return result; +} + +int TextToInt(char *str) +{ + char *p; + uint8_t radix = 10; + if ('#' == str[0]) { + radix = 16; + str++; + } + return strtol(str, &p, radix); +} + +char* ulltoa(unsigned long long value, char *str, int radix) +{ + char digits[64]; + char *dst = str; + int i = 0; + + + + do { + int n = value % radix; + digits[i++] = (n < 10) ? (char)n+'0' : (char)n-10+'A'; + value /= radix; + } while (value != 0); + + while (i > 0) { *dst++ = digits[--i]; } + + *dst = 0; + return str; +} + + + +char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween) +{ + + + + static const char * hex = "0123456789ABCDEF"; + int between = (inbetween) ? 3 : 2; + const unsigned char * pin = in; + char * pout = out; + for (; pin < in+insz; pout += between, pin++) { + pout[0] = hex[(pgm_read_byte(pin)>>4) & 0xF]; + pout[1] = hex[ pgm_read_byte(pin) & 0xF]; + if (inbetween) { pout[2] = inbetween; } + if (pout + 3 - out > outsz) { break; } + } + pout[(inbetween && insz) ? -1 : 0] = 0; + return out; +} + +char* Uint64toHex(uint64_t value, char *str, uint16_t bits) +{ + ulltoa(value, str, 16); + + int fill = 8; + if ((bits > 3) && (bits < 65)) { + fill = bits / 4; + if (bits % 4) { fill++; } + } + int len = strlen(str); + fill -= len; + if (fill > 0) { + memmove(str + fill, str, len +1); + memset(str, '0', fill); + } + return str; +} + +char* dtostrfd(double number, unsigned char prec, char *s) +{ + if ((isnan(number)) || (isinf(number))) { + strcpy(s, "null"); + return s; + } else { + return dtostrf(number, 1, prec, s); + } +} + +char* Unescape(char* buffer, uint32_t* size) +{ + uint8_t* read = (uint8_t*)buffer; + uint8_t* write = (uint8_t*)buffer; + int32_t start_size = *size; + int32_t end_size = *size; + uint8_t che = 0; + + + + while (start_size > 0) { + uint8_t ch = *read++; + start_size--; + if (ch != '\\') { + *write++ = ch; + } else { + if (start_size > 0) { + uint8_t chi = *read++; + start_size--; + end_size--; + switch (chi) { + case '\\': che = '\\'; break; + case 'a': che = '\a'; break; + case 'b': che = '\b'; break; + case 'e': che = '\e'; break; + case 'f': che = '\f'; break; + case 'n': che = '\n'; break; + case 'r': che = '\r'; break; + case 's': che = ' '; break; + case 't': che = '\t'; break; + case 'v': che = '\v'; break; + case 'x': { + uint8_t* start = read; + che = (uint8_t)strtol((const char*)read, (char**)&read, 16); + start_size -= (uint16_t)(read - start); + end_size -= (uint16_t)(read - start); + break; + } + case '"': che = '\"'; break; + + default : { + che = chi; + *write++ = ch; + end_size++; + } + } + *write++ = che; + } + } + } + *size = end_size; + *write++ = 0; + + + return buffer; +} + +char* RemoveSpace(char* p) +{ + char* write = p; + char* read = p; + char ch = '.'; + + while (ch != '\0') { + ch = *read++; + if (!isspace(ch)) { + *write++ = ch; + } + } + + return p; +} + +char* ReplaceCommaWithDot(char* p) +{ + char* write = (char*)p; + char* read = (char*)p; + char ch = '.'; + + while (ch != '\0') { + ch = *read++; + if (ch == ',') { + ch = '.'; + } + *write++ = ch; + } + return p; +} + +char* LowerCase(char* dest, const char* source) +{ + char* write = dest; + const char* read = source; + char ch = '.'; + + while (ch != '\0') { + ch = *read++; + *write++ = tolower(ch); + } + return dest; +} + +char* UpperCase(char* dest, const char* source) +{ + char* write = dest; + const char* read = source; + char ch = '.'; + + while (ch != '\0') { + ch = *read++; + *write++ = toupper(ch); + } + return dest; +} + +char* UpperCase_P(char* dest, const char* source) +{ + char* write = dest; + const char* read = source; + char ch = '.'; + + while (ch != '\0') { + ch = pgm_read_byte(read++); + *write++ = toupper(ch); + } + return dest; +} + +char* Trim(char* p) +{ + while ((*p != '\0') && isblank(*p)) { p++; } + char* q = p + strlen(p) -1; + while ((q >= p) && isblank(*q)) { q--; } + q++; + *q = '\0'; + return p; +} + +char* RemoveAllSpaces(char* p) +{ + + char *cursor = p; + uint32_t offset = 0; + while (1) { + *cursor = *(cursor + offset); + if ((' ' == *cursor) || ('\t' == *cursor) || ('\n' == *cursor)) { + offset++; + } else { + if (0 == *cursor) { break; } + cursor++; + } + } + return p; +} + +char* NoAlNumToUnderscore(char* dest, const char* source) +{ + char* write = dest; + const char* read = source; + char ch = '.'; + + while (ch != '\0') { + ch = *read++; + *write++ = (isalnum(ch) || ('\0' == ch)) ? ch : '_'; + } + return dest; +} + +char IndexSeparator(void) +{ + + + + + + + if (Settings.flag3.use_underscore) { + return '_'; + } else { + return '-'; + } +} + +void SetShortcutDefault(void) +{ + if ('\0' != XdrvMailbox.data[0]) { + XdrvMailbox.data[0] = '0' + SC_DEFAULT; + XdrvMailbox.data[1] = '\0'; + } +} + +uint8_t Shortcut(void) +{ + uint8_t result = 10; + + if ('\0' == XdrvMailbox.data[1]) { + if (('"' == XdrvMailbox.data[0]) || ('0' == XdrvMailbox.data[0])) { + result = SC_CLEAR; + } else { + result = atoi(XdrvMailbox.data); + if (0 == result) { + result = 10; + } + } + } + return result; +} + +bool ValidIpAddress(const char* str) +{ + const char* p = str; + + while (*p && ((*p == '.') || ((*p >= '0') && (*p <= '9')))) { p++; } + return (*p == '\0'); +} + +bool ParseIp(uint32_t* addr, const char* str) +{ + uint8_t *part = (uint8_t*)addr; + uint8_t i; + + *addr = 0; + for (i = 0; i < 4; i++) { + part[i] = strtoul(str, nullptr, 10); + str = strchr(str, '.'); + if (str == nullptr || *str == '\0') { + break; + } + str++; + } + return (3 == i); +} + +uint32_t ParseParameters(uint32_t count, uint32_t *params) +{ + char *p; + uint32_t i = 0; + for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < count; str = strtok_r(nullptr, ", ", &p), i++) { + params[i] = strtoul(str, nullptr, 0); + } + return i; +} + + +bool NewerVersion(char* version_str) +{ + uint32_t version = 0; + uint32_t i = 0; + char *str_ptr; + + char version_dup[strlen(version_str) +1]; + strncpy(version_dup, version_str, sizeof(version_dup)); + + for (char *str = strtok_r(version_dup, ".", &str_ptr); str && i < sizeof(VERSION); str = strtok_r(nullptr, ".", &str_ptr), i++) { + int field = atoi(str); + + if ((field < 0) || (field > 255)) { + return false; + } + + version = (version << 8) + field; + + if ((2 == i) && isalpha(str[strlen(str)-1])) { + field = str[strlen(str)-1] & 0x1f; + version = (version << 8) + field; + i++; + } + } + + + if ((i < 2) || (i > sizeof(VERSION))) { + return false; + } + + + while (i < sizeof(VERSION)) { + version <<= 8; + i++; + } + + return (version > VERSION); +} + +char* GetPowerDevice(char* dest, uint32_t idx, size_t size, uint32_t option) +{ + strncpy_P(dest, S_RSLT_POWER, size); + if ((devices_present + option) > 1) { + char sidx[8]; + snprintf_P(sidx, sizeof(sidx), PSTR("%d"), idx); + strncat(dest, sidx, size - strlen(dest) -1); + } + return dest; +} + +char* GetPowerDevice(char* dest, uint32_t idx, size_t size) +{ + return GetPowerDevice(dest, idx, size, 0); +} + +void GetEspHardwareType(void) +{ + + uint32_t efuse1 = *(uint32_t*)(0x3FF00050); + uint32_t efuse2 = *(uint32_t*)(0x3FF00054); + + + + is_8285 = ( (efuse1 & (1 << 4)) || (efuse2 & (1 << 16)) ); + if (is_8285 && (ESP.getFlashChipRealSize() > 1048576)) { + is_8285 = false; + } +} + +String GetDeviceHardware(void) +{ + char buff[10]; + if (is_8285) { + strcpy_P(buff, PSTR("ESP8285")); + } else { + strcpy_P(buff, PSTR("ESP8266EX")); + } + return String(buff); +} + +float ConvertTemp(float c) +{ + float result = c; + + global_update = uptime; + global_temperature = c; + + if (!isnan(c) && Settings.flag.temperature_conversion) { + result = c * 1.8 + 32; + } + result = result + (0.1 * Settings.temp_comp); + return result; +} + +float ConvertTempToCelsius(float c) +{ + float result = c; + + if (!isnan(c) && Settings.flag.temperature_conversion) { + result = (c - 32) / 1.8; + } + result = result + (0.1 * Settings.temp_comp); + return result; +} + +char TempUnit(void) +{ + return (Settings.flag.temperature_conversion) ? 'F' : 'C'; +} + +float ConvertHumidity(float h) +{ + global_update = uptime; + global_humidity = h; + + return h; +} + +float ConvertPressure(float p) +{ + float result = p; + + global_update = uptime; + global_pressure = p; + + if (!isnan(p) && Settings.flag.pressure_conversion) { + result = p * 0.75006375541921; + } + return result; +} + +String PressureUnit(void) +{ + return (Settings.flag.pressure_conversion) ? String(D_UNIT_MILLIMETER_MERCURY) : String(D_UNIT_PRESSURE); +} + +void ResetGlobalValues(void) +{ + if ((uptime - global_update) > GLOBAL_VALUES_VALID) { + global_update = 0; + global_temperature = 9999; + global_humidity = 0; + global_pressure = 0; + } +} + +uint32_t SqrtInt(uint32_t num) +{ + if (num <= 1) { + return num; + } + + uint32_t x = num / 2; + uint32_t y; + do { + y = (x + num / x) / 2; + if (y >= x) { + return x; + } + x = y; + } while (true); +} + +uint32_t RoundSqrtInt(uint32_t num) +{ + uint32_t s = SqrtInt(4 * num); + if (s & 1) { + s++; + } + return s / 2; +} + +char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack) +{ + + + char* write = destination; + const char* read = haystack; + + index++; + while (index--) { + size_t size = destination_size -1; + write = destination; + char ch = '.'; + while ((ch != '\0') && (ch != '|')) { + ch = pgm_read_byte(read++); + if (size && (ch != '|')) { + *write++ = ch; + size--; + } + } + if (0 == ch) { + if (index) { + write = destination; + } + break; + } + } + *write = '\0'; + return destination; +} + +int GetCommandCode(char* destination, size_t destination_size, const char* needle, const char* haystack) +{ + + + int result = -1; + const char* read = haystack; + char* write = destination; + + while (true) { + result++; + size_t size = destination_size -1; + write = destination; + char ch = '.'; + while ((ch != '\0') && (ch != '|')) { + ch = pgm_read_byte(read++); + if (size && (ch != '|')) { + *write++ = ch; + size--; + } + } + *write = '\0'; + if (!strcasecmp(needle, destination)) { + break; + } + if (0 == ch) { + result = -1; + break; + } + } + return result; +} + +bool DecodeCommand(const char* haystack, void (* const MyCommand[])(void)) +{ + GetTextIndexed(XdrvMailbox.command, CMDSZ, 0, haystack); + int prefix_length = strlen(XdrvMailbox.command); + if (prefix_length) { + char prefix[prefix_length +1]; + snprintf_P(prefix, sizeof(prefix), XdrvMailbox.topic); + if (strcasecmp(prefix, XdrvMailbox.command)) { + return false; + } + } + int command_code = GetCommandCode(XdrvMailbox.command + prefix_length, CMDSZ, XdrvMailbox.topic + prefix_length, haystack); + if (command_code > 0) { + XdrvMailbox.command_code = command_code -1; + MyCommand[XdrvMailbox.command_code](); + return true; + } + return false; +} + +const char kOptions[] PROGMEM = "OFF|" D_OFF "|FALSE|" D_FALSE "|STOP|" D_STOP "|" D_CELSIUS "|" + "ON|" D_ON "|TRUE|" D_TRUE "|START|" D_START "|" D_FAHRENHEIT "|" D_USER "|" + "TOGGLE|" D_TOGGLE "|" D_ADMIN "|" + "BLINK|" D_BLINK "|" + "BLINKOFF|" D_BLINKOFF "|" + "ALL" ; + +const uint8_t sNumbers[] PROGMEM = { 0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1, + 2,2,2, + 3,3, + 4,4, + 255 }; + +int GetStateNumber(char *state_text) +{ + char command[CMDSZ]; + int state_number = GetCommandCode(command, sizeof(command), state_text, kOptions); + if (state_number >= 0) { + state_number = pgm_read_byte(sNumbers + state_number); + } + return state_number; +} + +String GetSerialConfig(void) +{ + + + + + + const char kParity[] = "NEOI"; + + char config[4]; + config[0] = '5' + (Settings.serial_config & 0x3); + config[1] = kParity[(Settings.serial_config >> 3) & 0x3]; + config[2] = '1' + ((Settings.serial_config >> 2) & 0x1); + config[3] = '\0'; + return String(config); +} + +void SetSerialBegin() +{ + uint32_t baudrate = Settings.baudrate * 300; + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_SERIAL "Set to %s %d bit/s"), GetSerialConfig().c_str(), baudrate); + Serial.flush(); + Serial.begin(baudrate, (SerialConfig)pgm_read_byte(kTasmotaSerialConfig + Settings.serial_config)); +} + +void SetSerialConfig(uint32_t serial_config) +{ + if (serial_config > TS_SERIAL_8O2) { + serial_config = TS_SERIAL_8N1; + } + if (serial_config != Settings.serial_config) { + Settings.serial_config = serial_config; + SetSerialBegin(); + } +} + +void SetSerialBaudrate(uint32_t baudrate) +{ + Settings.baudrate = baudrate / 300; + if (Serial.baudRate() != baudrate) { + SetSerialBegin(); + } +} + +void SetSerial(uint32_t baudrate, uint32_t serial_config) +{ + Settings.flag.mqtt_serial = 0; + Settings.serial_config = serial_config; + Settings.baudrate = baudrate / 300; + SetSeriallog(LOG_LEVEL_NONE); + SetSerialBegin(); +} + +void ClaimSerial(void) +{ + serial_local = true; + AddLog_P(LOG_LEVEL_INFO, PSTR("SNS: Hardware Serial")); + SetSeriallog(LOG_LEVEL_NONE); + Settings.baudrate = Serial.baudRate() / 300; +} + +void SerialSendRaw(char *codes) +{ + char *p; + char stemp[3]; + uint8_t code; + + int size = strlen(codes); + + while (size > 1) { + strlcpy(stemp, codes, sizeof(stemp)); + code = strtol(stemp, &p, 16); + Serial.write(code); + size -= 2; + codes += 2; + } +} + +uint32_t GetHash(const char *buffer, size_t size) +{ + uint32_t hash = 0; + for (uint32_t i = 0; i <= size; i++) { + hash += (uint8_t)*buffer++ * (i +1); + } + return hash; +} + +void ShowSource(uint32_t source) +{ + if ((source > 0) && (source < SRC_MAX)) { + char stemp1[20]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SRC: %s"), GetTextIndexed(stemp1, sizeof(stemp1), source, kCommandSource)); + } +} + +void WebHexCode(uint32_t i, const char* code) +{ + char scolor[10]; + + strlcpy(scolor, code, sizeof(scolor)); + char* p = scolor; + if ('#' == p[0]) { p++; } + + if (3 == strlen(p)) { + p[6] = p[3]; + p[5] = p[2]; + p[4] = p[2]; + p[3] = p[1]; + p[2] = p[1]; + p[1] = p[0]; + } + + uint32_t color = strtol(p, nullptr, 16); + + + + + + + uint32_t j = sizeof(Settings.web_color) / 3; +# 917 "S:/Development/Tasmota/tasmota/support.ino" + if (i >= j) { + + i += ((((uint8_t*)&Settings.web_color2 - (uint8_t*)&Settings.web_color) / 3) - j); + } + Settings.web_color[i][0] = (color >> 16) & 0xFF; + Settings.web_color[i][1] = (color >> 8) & 0xFF; + Settings.web_color[i][2] = color & 0xFF; +} + +uint32_t WebColor(uint32_t i) +{ + uint32_t j = sizeof(Settings.web_color) / 3; + + + + + if (i >= j) { + + i += ((((uint8_t*)&Settings.web_color2 - (uint8_t*)&Settings.web_color) / 3) - j); + } + uint32_t tcolor = (Settings.web_color[i][0] << 16) | (Settings.web_color[i][1] << 8) | Settings.web_color[i][2]; + + return tcolor; +} + + + + + +const uint16_t TIMESZ = 100; + +char* ResponseGetTime(uint32_t format, char* time_str) +{ + switch (format) { + case 1: + snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\",\"Epoch\":%u"), GetDateAndTime(DT_LOCAL).c_str(), UtcTime()); + break; + case 2: + snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":%u"), UtcTime()); + break; + default: + snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL).c_str()); + } + return time_str; +} + +int Response_P(const char* format, ...) +{ + + va_list args; + va_start(args, format); + int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), format, args); + va_end(args); + return len; +} + +int ResponseTime_P(const char* format, ...) +{ + + va_list args; + va_start(args, format); + + ResponseGetTime(Settings.flag2.time_format, mqtt_data); + + int mlen = strlen(mqtt_data); + int len = vsnprintf_P(mqtt_data + mlen, sizeof(mqtt_data) - mlen, format, args); + va_end(args); + return len + mlen; +} + +int ResponseAppend_P(const char* format, ...) +{ + + va_list args; + va_start(args, format); + int mlen = strlen(mqtt_data); + int len = vsnprintf_P(mqtt_data + mlen, sizeof(mqtt_data) - mlen, format, args); + va_end(args); + return len + mlen; +} + +int ResponseAppendTimeFormat(uint32_t format) +{ + char time_str[TIMESZ]; + return ResponseAppend_P(ResponseGetTime(format, time_str)); +} + +int ResponseAppendTime(void) +{ + return ResponseAppendTimeFormat(Settings.flag2.time_format); +} + +int ResponseJsonEnd(void) +{ + return ResponseAppend_P(PSTR("}")); +} + +int ResponseJsonEndEnd(void) +{ + return ResponseAppend_P(PSTR("}}")); +} + + + + + +void DigitalWrite(uint32_t gpio_pin, uint32_t state) +{ + if (pin[gpio_pin] < 99) { + digitalWrite(pin[gpio_pin], state &1); + } +} + +uint8_t ModuleNr(void) +{ + + + return (USER_MODULE == Settings.module) ? 0 : Settings.module +1; +} + +bool ValidTemplateModule(uint32_t index) +{ + for (uint32_t i = 0; i < sizeof(kModuleNiceList); i++) { + if (index == pgm_read_byte(kModuleNiceList + i)) { + return true; + } + } + return false; +} + +bool ValidModule(uint32_t index) +{ + if (index == USER_MODULE) { return true; } + return ValidTemplateModule(index); +} + +String AnyModuleName(uint32_t index) +{ + if (USER_MODULE == index) { + return String(Settings.user_template.name); + } else { + return FPSTR(kModules[index].name); + } +} + +String ModuleName(void) +{ + return AnyModuleName(Settings.module); +} + +void ModuleGpios(myio *gp) +{ + uint8_t *dest = (uint8_t *)gp; + memset(dest, GPIO_NONE, sizeof(myio)); + + uint8_t src[sizeof(mycfgio)]; + if (USER_MODULE == Settings.module) { + memcpy(&src, &Settings.user_template.gp, sizeof(mycfgio)); + } else { + memcpy_P(&src, &kModules[Settings.module].gp, sizeof(mycfgio)); + } + + + + + uint32_t j = 0; + for (uint32_t i = 0; i < sizeof(mycfgio); i++) { + if (6 == i) { j = 9; } + if (8 == i) { j = 12; } + dest[j] = src[i]; + j++; + } + + + +} + +gpio_flag ModuleFlag(void) +{ + gpio_flag flag; + + if (USER_MODULE == Settings.module) { + flag = Settings.user_template.flag; + } else { + memcpy_P(&flag, &kModules[Settings.module].flag, sizeof(gpio_flag)); + } + + return flag; +} + +void ModuleDefault(uint32_t module) +{ + if (USER_MODULE == module) { module = WEMOS; } + Settings.user_template_base = module; + memcpy_P(&Settings.user_template, &kModules[module], sizeof(mytmplt)); +} + +void SetModuleType(void) +{ + my_module_type = (USER_MODULE == Settings.module) ? Settings.user_template_base : Settings.module; +} + +bool FlashPin(uint32_t pin) +{ + return (((pin > 5) && (pin < 9)) || (11 == pin)); +} + +uint8_t ValidPin(uint32_t pin, uint32_t gpio) +{ + if (FlashPin(pin)) { + return GPIO_NONE; + } + + + if ((WEMOS == Settings.module) && !Settings.flag3.user_esp8285_enable) { + if ((pin == 9) || (pin == 10)) { + return GPIO_NONE; + } + } + + return gpio; +} + +bool ValidGPIO(uint32_t pin, uint32_t gpio) +{ + return (GPIO_USER == ValidPin(pin, gpio)); +} + +bool ValidAdc(void) +{ + gpio_flag flag = ModuleFlag(); + uint32_t template_adc0 = flag.data &15; + return (ADC0_USER == template_adc0); +} + +bool GetUsedInModule(uint32_t val, uint8_t *arr) +{ + int offset = 0; + + if (!val) { return false; } + + if ((val >= GPIO_KEY1) && (val < GPIO_KEY1 + MAX_KEYS)) { + offset = (GPIO_KEY1_NP - GPIO_KEY1); + } + if ((val >= GPIO_KEY1_NP) && (val < GPIO_KEY1_NP + MAX_KEYS)) { + offset = -(GPIO_KEY1_NP - GPIO_KEY1); + } + if ((val >= GPIO_KEY1_INV) && (val < GPIO_KEY1_INV + MAX_KEYS)) { + offset = -(GPIO_KEY1_INV - GPIO_KEY1); + } + if ((val >= GPIO_KEY1_INV_NP) && (val < GPIO_KEY1_INV_NP + MAX_KEYS)) { + offset = -(GPIO_KEY1_INV_NP - GPIO_KEY1); + } + + if ((val >= GPIO_SWT1) && (val < GPIO_SWT1 + MAX_SWITCHES)) { + offset = (GPIO_SWT1_NP - GPIO_SWT1); + } + if ((val >= GPIO_SWT1_NP) && (val < GPIO_SWT1_NP + MAX_SWITCHES)) { + offset = -(GPIO_SWT1_NP - GPIO_SWT1); + } + + if ((val >= GPIO_REL1) && (val < GPIO_REL1 + MAX_RELAYS)) { + offset = (GPIO_REL1_INV - GPIO_REL1); + } + if ((val >= GPIO_REL1_INV) && (val < GPIO_REL1_INV + MAX_RELAYS)) { + offset = -(GPIO_REL1_INV - GPIO_REL1); + } + + if ((val >= GPIO_LED1) && (val < GPIO_LED1 + MAX_LEDS)) { + offset = (GPIO_LED1_INV - GPIO_LED1); + } + if ((val >= GPIO_LED1_INV) && (val < GPIO_LED1_INV + MAX_LEDS)) { + offset = -(GPIO_LED1_INV - GPIO_LED1); + } + + if ((val >= GPIO_PWM1) && (val < GPIO_PWM1 + MAX_PWMS)) { + offset = (GPIO_PWM1_INV - GPIO_PWM1); + } + if ((val >= GPIO_PWM1_INV) && (val < GPIO_PWM1_INV + MAX_PWMS)) { + offset = -(GPIO_PWM1_INV - GPIO_PWM1); + } + + if ((val >= GPIO_CNTR1) && (val < GPIO_CNTR1 + MAX_COUNTERS)) { + offset = (GPIO_CNTR1_NP - GPIO_CNTR1); + } + if ((val >= GPIO_CNTR1_NP) && (val < GPIO_CNTR1_NP + MAX_COUNTERS)) { + offset = -(GPIO_CNTR1_NP - GPIO_CNTR1); + } + + for (uint32_t i = 0; i < MAX_GPIO_PIN; i++) { + if (arr[i] == val) { return true; } + if (arr[i] == val + offset) { return true; } + } + return false; +} + +bool JsonTemplate(const char* dataBuf) +{ + + + if (strlen(dataBuf) < 9) { return false; } + + StaticJsonBuffer<350> jb; + JsonObject& obj = jb.parseObject(dataBuf); + if (!obj.success()) { return false; } + + + const char* name = obj[D_JSON_NAME]; + if (name != nullptr) { + strlcpy(Settings.user_template.name, name, sizeof(Settings.user_template.name)); + } + if (obj[D_JSON_GPIO].success()) { + for (uint32_t i = 0; i < sizeof(mycfgio); i++) { + Settings.user_template.gp.io[i] = obj[D_JSON_GPIO][i] | 0; + } + } + if (obj[D_JSON_FLAG].success()) { + uint8_t flag = obj[D_JSON_FLAG] | 0; + memcpy(&Settings.user_template.flag, &flag, sizeof(gpio_flag)); + } + if (obj[D_JSON_BASE].success()) { + uint8_t base = obj[D_JSON_BASE]; + if ((0 == base) || !ValidTemplateModule(base -1)) { base = 18; } + Settings.user_template_base = base -1; + } + return true; +} + +void TemplateJson(void) +{ + Response_P(PSTR("{\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), Settings.user_template.name); + for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) { + ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Settings.user_template.gp.io[i]); + } + ResponseAppend_P(PSTR("],\"" D_JSON_FLAG "\":%d,\"" D_JSON_BASE "\":%d}"), Settings.user_template.flag, Settings.user_template_base +1); +} + + + + + +inline int32_t TimeDifference(uint32_t prev, uint32_t next) +{ + return ((int32_t) (next - prev)); +} + +int32_t TimePassedSince(uint32_t timestamp) +{ + + + return TimeDifference(timestamp, millis()); +} + +bool TimeReached(uint32_t timer) +{ + + const long passed = TimePassedSince(timer); + return (passed >= 0); +} + +void SetNextTimeInterval(unsigned long& timer, const unsigned long step) +{ + timer += step; + const long passed = TimePassedSince(timer); + if (passed < 0) { return; } + if (static_cast(passed) > step) { + + timer = millis() + step; + return; + } + + timer = millis() + (step - passed); +} + +int32_t TimePassedSinceUsec(uint32_t timestamp) +{ + return TimeDifference(timestamp, micros()); +} + +bool TimeReachedUsec(uint32_t timer) +{ + + const long passed = TimePassedSinceUsec(timer); + return (passed >= 0); +} + + + + + +#ifdef USE_I2C +const uint8_t I2C_RETRY_COUNTER = 3; + +uint32_t i2c_active[4] = { 0 }; +uint32_t i2c_buffer = 0; + +bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size) +{ + uint8_t retry = I2C_RETRY_COUNTER; + bool status = false; + + i2c_buffer = 0; + while (!status && retry) { + Wire.beginTransmission(addr); + Wire.write(reg); + if (0 == Wire.endTransmission(false)) { + Wire.requestFrom((int)addr, (int)size); + if (Wire.available() == size) { + for (uint32_t i = 0; i < size; i++) { + i2c_buffer = i2c_buffer << 8 | Wire.read(); + } + status = true; + } + } + retry--; + } + return status; +} + +bool I2cValidRead8(uint8_t *data, uint8_t addr, uint8_t reg) +{ + bool status = I2cValidRead(addr, reg, 1); + *data = (uint8_t)i2c_buffer; + return status; +} + +bool I2cValidRead16(uint16_t *data, uint8_t addr, uint8_t reg) +{ + bool status = I2cValidRead(addr, reg, 2); + *data = (uint16_t)i2c_buffer; + return status; +} + +bool I2cValidReadS16(int16_t *data, uint8_t addr, uint8_t reg) +{ + bool status = I2cValidRead(addr, reg, 2); + *data = (int16_t)i2c_buffer; + return status; +} + +bool I2cValidRead16LE(uint16_t *data, uint8_t addr, uint8_t reg) +{ + uint16_t ldata; + bool status = I2cValidRead16(&ldata, addr, reg); + *data = (ldata >> 8) | (ldata << 8); + return status; +} + +bool I2cValidReadS16_LE(int16_t *data, uint8_t addr, uint8_t reg) +{ + uint16_t ldata; + bool status = I2cValidRead16LE(&ldata, addr, reg); + *data = (int16_t)ldata; + return status; +} + +bool I2cValidRead24(int32_t *data, uint8_t addr, uint8_t reg) +{ + bool status = I2cValidRead(addr, reg, 3); + *data = i2c_buffer; + return status; +} + +uint8_t I2cRead8(uint8_t addr, uint8_t reg) +{ + I2cValidRead(addr, reg, 1); + return (uint8_t)i2c_buffer; +} + +uint16_t I2cRead16(uint8_t addr, uint8_t reg) +{ + I2cValidRead(addr, reg, 2); + return (uint16_t)i2c_buffer; +} + +int16_t I2cReadS16(uint8_t addr, uint8_t reg) +{ + I2cValidRead(addr, reg, 2); + return (int16_t)i2c_buffer; +} + +uint16_t I2cRead16LE(uint8_t addr, uint8_t reg) +{ + I2cValidRead(addr, reg, 2); + uint16_t temp = (uint16_t)i2c_buffer; + return (temp >> 8) | (temp << 8); +} + +int16_t I2cReadS16_LE(uint8_t addr, uint8_t reg) +{ + return (int16_t)I2cRead16LE(addr, reg); +} + +int32_t I2cRead24(uint8_t addr, uint8_t reg) +{ + I2cValidRead(addr, reg, 3); + return i2c_buffer; +} + +bool I2cWrite(uint8_t addr, uint8_t reg, uint32_t val, uint8_t size) +{ + uint8_t x = I2C_RETRY_COUNTER; + + do { + Wire.beginTransmission((uint8_t)addr); + Wire.write(reg); + uint8_t bytes = size; + while (bytes--) { + Wire.write((val >> (8 * bytes)) & 0xFF); + } + x--; + } while (Wire.endTransmission(true) != 0 && x != 0); + return (x); +} + +bool I2cWrite8(uint8_t addr, uint8_t reg, uint16_t val) +{ + return I2cWrite(addr, reg, val, 1); +} + +bool I2cWrite16(uint8_t addr, uint8_t reg, uint16_t val) +{ + return I2cWrite(addr, reg, val, 2); +} + +int8_t I2cReadBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len) +{ + Wire.beginTransmission((uint8_t)addr); + Wire.write((uint8_t)reg); + Wire.endTransmission(); + if (len != Wire.requestFrom((uint8_t)addr, (uint8_t)len)) { + return 1; + } + while (len--) { + *reg_data = (uint8_t)Wire.read(); + reg_data++; + } + return 0; +} + +int8_t I2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len) +{ + Wire.beginTransmission((uint8_t)addr); + Wire.write((uint8_t)reg); + while (len--) { + Wire.write(*reg_data); + reg_data++; + } + Wire.endTransmission(); + return 0; +} + +void I2cScan(char *devs, unsigned int devs_len) +{ + + + + + + + + uint8_t error = 0; + uint8_t address = 0; + uint8_t any = 0; + + snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"" D_JSON_I2CSCAN_DEVICES_FOUND_AT)); + for (address = 1; address <= 127; address++) { + Wire.beginTransmission(address); + error = Wire.endTransmission(); + if (0 == error) { + any = 1; + snprintf_P(devs, devs_len, PSTR("%s 0x%02x"), devs, address); + } + else if (error != 2) { + any = 2; + snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"Error %d at 0x%02x"), error, address); + break; + } + } + if (any) { + strncat(devs, "\"}", devs_len - strlen(devs) -1); + } + else { + snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"" D_JSON_I2CSCAN_NO_DEVICES_FOUND "\"}")); + } +} + +void I2cResetActive(uint32_t addr, uint32_t count = 1) +{ + addr &= 0x7F; + count &= 0x7F; + while (count-- && (addr < 128)) { + i2c_active[addr / 32] &= ~(1 << (addr % 32)); + addr++; + } + +} + +void I2cSetActive(uint32_t addr, uint32_t count = 1) +{ + addr &= 0x7F; + count &= 0x7F; + while (count-- && (addr < 128)) { + i2c_active[addr / 32] |= (1 << (addr % 32)); + addr++; + } + +} + +void I2cSetActiveFound(uint32_t addr, const char *types) +{ + I2cSetActive(addr); + AddLog_P2(LOG_LEVEL_INFO, S_LOG_I2C_FOUND_AT, types, addr); +} + +bool I2cActive(uint32_t addr) +{ + addr &= 0x7F; + if (i2c_active[addr / 32] & (1 << (addr % 32))) { + return true; + } + return false; +} + +bool I2cSetDevice(uint32_t addr) +{ + addr &= 0x7F; + if (I2cActive(addr)) { + return false; + } + Wire.beginTransmission((uint8_t)addr); + return (0 == Wire.endTransmission()); +} +#endif +# 1560 "S:/Development/Tasmota/tasmota/support.ino" +void SetSeriallog(uint32_t loglevel) +{ + Settings.seriallog_level = loglevel; + seriallog_level = loglevel; + seriallog_timer = 0; +} + +void SetSyslog(uint32_t loglevel) +{ + Settings.syslog_level = loglevel; + syslog_level = loglevel; + syslog_timer = 0; +} + +#ifdef USE_WEBSERVER +void GetLog(uint32_t idx, char** entry_pp, size_t* len_p) +{ + char* entry_p = nullptr; + size_t len = 0; + + if (idx) { + char* it = web_log; + do { + uint32_t cur_idx = *it; + it++; + size_t tmp = strchrspn(it, '\1'); + tmp++; + if (cur_idx == idx) { + len = tmp; + entry_p = it; + break; + } + it += tmp; + } while (it < web_log + WEB_LOG_SIZE && *it != '\0'); + } + *entry_pp = entry_p; + *len_p = len; +} +#endif + +void Syslog(void) +{ + + + uint32_t current_hash = GetHash(SettingsText(SET_SYSLOG_HOST), strlen(SettingsText(SET_SYSLOG_HOST))); + if (syslog_host_hash != current_hash) { + syslog_host_hash = current_hash; + WiFi.hostByName(SettingsText(SET_SYSLOG_HOST), syslog_host_addr); + } + if (PortUdp.beginPacket(syslog_host_addr, Settings.syslog_port)) { + char syslog_preamble[64]; + snprintf_P(syslog_preamble, sizeof(syslog_preamble), PSTR("%s ESP-"), my_hostname); + memmove(log_data + strlen(syslog_preamble), log_data, sizeof(log_data) - strlen(syslog_preamble)); + log_data[sizeof(log_data) -1] = '\0'; + memcpy(log_data, syslog_preamble, strlen(syslog_preamble)); + PortUdp.write(log_data, strlen(log_data)); + PortUdp.endPacket(); + delay(1); + } else { + syslog_level = 0; + syslog_timer = SYSLOG_TIMER; + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SYSLOG_HOST_NOT_FOUND ". " D_RETRY_IN " %d " D_UNIT_SECOND), SYSLOG_TIMER); + } +} + +void AddLog(uint32_t loglevel) +{ + char mxtime[10]; + + snprintf_P(mxtime, sizeof(mxtime), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d "), RtcTime.hour, RtcTime.minute, RtcTime.second); + + if (loglevel <= seriallog_level) { + Serial.printf("%s%s\r\n", mxtime, log_data); + } +#ifdef USE_WEBSERVER + if (Settings.webserver && (loglevel <= Settings.weblog_level)) { + + + web_log_index &= 0xFF; + if (!web_log_index) web_log_index++; + while (web_log_index == web_log[0] || + strlen(web_log) + strlen(log_data) + 13 > WEB_LOG_SIZE) + { + char* it = web_log; + it++; + it += strchrspn(it, '\1'); + it++; + memmove(web_log, it, WEB_LOG_SIZE -(it-web_log)); + } + snprintf_P(web_log, sizeof(web_log), PSTR("%s%c%s%s\1"), web_log, web_log_index++, mxtime, log_data); + web_log_index &= 0xFF; + if (!web_log_index) web_log_index++; + } +#endif + if (Settings.flag.mqtt_enabled && + !global_state.mqtt_down && + (loglevel <= Settings.mqttlog_level)) { MqttPublishLogging(mxtime); } + + if (!global_state.wifi_down && + (loglevel <= syslog_level)) { Syslog(); } +} + +void AddLog_P(uint32_t loglevel, const char *formatP) +{ + snprintf_P(log_data, sizeof(log_data), formatP); + AddLog(loglevel); +} + +void AddLog_P(uint32_t loglevel, const char *formatP, const char *formatP2) +{ + char message[sizeof(log_data)]; + + snprintf_P(log_data, sizeof(log_data), formatP); + snprintf_P(message, sizeof(message), formatP2); + strncat(log_data, message, sizeof(log_data) - strlen(log_data) -1); + AddLog(loglevel); +} + +void PrepLog_P2(uint32_t loglevel, PGM_P formatP, ...) +{ + va_list arg; + va_start(arg, formatP); + vsnprintf_P(log_data, sizeof(log_data), formatP, arg); + va_end(arg); + + prepped_loglevel = loglevel; +} + +void AddLog_P2(uint32_t loglevel, PGM_P formatP, ...) +{ + va_list arg; + va_start(arg, formatP); + vsnprintf_P(log_data, sizeof(log_data), formatP, arg); + va_end(arg); + + AddLog(loglevel); +} + +void AddLog_Debug(PGM_P formatP, ...) +{ + va_list arg; + va_start(arg, formatP); + vsnprintf_P(log_data, sizeof(log_data), formatP, arg); + va_end(arg); + + AddLog(LOG_LEVEL_DEBUG); +} + +void AddLogBuffer(uint32_t loglevel, uint8_t *buffer, uint32_t count) +{ +# 1722 "S:/Development/Tasmota/tasmota/support.ino" + char hex_char[(count * 3) + 2]; + AddLog_P2(loglevel, PSTR("DMP: %s"), ToHex_P(buffer, count, hex_char, sizeof(hex_char), ' ')); +} + +void AddLogSerial(uint32_t loglevel) +{ + AddLogBuffer(loglevel, (uint8_t*)serial_in_buffer, serial_in_byte_counter); +} + +void AddLogMissed(char *sensor, uint32_t misses) +{ + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SNS: %s missed %d"), sensor, SENSOR_MAX_MISS - misses); +} +# 1 "S:/Development/Tasmota/tasmota/support_button.ino" +# 20 "S:/Development/Tasmota/tasmota/support_button.ino" +#define BUTTON_V1 +#ifdef BUTTON_V1 + + + + +#define MAX_BUTTON_COMMANDS 5 +const char kCommands[] PROGMEM = + D_CMND_WIFICONFIG " 2|" D_CMND_WIFICONFIG " 2|" D_CMND_WIFICONFIG " 2|" D_CMND_RESTART " 1|" D_CMND_UPGRADE " 1"; + +struct BUTTON { + unsigned long debounce = 0; + uint16_t hold_timer[MAX_KEYS] = { 0 }; + uint16_t dual_code = 0; + + uint8_t last_state[MAX_KEYS] = { NOT_PRESSED, NOT_PRESSED, NOT_PRESSED, NOT_PRESSED }; + uint8_t window_timer[MAX_KEYS] = { 0 }; + uint8_t press_counter[MAX_KEYS] = { 0 }; + + uint8_t dual_receive_count = 0; + uint8_t no_pullup_mask = 0; + uint8_t inverted_mask = 0; + uint8_t present = 0; + uint8_t adc = 99; +} Button; + + + +void ButtonPullupFlag(uint8 button_bit) +{ + bitSet(Button.no_pullup_mask, button_bit); +} + +void ButtonInvertFlag(uint8 button_bit) +{ + bitSet(Button.inverted_mask, button_bit); +} + +void ButtonInit(void) +{ + Button.present = 0; + for (uint32_t i = 0; i < MAX_KEYS; i++) { + if (pin[GPIO_KEY1 +i] < 99) { + Button.present++; + pinMode(pin[GPIO_KEY1 +i], bitRead(Button.no_pullup_mask, i) ? INPUT : ((16 == pin[GPIO_KEY1 +i]) ? INPUT_PULLDOWN_16 : INPUT_PULLUP)); + } +#ifndef USE_ADC_VCC + else if ((99 == Button.adc) && ((ADC0_BUTTON == my_adc0) || (ADC0_BUTTON_INV == my_adc0))) { + Button.present++; + Button.adc = i; + } +#endif + } +} + +uint8_t ButtonSerial(uint8_t serial_in_byte) +{ + if (Button.dual_receive_count) { + Button.dual_receive_count--; + if (Button.dual_receive_count) { + Button.dual_code = (Button.dual_code << 8) | serial_in_byte; + serial_in_byte = 0; + } else { + if (serial_in_byte != 0xA1) { + Button.dual_code = 0; + } + } + } + if (0xA0 == serial_in_byte) { + serial_in_byte = 0; + Button.dual_code = 0; + Button.dual_receive_count = 3; + } + + return serial_in_byte; +} +# 108 "S:/Development/Tasmota/tasmota/support_button.ino" +void ButtonHandler(void) +{ + if (uptime < 4) { return; } + + uint8_t hold_time_extent = IMMINENT_RESET_FACTOR; + uint16_t loops_per_second = 1000 / Settings.button_debounce; + char scmnd[20]; + + + + for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) { + uint8_t button = NOT_PRESSED; + uint8_t button_present = 0; + + if (!button_index && ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type))) { + button_present = 1; + if (Button.dual_code) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON " " D_CODE " %04X"), Button.dual_code); + button = PRESSED; + if (0xF500 == Button.dual_code) { + Button.hold_timer[button_index] = (loops_per_second * Settings.param[P_HOLD_TIME] / 10) -1; + hold_time_extent = 1; + } + Button.dual_code = 0; + } + } + else if (pin[GPIO_KEY1 +button_index] < 99) { + button_present = 1; + button = (digitalRead(pin[GPIO_KEY1 +button_index]) != bitRead(Button.inverted_mask, button_index)); + } +#ifndef USE_ADC_VCC + if (Button.adc == button_index) { + button_present = 1; + if (ADC0_BUTTON_INV == my_adc0) { + button = (AdcRead(1) < 128); + } + else if (ADC0_BUTTON == my_adc0) { + button = (AdcRead(1) > 128); + } + } +#endif + + if (button_present) { + XdrvMailbox.index = button_index; + XdrvMailbox.payload = button; + if (XdrvCall(FUNC_BUTTON_PRESSED)) { + + } + else if (SONOFF_4CHPRO == my_module_type) { + if (Button.hold_timer[button_index]) { Button.hold_timer[button_index]--; } + + bool button_pressed = false; + if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_LEVEL_10), button_index +1); + Button.hold_timer[button_index] = loops_per_second; + button_pressed = true; + } + if ((NOT_PRESSED == button) && (PRESSED == Button.last_state[button_index])) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_LEVEL_01), button_index +1); + if (!Button.hold_timer[button_index]) { button_pressed = true; } + } + if (button_pressed) { + if (!SendKey(KEY_BUTTON, button_index +1, POWER_TOGGLE)) { + ExecuteCommandPower(button_index +1, POWER_TOGGLE, SRC_BUTTON); + } + } + } + else { + if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) { + if (Settings.flag.button_single) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_IMMEDIATE), button_index +1); + if (!SendKey(KEY_BUTTON, button_index +1, POWER_TOGGLE)) { + ExecuteCommandPower(button_index +1, POWER_TOGGLE, SRC_BUTTON); + } + } else { + Button.press_counter[button_index] = (Button.window_timer[button_index]) ? Button.press_counter[button_index] +1 : 1; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_MULTI_PRESS " %d"), button_index +1, Button.press_counter[button_index]); + Button.window_timer[button_index] = loops_per_second / 2; + } + blinks = 201; + } + + if (NOT_PRESSED == button) { + Button.hold_timer[button_index] = 0; + } else { + Button.hold_timer[button_index]++; + if (Settings.flag.button_single) { + if (Button.hold_timer[button_index] == loops_per_second * hold_time_extent * Settings.param[P_HOLD_TIME] / 10) { + + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_SETOPTION "13 0")); + ExecuteCommand(scmnd, SRC_BUTTON); + } + } else { + if (Settings.flag.button_restrict) { + if (Settings.param[P_HOLD_IGNORE] > 0) { + if (Button.hold_timer[button_index] > loops_per_second * Settings.param[P_HOLD_IGNORE] / 10) { + Button.hold_timer[button_index] = 0; + Button.press_counter[button_index] = 0; + DEBUG_CORE_LOG(PSTR("BTN: " D_BUTTON "%d cancel by " D_CMND_SETOPTION "40 %d"), button_index +1, Settings.param[P_HOLD_IGNORE]); + } + } + if (Button.hold_timer[button_index] == loops_per_second * Settings.param[P_HOLD_TIME] / 10) { + Button.press_counter[button_index] = 0; + SendKey(KEY_BUTTON, button_index +1, POWER_HOLD); + } + } else { + if (Button.hold_timer[button_index] == loops_per_second * hold_time_extent * Settings.param[P_HOLD_TIME] / 10) { + Button.press_counter[button_index] = 0; + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); + ExecuteCommand(scmnd, SRC_BUTTON); + } + } + } + } + + if (!Settings.flag.button_single) { + if (Button.window_timer[button_index]) { + Button.window_timer[button_index]--; + } else { + if (!restart_flag && !Button.hold_timer[button_index] && (Button.press_counter[button_index] > 0) && (Button.press_counter[button_index] < MAX_BUTTON_COMMANDS +3)) { + bool single_press = false; + if (Button.press_counter[button_index] < 3) { + if ((SONOFF_DUAL_R2 == my_module_type) || (SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { + single_press = true; + } else { + single_press = (Settings.flag.button_swap +1 == Button.press_counter[button_index]); + if ((1 == Button.present) && (2 == devices_present)) { + if (Settings.flag.button_swap) { + Button.press_counter[button_index] = (single_press) ? 1 : 2; + } + } else { + Button.press_counter[button_index] = 1; + } + } + } +#if defined(USE_LIGHT) && defined(ROTARY_V1) + if (!((0 == button_index) && RotaryButtonPressed())) { +#endif + if (single_press && SendKey(KEY_BUTTON, button_index + Button.press_counter[button_index], POWER_TOGGLE)) { + + } else { + if (Button.press_counter[button_index] < 3) { + if (WifiState() > WIFI_RESTART) { + restart_flag = 1; + } else { + ExecuteCommandPower(button_index + Button.press_counter[button_index], POWER_TOGGLE, SRC_BUTTON); + } + } else { + if (!Settings.flag.button_restrict) { + GetTextIndexed(scmnd, sizeof(scmnd), Button.press_counter[button_index] -3, kCommands); + ExecuteCommand(scmnd, SRC_BUTTON); + } + } + } +#if defined(USE_LIGHT) && defined(ROTARY_V1) + } +#endif + Button.press_counter[button_index] = 0; + } + } + } + } + } + Button.last_state[button_index] = button; + } +} + +void ButtonLoop(void) +{ + if (Button.present) { + if (TimeReached(Button.debounce)) { + SetNextTimeInterval(Button.debounce, Settings.button_debounce); + ButtonHandler(); + } + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/support_command.ino" +# 20 "S:/Development/Tasmota/tasmota/support_command.ino" +const char kTasmotaCommands[] PROGMEM = "|" + D_CMND_BACKLOG "|" D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_STATUS "|" D_CMND_STATE "|" D_CMND_SLEEP "|" D_CMND_UPGRADE "|" D_CMND_UPLOAD "|" D_CMND_OTAURL "|" + D_CMND_SERIALLOG "|" D_CMND_RESTART "|" D_CMND_POWERONSTATE "|" D_CMND_PULSETIME "|" D_CMND_BLINKTIME "|" D_CMND_BLINKCOUNT "|" D_CMND_SAVEDATA "|" + D_CMND_SETOPTION "|" D_CMND_TEMPERATURE_RESOLUTION "|" D_CMND_HUMIDITY_RESOLUTION "|" D_CMND_PRESSURE_RESOLUTION "|" D_CMND_POWER_RESOLUTION "|" + D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_FREQUENCY_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_WEIGHT_RESOLUTION "|" + D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|" + D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|" + D_CMND_SERIALDELIMITER "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|" + D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|" + D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|" +#ifdef USE_I2C + D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|" +#endif +#ifdef USE_DEVICE_GROUPS + D_CMND_DEVGROUP_SHARE "|" +#endif + D_CMND_SENSOR "|" D_CMND_DRIVER; + +void (* const TasmotaCommand[])(void) PROGMEM = { + &CmndBacklog, &CmndDelay, &CmndPower, &CmndStatus, &CmndState, &CmndSleep, &CmndUpgrade, &CmndUpgrade, &CmndOtaUrl, + &CmndSeriallog, &CmndRestart, &CmndPowerOnState, &CmndPulsetime, &CmndBlinktime, &CmndBlinkcount, &CmndSavedata, + &CmndSetoption, &CmndTemperatureResolution, &CmndHumidityResolution, &CmndPressureResolution, &CmndPowerResolution, + &CmndVoltageResolution, &CmndFrequencyResolution, &CmndCurrentResolution, &CmndEnergyResolution, &CmndWeightResolution, + &CmndModule, &CmndModules, &CmndGpio, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange, + &CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig, + &CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig, + &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd, + &CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower, &CmndTempOffset, +#ifdef USE_I2C + &CmndI2cScan, CmndI2cDriver, +#endif +#ifdef USE_DEVICE_GROUPS + &CmndDevGroupShare, +#endif + &CmndSensor, &CmndDriver }; + +const char kWifiConfig[] PROGMEM = + D_WCFG_0_RESTART "||" D_WCFG_2_WIFIMANAGER "||" D_WCFG_4_RETRY "|" D_WCFG_5_WAIT "|" D_WCFG_6_SERIAL "|" D_WCFG_7_WIFIMANAGER_RESET_ONLY; + + + +void ResponseCmndNumber(int value) +{ + Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, value); +} + +void ResponseCmndFloat(float value, uint32_t decimals) +{ + char stemp1[TOPSZ]; + dtostrfd(value, decimals, stemp1); + Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, stemp1); +} + +void ResponseCmndIdxNumber(int value) +{ + Response_P(S_JSON_COMMAND_INDEX_NVALUE, XdrvMailbox.command, XdrvMailbox.index, value); +} + +void ResponseCmndChar(const char* value) +{ + Response_P(S_JSON_COMMAND_SVALUE, XdrvMailbox.command, value); +} + +void ResponseCmndStateText(uint32_t value) +{ + ResponseCmndChar(GetStateText(value)); +} + +void ResponseCmndDone(void) +{ + ResponseCmndChar(D_JSON_DONE); +} + +void ResponseCmndIdxChar(const char* value) +{ + Response_P(S_JSON_COMMAND_INDEX_SVALUE, XdrvMailbox.command, XdrvMailbox.index, value); +} + +void ResponseCmndAll(uint32_t text_index, uint32_t count) +{ + mqtt_data[0] = '\0'; + for (uint32_t i = 0; i < count; i++) { + ResponseAppend_P(PSTR("%c\"%s%d\":\"%s\""), (i) ? ',' : '{', XdrvMailbox.command, i +1, SettingsText(text_index +i)); + } + ResponseJsonEnd(); +} + + + +void ExecuteCommand(const char *cmnd, uint32_t source) +{ + + + +#ifdef USE_DEBUG_DRIVER + ShowFreeMem(PSTR("ExecuteCommand")); +#endif + ShowSource(source); + + const char *pos = cmnd; + while (*pos && isspace(*pos)) { + pos++; + } + + const char *start = pos; + + while (*pos && (isalpha(*pos) || isdigit(*pos) || '_' == *pos || '/' == *pos)) { + if ('/' == *pos) { + start = pos + 1; + } + pos++; + } + if ('\0' == *start || pos <= start) { + return; + } + + uint32_t size = pos - start; + char stopic[size + 2]; + stopic[0] = '/'; + memcpy(stopic+1, start, size); + stopic[size+1] = '\0'; + + char svalue[strlen(pos) +1]; + strlcpy(svalue, pos, sizeof(svalue)); + CommandHandler(stopic, svalue, strlen(svalue)); +} +# 154 "S:/Development/Tasmota/tasmota/support_command.ino" +void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len) +{ +#ifdef USE_DEBUG_DRIVER + ShowFreeMem(PSTR("CommandHandler")); +#endif + + while (*dataBuf && isspace(*dataBuf)) { + dataBuf++; + data_len--; + } + + bool grpflg = (strstr(topicBuf, SettingsText(SET_MQTT_GRP_TOPIC)) != nullptr); + + char stemp1[TOPSZ]; + GetFallbackTopic_P(stemp1, ""); + fallback_topic_flag = (!strncmp(topicBuf, stemp1, strlen(stemp1))); + + char *type = strrchr(topicBuf, '/'); + + uint32_t index = 1; + bool user_index = false; + if (type != nullptr) { + type++; + uint32_t i; + for (i = 0; i < strlen(type); i++) { + type[i] = toupper(type[i]); + } + while (isdigit(type[i-1])) { + i--; + } + if (i < strlen(type)) { + index = atoi(type +i); + user_index = true; + } + type[i] = '\0'; + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CMD: " D_GROUP " %d, " D_INDEX " %d, " D_COMMAND " \"%s\", " D_DATA " \"%s\""), grpflg, index, type, dataBuf); + + if (type != nullptr) { + Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_ERROR "\"}")); + + if (Settings.ledstate &0x02) { blinks++; } + + if (!strcmp(dataBuf,"?")) { data_len = 0; } + + char *p; + int32_t payload = strtol(dataBuf, &p, 0); + if (p == dataBuf) { payload = -99; } + int temp_payload = GetStateNumber(dataBuf); + if (temp_payload > -1) { payload = temp_payload; } + + DEBUG_CORE_LOG(PSTR("CMD: Payload %d"), payload); + + + backlog_delay = millis() + Settings.param[P_BACKLOG_DELAY]; + + char command[CMDSZ] = { 0 }; + XdrvMailbox.command = command; + XdrvMailbox.index = index; + XdrvMailbox.data_len = data_len; + XdrvMailbox.payload = payload; + XdrvMailbox.grpflg = grpflg; + XdrvMailbox.usridx = user_index; + XdrvMailbox.topic = type; + XdrvMailbox.data = dataBuf; + +#ifdef USE_SCRIPT_SUB_COMMAND + + if (!Script_SubCmd()) { + if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) { + if (!XdrvCall(FUNC_COMMAND)) { + if (!XsnsCall(FUNC_COMMAND)) { + type = nullptr; + } + } + } + } +#else + if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) { + if (!XdrvCall(FUNC_COMMAND)) { + if (!XsnsCall(FUNC_COMMAND)) { + type = nullptr; + } + } + } +#endif + + } + + if (type == nullptr) { + blinks = 201; + snprintf_P(stemp1, sizeof(stemp1), PSTR(D_JSON_COMMAND)); + Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_UNKNOWN "\"}")); + type = (char*)stemp1; + } + + if (mqtt_data[0] != '\0') { + MqttPublishPrefixTopic_P(RESULT_OR_STAT, type); + XdrvRulesProcess(); + } + fallback_topic_flag = false; +} + + + +void CmndBacklog(void) +{ + if (XdrvMailbox.data_len) { + +#ifdef SUPPORT_IF_STATEMENT + char *blcommand = strtok(XdrvMailbox.data, ";"); + while ((blcommand != nullptr) && (backlog.size() < MAX_BACKLOG)) +#else + uint32_t bl_pointer = (!backlog_pointer) ? MAX_BACKLOG -1 : backlog_pointer; + bl_pointer--; + char *blcommand = strtok(XdrvMailbox.data, ";"); + while ((blcommand != nullptr) && (backlog_index != bl_pointer)) +#endif + { + while(true) { + blcommand = Trim(blcommand); + if (!strncasecmp_P(blcommand, PSTR(D_CMND_BACKLOG), strlen(D_CMND_BACKLOG))) { + blcommand += strlen(D_CMND_BACKLOG); + } else { + break; + } + } + if (*blcommand != '\0') { +#ifdef SUPPORT_IF_STATEMENT + if (backlog.size() < MAX_BACKLOG) { + backlog.add(blcommand); + } +#else + backlog[backlog_index] = String(blcommand); + backlog_index++; + if (backlog_index >= MAX_BACKLOG) backlog_index = 0; +#endif + } + blcommand = strtok(nullptr, ";"); + } + + mqtt_data[0] = '\0'; + } else { + bool blflag = BACKLOG_EMPTY; +#ifdef SUPPORT_IF_STATEMENT + backlog.clear(); +#else + backlog_pointer = backlog_index; +#endif + ResponseCmndChar(blflag ? D_JSON_EMPTY : D_JSON_ABORTED); + } +} + +void CmndDelay(void) +{ + if ((XdrvMailbox.payload >= (MIN_BACKLOG_DELAY / 100)) && (XdrvMailbox.payload <= 3600)) { + backlog_delay = millis() + (100 * XdrvMailbox.payload); + } + uint32_t bl_delay = 0; + long bl_delta = TimePassedSince(backlog_delay); + if (bl_delta < 0) { bl_delay = (bl_delta *-1) / 100; } + ResponseCmndNumber(bl_delay); +} + +void CmndPower(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= devices_present)) { + if ((XdrvMailbox.payload < POWER_OFF) || (XdrvMailbox.payload > POWER_BLINK_STOP)) { + XdrvMailbox.payload = POWER_SHOW_STATE; + } + + ExecuteCommandPower(XdrvMailbox.index, XdrvMailbox.payload, SRC_IGNORE); + mqtt_data[0] = '\0'; + } + else if (0 == XdrvMailbox.index) { + if ((XdrvMailbox.payload < POWER_OFF) || (XdrvMailbox.payload > POWER_TOGGLE)) { + XdrvMailbox.payload = POWER_SHOW_STATE; + } + SetAllPower(XdrvMailbox.payload, SRC_IGNORE); + mqtt_data[0] = '\0'; + } +} + +void CmndStatus(void) +{ + uint32_t payload = ((XdrvMailbox.payload < 0) || (XdrvMailbox.payload > MAX_STATUS)) ? 99 : XdrvMailbox.payload; + + uint32_t option = STAT; + char stemp[200]; + char stemp2[TOPSZ]; + + + + + + if ((!Settings.flag.mqtt_enabled) && (6 == payload)) { payload = 99; } + if (!energy_flg && (9 == payload)) { payload = 99; } + if (!CrashFlag() && (12 == payload)) { payload = 99; } + + if ((0 == payload) || (99 == payload)) { + uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present; +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { maxfn = 1; } +#endif + stemp[0] = '\0'; + for (uint32_t i = 0; i < maxfn; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("%s%s\"%s\"" ), stemp, (i > 0 ? "," : ""), SettingsText(SET_FRIENDLYNAME1 +i)); + } + stemp2[0] = '\0'; + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { + snprintf_P(stemp2, sizeof(stemp2), PSTR("%s%s%d" ), stemp2, (i > 0 ? "," : ""), Settings.switchmode[i]); + } + Response_P(PSTR("{\"" D_CMND_STATUS "\":{\"" D_CMND_MODULE "\":%d,\"" D_CMND_FRIENDLYNAME "\":[%s],\"" D_CMND_TOPIC "\":\"%s\",\"" + D_CMND_BUTTONTOPIC "\":\"%s\",\"" D_CMND_POWER "\":%d,\"" D_CMND_POWERONSTATE "\":%d,\"" D_CMND_LEDSTATE "\":%d,\"" + D_CMND_LEDMASK "\":\"%04X\",\"" D_CMND_SAVEDATA "\":%d,\"" D_JSON_SAVESTATE "\":%d,\"" D_CMND_SWITCHTOPIC "\":\"%s\",\"" + D_CMND_SWITCHMODE "\":[%s],\"" D_CMND_BUTTONRETAIN "\":%d,\"" D_CMND_SWITCHRETAIN "\":%d,\"" D_CMND_SENSORRETAIN "\":%d,\"" D_CMND_POWERRETAIN "\":%d}}"), + ModuleNr(), stemp, mqtt_topic, + SettingsText(SET_MQTT_BUTTON_TOPIC), power, Settings.poweronstate, Settings.ledstate, + Settings.ledmask, Settings.save_data, + Settings.flag.save_state, + SettingsText(SET_MQTT_SWITCH_TOPIC), + stemp2, + Settings.flag.mqtt_button_retain, + Settings.flag.mqtt_switch_retain, + Settings.flag.mqtt_sensor_retain, + Settings.flag.mqtt_power_retain); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS)); + } + + if ((0 == payload) || (1 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS1_PARAMETER "\":{\"" D_JSON_BAUDRATE "\":%d,\"" D_CMND_SERIALCONFIG "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\",\"" D_CMND_OTAURL "\":\"%s\",\"" + D_JSON_RESTARTREASON "\":\"%s\",\"" D_JSON_UPTIME "\":\"%s\",\"" D_JSON_STARTUPUTC "\":\"%s\",\"" D_CMND_SLEEP "\":%d,\"" + D_JSON_CONFIG_HOLDER "\":%d,\"" D_JSON_BOOTCOUNT "\":%d,\"BCResetTime\":\"%s\",\"" D_JSON_SAVECOUNT "\":%d,\"" D_JSON_SAVEADDRESS "\":\"%X\"}}"), + Settings.baudrate * 300, GetSerialConfig().c_str(), SettingsText(SET_MQTT_GRP_TOPIC), SettingsText(SET_OTAURL), + GetResetReason().c_str(), GetUptime().c_str(), GetDateAndTime(DT_RESTART).c_str(), Settings.sleep, + Settings.cfg_holder, Settings.bootcount, GetDateAndTime(DT_BOOTCOUNT).c_str(), Settings.save_flag, GetSettingsAddress()); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "1")); + } + + if ((0 == payload) || (2 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS2_FIRMWARE "\":{\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_BUILDDATETIME "\":\"%s\",\"" + D_JSON_BOOTVERSION "\":%d,\"" D_JSON_COREVERSION "\":\"" ARDUINO_ESP8266_RELEASE "\",\"" D_JSON_SDKVERSION "\":\"%s\"," + "\"Hardware\":\"%s\"" + "%s}}"), + my_version, my_image, GetBuildDateAndTime().c_str(), + ESP.getBootVersion(), ESP.getSdkVersion(), + GetDeviceHardware().c_str(), + GetStatistics().c_str()); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "2")); + } + + if ((0 == payload) || (3 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS3_LOGGING "\":{\"" D_CMND_SERIALLOG "\":%d,\"" D_CMND_WEBLOG "\":%d,\"" D_CMND_MQTTLOG "\":%d,\"" D_CMND_SYSLOG "\":%d,\"" + D_CMND_LOGHOST "\":\"%s\",\"" D_CMND_LOGPORT "\":%d,\"" D_CMND_SSID "\":[\"%s\",\"%s\"],\"" D_CMND_TELEPERIOD "\":%d,\"" + D_JSON_RESOLUTION "\":\"%08X\",\"" D_CMND_SETOPTION "\":[\"%08X\",\"%s\",\"%08X\",\"%08X\"]}}"), + Settings.seriallog_level, Settings.weblog_level, Settings.mqttlog_level, Settings.syslog_level, + SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), Settings.tele_period, + Settings.flag2.data, Settings.flag.data, ToHex_P((unsigned char*)Settings.param, PARAM8_SIZE, stemp2, sizeof(stemp2)), + Settings.flag3.data, Settings.flag4.data); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "3")); + } + + if ((0 == payload) || (4 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS4_MEMORY "\":{\"" D_JSON_PROGRAMSIZE "\":%d,\"" D_JSON_FREEMEMORY "\":%d,\"" D_JSON_HEAPSIZE "\":%d,\"" + D_JSON_PROGRAMFLASHSIZE "\":%d,\"" D_JSON_FLASHSIZE "\":%d,\"" D_JSON_FLASHCHIPID "\":\"%06X\",\"" D_JSON_FLASHMODE "\":%d,\"" + D_JSON_FEATURES "\":[\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\"]"), + ESP.getSketchSize()/1024, ESP.getFreeSketchSpace()/1024, ESP.getFreeHeap()/1024, + ESP.getFlashChipSize()/1024, ESP.getFlashChipRealSize()/1024, ESP.getFlashChipId(), ESP.getFlashChipMode(), + LANGUAGE_LCID, feature_drv1, feature_drv2, feature_sns1, feature_sns2, feature5, feature6); + XsnsDriverState(); + ResponseAppend_P(PSTR(",\"Sensors\":")); + XsnsSensorState(); + ResponseJsonEndEnd(); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "4")); + } + + if ((0 == payload) || (5 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS5_NETWORK "\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"" D_JSON_GATEWAY "\":\"%s\",\"" + D_JSON_SUBNETMASK "\":\"%s\",\"" D_JSON_DNSSERVER "\":\"%s\",\"" D_JSON_MAC "\":\"%s\",\"" + D_CMND_WEBSERVER "\":%d,\"" D_CMND_WIFICONFIG "\":%d,\"" D_CMND_WIFIPOWER "\":%s}}"), + my_hostname, WiFi.localIP().toString().c_str(), IPAddress(Settings.ip_address[1]).toString().c_str(), + IPAddress(Settings.ip_address[2]).toString().c_str(), IPAddress(Settings.ip_address[3]).toString().c_str(), WiFi.macAddress().c_str(), + Settings.webserver, Settings.sta_config, WifiGetOutputPower().c_str()); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "5")); + } + + if (((0 == payload) || (6 == payload)) && Settings.flag.mqtt_enabled) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS6_MQTT "\":{\"" D_CMND_MQTTHOST "\":\"%s\",\"" D_CMND_MQTTPORT "\":%d,\"" D_CMND_MQTTCLIENT D_JSON_MASK "\":\"%s\",\"" + D_CMND_MQTTCLIENT "\":\"%s\",\"" D_CMND_MQTTUSER "\":\"%s\",\"" D_JSON_MQTT_COUNT "\":%d,\"MAX_PACKET_SIZE\":%d,\"KEEPALIVE\":%d}}"), + SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), + mqtt_client, SettingsText(SET_MQTT_USER), MqttConnectCount(), MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "6")); + } + + if ((0 == payload) || (7 == payload)) { + if (99 == Settings.timezone) { + snprintf_P(stemp, sizeof(stemp), PSTR("%d" ), Settings.timezone); + } else { + snprintf_P(stemp, sizeof(stemp), PSTR("\"%s\"" ), GetTimeZone().c_str()); + } +#if defined(USE_TIMERS) && defined(USE_SUNRISE) + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_JSON_UTC_TIME "\":\"%s\",\"" D_JSON_LOCAL_TIME "\":\"%s\",\"" D_JSON_STARTDST "\":\"%s\",\"" + D_JSON_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%s,\"" D_JSON_SUNRISE "\":\"%s\",\"" D_JSON_SUNSET "\":\"%s\"}}"), + GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_LOCALNOTZ).c_str(), GetDateAndTime(DT_DST).c_str(), + GetDateAndTime(DT_STD).c_str(), stemp, GetSun(0).c_str(), GetSun(1).c_str()); +#else + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_JSON_UTC_TIME "\":\"%s\",\"" D_JSON_LOCAL_TIME "\":\"%s\",\"" D_JSON_STARTDST "\":\"%s\",\"" + D_JSON_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%s}}"), + GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_LOCALNOTZ).c_str(), GetDateAndTime(DT_DST).c_str(), + GetDateAndTime(DT_STD).c_str(), stemp); +#endif + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "7")); + } + +#if defined(USE_ENERGY_SENSOR) && defined(USE_ENERGY_MARGIN_DETECTION) + if (energy_flg) { + if ((0 == payload) || (9 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":%d,\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\"" + D_CMND_VOLTAGELOW "\":%d,\"" D_CMND_VOLTAGEHIGH "\":%d,\"" D_CMND_CURRENTLOW "\":%d,\"" D_CMND_CURRENTHIGH "\":%d}}"), + Settings.energy_power_delta, Settings.energy_min_power, Settings.energy_max_power, + Settings.energy_min_voltage, Settings.energy_max_voltage, Settings.energy_min_current, Settings.energy_max_current); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "9")); + } + } +#endif + + if ((0 == payload) || (8 == payload) || (10 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS10_SENSOR "\":")); + MqttShowSensor(); + ResponseJsonEnd(); + if (8 == payload) { + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "8")); + } else { + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "10")); + } + } + + if ((0 == payload) || (11 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS11_STATUS "\":")); + MqttShowState(); + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "11")); + } + + if (CrashFlag()) { + if ((0 == payload) || (12 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS12_STATUS "\":")); + CrashDump(); + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "12")); + } + } + +#ifdef USE_SCRIPT_STATUS + if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data); +#endif + mqtt_data[0] = '\0'; +} + +void CmndState(void) +{ + mqtt_data[0] = '\0'; + MqttShowState(); + if (Settings.flag3.hass_tele_on_power) { + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN); + } +#ifdef USE_HOME_ASSISTANT + if (Settings.flag.hass_discovery) { + HAssPublishStatus(); + } +#endif +} + +void CmndTempOffset(void) +{ + if (XdrvMailbox.data_len > 0) { + int value = (int)(CharToFloat(XdrvMailbox.data) * 10); + if ((value > -127) && (value < 127)) { + Settings.temp_comp = value; + } + } + ResponseCmndFloat((float)(Settings.temp_comp) / 10, 1); +} + +void CmndSleep(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 251)) { + Settings.sleep = XdrvMailbox.payload; + sleep = XdrvMailbox.payload; + WiFiSetSleepMode(); + } + Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.sleep, sleep); + +} + +void CmndUpgrade(void) +{ + + + + + if (((1 == XdrvMailbox.data_len) && (1 == XdrvMailbox.payload)) || ((XdrvMailbox.data_len >= 3) && NewerVersion(XdrvMailbox.data))) { + ota_state_flag = 3; + char stemp1[TOPSZ]; + Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), XdrvMailbox.command, my_version, GetOtaUrl(stemp1, sizeof(stemp1))); + } else { + Response_P(PSTR("{\"%s\":\"" D_JSON_ONE_OR_GT "\"}"), XdrvMailbox.command, my_version); + } +} + +void CmndOtaUrl(void) +{ + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_OTAURL, (SC_DEFAULT == Shortcut()) ? OTA_URL : XdrvMailbox.data); + } + ResponseCmndChar(SettingsText(SET_OTAURL)); +} + +void CmndSeriallog(void) +{ + if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) { + Settings.flag.mqtt_serial = 0; + SetSeriallog(XdrvMailbox.payload); + } + Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.seriallog_level, seriallog_level); +} + +void CmndRestart(void) +{ + switch (XdrvMailbox.payload) { + case 1: + restart_flag = 2; + ResponseCmndChar(D_JSON_RESTARTING); + break; + case -1: + CmndCrash(); + break; + case -2: + CmndWDT(); + break; + case -3: + CmndBlockedLoop(); + break; + case 99: + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING)); + EspRestart(); + break; + default: + ResponseCmndChar(D_JSON_ONE_TO_RESTART); + } +} + +void CmndPowerOnState(void) +{ + if (my_module_type != MOTOR) { + + + + + + + + if ((XdrvMailbox.payload >= POWER_ALL_OFF) && (XdrvMailbox.payload <= POWER_ALL_OFF_PULSETIME_ON)) { + Settings.poweronstate = XdrvMailbox.payload; + if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) { + for (uint32_t i = 1; i <= devices_present; i++) { + ExecuteCommandPower(i, POWER_ON, SRC_IGNORE); + } + } + } + ResponseCmndNumber(Settings.poweronstate); + } +} + +void CmndPulsetime(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PULSETIMERS)) { + uint32_t items = 1; + if (!XdrvMailbox.usridx && !XdrvMailbox.data_len) { + items = MAX_PULSETIMERS; + } else { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 65536)) { + Settings.pulse_timer[XdrvMailbox.index -1] = XdrvMailbox.payload; + SetPulseTimer(XdrvMailbox.index -1, XdrvMailbox.payload); + } + } + mqtt_data[0] = '\0'; + for (uint32_t i = 0; i < items; i++) { + uint32_t index = (1 == items) ? XdrvMailbox.index : i +1; + ResponseAppend_P(PSTR("%c\"%s%d\":{\"" D_JSON_SET "\":%d,\"" D_JSON_REMAINING "\":%d}"), + (i) ? ',' : '{', + XdrvMailbox.command, index, + Settings.pulse_timer[index -1], GetPulseTimer(index -1)); + } + ResponseJsonEnd(); + } +} + +void CmndBlinktime(void) +{ + if ((XdrvMailbox.payload > 1) && (XdrvMailbox.payload <= 3600)) { + Settings.blinktime = XdrvMailbox.payload; + if (blink_timer > 0) { blink_timer = millis() + (100 * XdrvMailbox.payload); } + } + ResponseCmndNumber(Settings.blinktime); +} + +void CmndBlinkcount(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 65536)) { + Settings.blinkcount = XdrvMailbox.payload; + if (blink_counter) { blink_counter = Settings.blinkcount *2; } + } + ResponseCmndNumber(Settings.blinkcount); +} + +void CmndSavedata(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3600)) { + Settings.save_data = XdrvMailbox.payload; + save_data_counter = Settings.save_data; + } + SettingsSaveAll(); + char stemp1[TOPSZ]; + if (Settings.save_data > 1) { + snprintf_P(stemp1, sizeof(stemp1), PSTR(D_JSON_EVERY " %d " D_UNIT_SECOND), Settings.save_data); + } + ResponseCmndChar((Settings.save_data > 1) ? stemp1 : GetStateText(Settings.save_data)); +} + +void CmndSetoption(void) +{ + if (XdrvMailbox.index < 114) { + uint32_t ptype; + uint32_t pindex; + if (XdrvMailbox.index <= 31) { + ptype = 2; + pindex = XdrvMailbox.index; + } + else if (XdrvMailbox.index <= 49) { + ptype = 1; + pindex = XdrvMailbox.index -32; + } + else if (XdrvMailbox.index <= 81) { + ptype = 3; + pindex = XdrvMailbox.index -50; + } + else { + ptype = 4; + pindex = XdrvMailbox.index -82; + } + + if (XdrvMailbox.payload >= 0) { + if (1 == ptype) { + uint32_t param_low = 0; + uint32_t param_high = 255; + switch (pindex) { + case P_HOLD_TIME: + case P_MAX_POWER_RETRY: + param_low = 1; + param_high = 250; + break; + } + if ((XdrvMailbox.payload >= param_low) && (XdrvMailbox.payload <= param_high)) { + Settings.param[pindex] = XdrvMailbox.payload; +#ifdef USE_LIGHT + if (P_RGB_REMAP == pindex) { + LightUpdateColorMapping(); + restart_flag = 2; + } +#endif +#if (defined(USE_IR_REMOTE) && defined(USE_IR_RECEIVE)) || defined(USE_IR_REMOTE_FULL) + if (P_IR_UNKNOW_THRESHOLD == pindex) { + IrReceiveUpdateThreshold(); + } +#endif + } else { + ptype = 99; + } + } else { + if (XdrvMailbox.payload <= 1) { + if (2 == ptype) { + switch (pindex) { + case 5: + case 6: + case 7: + case 9: + case 14: + case 22: + case 23: + case 25: + case 27: + ptype = 99; + break; + case 3: + case 15: + restart_flag = 2; + default: + bitWrite(Settings.flag.data, pindex, XdrvMailbox.payload); + } + if (12 == pindex) { + stop_flash_rotate = XdrvMailbox.payload; + SettingsSave(2); + } + #ifdef USE_HOME_ASSISTANT + if ((19 == pindex) || (30 == pindex)) { + HAssDiscover(); + } + #endif + } + else if (3 == ptype) { + bitWrite(Settings.flag3.data, pindex, XdrvMailbox.payload); + switch (pindex) { + case 5: + if (0 == XdrvMailbox.payload) { + restart_flag = 2; + } + break; + case 10: + WiFiSetSleepMode(); + break; + case 18: + case 25: + restart_flag = 2; + break; + } + } + else if (4 == ptype) { + bitWrite(Settings.flag4.data, pindex, XdrvMailbox.payload); + } + } else { + ptype = 99; + } + } + } + + if (ptype < 99) { + if (1 == ptype) { + ResponseCmndIdxNumber(Settings.param[pindex]); + } else { + uint32_t flag = Settings.flag.data; + if (3 == ptype) { + flag = Settings.flag3.data; + } + else if (4 == ptype) { + flag = Settings.flag4.data; + } + ResponseCmndIdxChar(GetStateText(bitRead(flag, pindex))); + } + } + } +} + +void CmndTemperatureResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + Settings.flag2.temperature_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.temperature_resolution); +} + +void CmndHumidityResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + Settings.flag2.humidity_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.humidity_resolution); +} + +void CmndPressureResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + Settings.flag2.pressure_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.pressure_resolution); +} + +void CmndPowerResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + Settings.flag2.wattage_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.wattage_resolution); +} + +void CmndVoltageResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + Settings.flag2.voltage_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.voltage_resolution); +} + +void CmndFrequencyResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + Settings.flag2.frequency_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.frequency_resolution); +} + +void CmndCurrentResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + Settings.flag2.current_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.current_resolution); +} + +void CmndEnergyResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 5)) { + Settings.flag2.energy_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.energy_resolution); +} + +void CmndWeightResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + Settings.flag2.weight_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.weight_resolution); +} + +void CmndModule(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= MAXMODULE)) { + bool present = false; + if (0 == XdrvMailbox.payload) { + XdrvMailbox.payload = USER_MODULE; + present = true; + } else { + XdrvMailbox.payload--; + present = ValidTemplateModule(XdrvMailbox.payload); + } + if (present) { + Settings.last_module = Settings.module; + Settings.module = XdrvMailbox.payload; + SetModuleType(); + if (Settings.last_module != XdrvMailbox.payload) { + for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { + Settings.my_gp.io[i] = GPIO_NONE; + } + } + restart_flag = 2; + } + } + Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, ModuleNr(), ModuleName().c_str()); +} + +void CmndModules(void) +{ + uint32_t midx = USER_MODULE; + uint32_t lines = 1; + bool jsflg = false; + for (uint32_t i = 0; i <= sizeof(kModuleNiceList); i++) { + if (i > 0) { midx = pgm_read_byte(kModuleNiceList + i -1); } + if (!jsflg) { + Response_P(PSTR("{\"" D_CMND_MODULES "%d\":{"), lines); + } else { + ResponseAppend_P(PSTR(",")); + } + jsflg = true; + uint32_t j = i ? midx +1 : 0; + if ((ResponseAppend_P(PSTR("\"%d\":\"%s\""), j, AnyModuleName(midx).c_str()) > (LOGSZ - TOPSZ)) || (i == sizeof(kModuleNiceList))) { + ResponseJsonEndEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, UpperCase(XdrvMailbox.command, XdrvMailbox.command)); + jsflg = false; + lines++; + } + } + mqtt_data[0] = '\0'; +} + +void CmndGpio(void) +{ + if (XdrvMailbox.index < sizeof(Settings.my_gp)) { + myio cmodule; + ModuleGpios(&cmodule); + if (ValidGPIO(XdrvMailbox.index, cmodule.io[XdrvMailbox.index]) && (XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < GPIO_SENSOR_END)) { + bool present = false; + for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) { + uint32_t midx = pgm_read_byte(kGpioNiceList + i); + if (midx == XdrvMailbox.payload) { present = true; } + } + if (present) { + for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { + if (ValidGPIO(i, cmodule.io[i]) && (Settings.my_gp.io[i] == XdrvMailbox.payload)) { + Settings.my_gp.io[i] = GPIO_NONE; + } + } + Settings.my_gp.io[XdrvMailbox.index] = XdrvMailbox.payload; + restart_flag = 2; + } + } + Response_P(PSTR("{")); + bool jsflg = false; + for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { + if (ValidGPIO(i, cmodule.io[i]) || ((GPIO_USER == XdrvMailbox.payload) && !FlashPin(i))) { + if (jsflg) { ResponseAppend_P(PSTR(",")); } + jsflg = true; + uint8_t sensor_type = Settings.my_gp.io[i]; + if (!ValidGPIO(i, cmodule.io[i])) { + sensor_type = cmodule.io[i]; + if (GPIO_USER == sensor_type) { + sensor_type = GPIO_NONE; + } + } + uint8_t sensor_name_idx = sensor_type; + const char *sensor_names = kSensorNames; + if (sensor_type > GPIO_FIX_START) { + sensor_name_idx = sensor_type - GPIO_FIX_START -1; + sensor_names = kSensorNamesFixed; + } + char stemp1[TOPSZ]; + ResponseAppend_P(PSTR("\"" D_CMND_GPIO "%d\":{\"%d\":\"%s\"}"), + i, sensor_type, GetTextIndexed(stemp1, sizeof(stemp1), sensor_name_idx, sensor_names)); + } + } + if (jsflg) { + ResponseJsonEnd(); + } else { + ResponseCmndChar(D_JSON_NOT_SUPPORTED); + } + } +} + +void CmndGpios(void) +{ + myio cmodule; + ModuleGpios(&cmodule); + uint32_t lines = 1; + bool jsflg = false; + for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) { + uint32_t midx = pgm_read_byte(kGpioNiceList + i); + if ((XdrvMailbox.payload != 255) && GetUsedInModule(midx, cmodule.io)) { continue; } + if (!jsflg) { + Response_P(PSTR("{\"" D_CMND_GPIOS "%d\":{"), lines); + } else { + ResponseAppend_P(PSTR(",")); + } + jsflg = true; + char stemp1[TOPSZ]; + if ((ResponseAppend_P(PSTR("\"%d\":\"%s\""), midx, GetTextIndexed(stemp1, sizeof(stemp1), midx, kSensorNames)) > (LOGSZ - TOPSZ)) || (i == sizeof(kGpioNiceList) -1)) { + ResponseJsonEndEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, UpperCase(XdrvMailbox.command, XdrvMailbox.command)); + jsflg = false; + lines++; + } + } + mqtt_data[0] = '\0'; +} + +void CmndTemplate(void) +{ + + bool error = false; + + if (strstr(XdrvMailbox.data, "{") == nullptr) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= MAXMODULE)) { + XdrvMailbox.payload--; + if (ValidTemplateModule(XdrvMailbox.payload)) { + ModuleDefault(XdrvMailbox.payload); + if (USER_MODULE == Settings.module) { restart_flag = 2; } + } + } + else if (0 == XdrvMailbox.payload) { + if (Settings.module != USER_MODULE) { + ModuleDefault(Settings.module); + } + } + else if (255 == XdrvMailbox.payload) { + if (Settings.module != USER_MODULE) { + ModuleDefault(Settings.module); + } + snprintf_P(Settings.user_template.name, sizeof(Settings.user_template.name), PSTR("Merged")); + uint32_t j = 0; + for (uint32_t i = 0; i < sizeof(mycfgio); i++) { + if (6 == i) { j = 9; } + if (8 == i) { j = 12; } + if (my_module.io[j] > GPIO_NONE) { + Settings.user_template.gp.io[i] = my_module.io[j]; + } + j++; + } + } + } + else { + if (JsonTemplate(XdrvMailbox.data)) { + if (USER_MODULE == Settings.module) { restart_flag = 2; } + } else { + ResponseCmndChar(D_JSON_INVALID_JSON); + error = true; + } + } + if (!error) { TemplateJson(); } +} + +void CmndPwm(void) +{ + if (pwm_present && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PWMS)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= Settings.pwm_range) && (pin[GPIO_PWM1 + XdrvMailbox.index -1] < 99)) { + Settings.pwm_value[XdrvMailbox.index -1] = XdrvMailbox.payload; + analogWrite(pin[GPIO_PWM1 + XdrvMailbox.index -1], bitRead(pwm_inverted, XdrvMailbox.index -1) ? Settings.pwm_range - XdrvMailbox.payload : XdrvMailbox.payload); + } + Response_P(PSTR("{")); + MqttShowPWMState(); + ResponseJsonEnd(); + } +} + +void CmndPwmfrequency(void) +{ + if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= PWM_MIN) && (XdrvMailbox.payload <= PWM_MAX))) { + Settings.pwm_frequency = (1 == XdrvMailbox.payload) ? PWM_FREQ : XdrvMailbox.payload; + analogWriteFreq(Settings.pwm_frequency); + } + ResponseCmndNumber(Settings.pwm_frequency); +} + +void CmndPwmrange(void) +{ + if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload > 254) && (XdrvMailbox.payload < 1024))) { + Settings.pwm_range = (1 == XdrvMailbox.payload) ? PWM_RANGE : XdrvMailbox.payload; + for (uint32_t i = 0; i < MAX_PWMS; i++) { + if (Settings.pwm_value[i] > Settings.pwm_range) { + Settings.pwm_value[i] = Settings.pwm_range; + } + } + analogWriteRange(Settings.pwm_range); + } + ResponseCmndNumber(Settings.pwm_range); +} + +void CmndButtonDebounce(void) +{ + if ((XdrvMailbox.payload > 39) && (XdrvMailbox.payload < 1001)) { + Settings.button_debounce = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.button_debounce); +} + +void CmndSwitchDebounce(void) +{ + if ((XdrvMailbox.payload > 39) && (XdrvMailbox.payload < 1001)) { + Settings.switch_debounce = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.switch_debounce); +} + +void CmndBaudrate(void) +{ + if (XdrvMailbox.payload >= 300) { + XdrvMailbox.payload /= 300; + uint32_t baudrate = (XdrvMailbox.payload & 0xFFFF) * 300; + SetSerialBaudrate(baudrate); + } + ResponseCmndNumber(Settings.baudrate * 300); +} + +void CmndSerialConfig(void) +{ + + + + + if (XdrvMailbox.data_len > 0) { + if (XdrvMailbox.data_len < 3) { + if ((XdrvMailbox.payload >= TS_SERIAL_5N1) && (XdrvMailbox.payload <= TS_SERIAL_8O2)) { + SetSerialConfig(XdrvMailbox.payload); + } + } + else if ((XdrvMailbox.payload >= 5) && (XdrvMailbox.payload <= 8)) { + uint8_t serial_config = XdrvMailbox.payload -5; + + bool valid = true; + char parity = (XdrvMailbox.data[1] & 0xdf); + if ('E' == parity) { + serial_config += 0x08; + } + else if ('O' == parity) { + serial_config += 0x10; + } + else if ('N' != parity) { + valid = false; + } + + if ('2' == XdrvMailbox.data[2]) { + serial_config += 0x04; + } + else if ('1' != XdrvMailbox.data[2]) { + valid = false; + } + + if (valid) { + SetSerialConfig(serial_config); + } + } + } + ResponseCmndChar(GetSerialConfig().c_str()); +} + +void CmndSerialSend(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 5)) { + SetSeriallog(LOG_LEVEL_NONE); + Settings.flag.mqtt_serial = 1; + Settings.flag.mqtt_serial_raw = (XdrvMailbox.index > 3) ? 1 : 0; + if (XdrvMailbox.data_len > 0) { + if (1 == XdrvMailbox.index) { + Serial.printf("%s\n", XdrvMailbox.data); + } + else if (2 == XdrvMailbox.index || 4 == XdrvMailbox.index) { + for (uint32_t i = 0; i < XdrvMailbox.data_len; i++) { + Serial.write(XdrvMailbox.data[i]); + } + } + else if (3 == XdrvMailbox.index) { + uint32_t dat_len = XdrvMailbox.data_len; + Serial.printf("%s", Unescape(XdrvMailbox.data, &dat_len)); + } + else if (5 == XdrvMailbox.index) { + SerialSendRaw(RemoveSpace(XdrvMailbox.data)); + } + ResponseCmndDone(); + } + } +} + +void CmndSerialDelimiter(void) +{ + if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.payload < 256)) { + if (XdrvMailbox.payload > 0) { + Settings.serial_delimiter = XdrvMailbox.payload; + } else { + uint32_t dat_len = XdrvMailbox.data_len; + Unescape(XdrvMailbox.data, &dat_len); + Settings.serial_delimiter = XdrvMailbox.data[0]; + } + } + ResponseCmndNumber(Settings.serial_delimiter); +} + +void CmndSyslog(void) +{ + if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) { + SetSyslog(XdrvMailbox.payload); + } + Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.syslog_level, syslog_level); +} + +void CmndLoghost(void) +{ + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_SYSLOG_HOST, (SC_DEFAULT == Shortcut()) ? SYS_LOG_HOST : XdrvMailbox.data); + } + ResponseCmndChar(SettingsText(SET_SYSLOG_HOST)); +} + +void CmndLogport(void) +{ + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 65536)) { + Settings.syslog_port = (1 == XdrvMailbox.payload) ? SYS_LOG_PORT : XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.syslog_port); +} + +void CmndIpAddress(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) { + uint32_t address; + if (ParseIp(&address, XdrvMailbox.data)) { + Settings.ip_address[XdrvMailbox.index -1] = address; + + } + char stemp1[TOPSZ]; + snprintf_P(stemp1, sizeof(stemp1), PSTR(" (%s)"), WiFi.localIP().toString().c_str()); + Response_P(S_JSON_COMMAND_INDEX_SVALUE_SVALUE, XdrvMailbox.command, XdrvMailbox.index, IPAddress(Settings.ip_address[XdrvMailbox.index -1]).toString().c_str(), (1 == XdrvMailbox.index) ? stemp1:""); + } +} + +void CmndNtpServer(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_NTP_SERVERS)) { + if (!XdrvMailbox.usridx) { + ResponseCmndAll(SET_NTPSERVER1, MAX_NTP_SERVERS); + } else { + uint32_t ntp_server = SET_NTPSERVER1 + XdrvMailbox.index -1; + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(ntp_server, + (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? NTP_SERVER1 : (2 == XdrvMailbox.index) ? NTP_SERVER2 : NTP_SERVER3 : XdrvMailbox.data); + SettingsUpdateText(ntp_server, ReplaceCommaWithDot(SettingsText(ntp_server))); + + ntp_force_sync = true; + } + ResponseCmndIdxChar(SettingsText(ntp_server)); + } + } +} + +void CmndAp(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { + switch (XdrvMailbox.payload) { + case 0: + Settings.sta_active ^= 1; + break; + case 1: + case 2: + Settings.sta_active = XdrvMailbox.payload -1; + } + restart_flag = 2; + } + Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active)); +} + +void CmndSsid(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SSIDS)) { + if (!XdrvMailbox.usridx) { + ResponseCmndAll(SET_STASSID1, MAX_SSIDS); + } else { + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_STASSID1 + XdrvMailbox.index -1, + (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_SSID1 : STA_SSID2 : XdrvMailbox.data); + Settings.sta_active = XdrvMailbox.index -1; + restart_flag = 2; + } + ResponseCmndIdxChar(SettingsText(SET_STASSID1 + XdrvMailbox.index -1)); + } + } +} + +void CmndPassword(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) { + if ((XdrvMailbox.data_len > 4) || (SC_CLEAR == Shortcut()) || (SC_DEFAULT == Shortcut())) { + SettingsUpdateText(SET_STAPWD1 + XdrvMailbox.index -1, + (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_PASS1 : STA_PASS2 : XdrvMailbox.data); + Settings.sta_active = XdrvMailbox.index -1; + restart_flag = 2; + ResponseCmndIdxChar(SettingsText(SET_STAPWD1 + XdrvMailbox.index -1)); + } else { + Response_P(S_JSON_COMMAND_INDEX_ASTERISK, XdrvMailbox.command, XdrvMailbox.index); + } + } +} + +void CmndHostname(void) +{ + if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) { + SettingsUpdateText(SET_HOSTNAME, (SC_DEFAULT == Shortcut()) ? WIFI_HOSTNAME : XdrvMailbox.data); + if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) { + SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME); + } + restart_flag = 2; + } + ResponseCmndChar(SettingsText(SET_HOSTNAME)); +} + +void CmndWifiConfig(void) +{ + if ((XdrvMailbox.payload >= WIFI_RESTART) && (XdrvMailbox.payload < MAX_WIFI_OPTION)) { + if ((EX_WIFI_SMARTCONFIG == XdrvMailbox.payload) || (EX_WIFI_WPSCONFIG == XdrvMailbox.payload)) { + XdrvMailbox.payload = WIFI_MANAGER; + } + Settings.sta_config = XdrvMailbox.payload; + wifi_state_flag = Settings.sta_config; + if (WifiState() > WIFI_RESTART) { + restart_flag = 2; + } + } + char stemp1[TOPSZ]; + Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_config, GetTextIndexed(stemp1, sizeof(stemp1), Settings.sta_config, kWifiConfig)); +} + +void CmndFriendlyname(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_FRIENDLYNAMES)) { + if (!XdrvMailbox.usridx) { + ResponseCmndAll(SET_FRIENDLYNAME1, MAX_FRIENDLYNAMES); + } else { + if (XdrvMailbox.data_len > 0) { + char stemp1[TOPSZ]; + if (1 == XdrvMailbox.index) { + snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME)); + } else { + snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME "%d"), XdrvMailbox.index); + } + SettingsUpdateText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : (SC_DEFAULT == Shortcut()) ? stemp1 : XdrvMailbox.data); + } + ResponseCmndIdxChar(SettingsText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1)); + } + } +} + +void CmndSwitchMode(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SWITCHES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_SWITCH_OPTION)) { + Settings.switchmode[XdrvMailbox.index -1] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Settings.switchmode[XdrvMailbox.index-1]); + } +} + +void CmndInterlock(void) +{ + + uint32_t max_relays = devices_present; + if (light_type) { max_relays--; } + if (max_relays > sizeof(Settings.interlock[0]) * 8) { max_relays = sizeof(Settings.interlock[0]) * 8; } + if (max_relays > 1) { + if (XdrvMailbox.data_len > 0) { + if (strstr(XdrvMailbox.data, ",") != nullptr) { + for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; } + char *group; + char *q; + uint32_t group_index = 0; + power_t relay_mask = 0; + for (group = strtok_r(XdrvMailbox.data, " ", &q); group && group_index < MAX_INTERLOCKS; group = strtok_r(nullptr, " ", &q)) { + char *str; + char *p; + for (str = strtok_r(group, ",", &p); str; str = strtok_r(nullptr, ",", &p)) { + int pbit = atoi(str); + if ((pbit > 0) && (pbit <= max_relays)) { + pbit--; + if (!bitRead(relay_mask, pbit)) { + bitSet(relay_mask, pbit); + bitSet(Settings.interlock[group_index], pbit); + } + } + } + group_index++; + } + for (uint32_t i = 0; i < group_index; i++) { + uint32_t minimal_bits = 0; + for (uint32_t j = 0; j < max_relays; j++) { + if (bitRead(Settings.interlock[i], j)) { minimal_bits++; } + } + if (minimal_bits < 2) { Settings.interlock[i] = 0; } + } + } else { + Settings.flag.interlock = XdrvMailbox.payload &1; + if (Settings.flag.interlock) { + SetDevicePower(power, SRC_IGNORE); + } + } +#ifdef USE_SHUTTER + if (Settings.flag3.shutter_mode) { + ShutterInit(); + } +#endif + } + Response_P(PSTR("{\"" D_CMND_INTERLOCK "\":\"%s\",\"" D_JSON_GROUPS "\":\""), GetStateText(Settings.flag.interlock)); + uint32_t anygroup = 0; + for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { + if (Settings.interlock[i]) { + anygroup++; + ResponseAppend_P(PSTR("%s"), (anygroup > 1) ? " " : ""); + uint32_t anybit = 0; + power_t mask = 1; + for (uint32_t j = 0; j < max_relays; j++) { + if (Settings.interlock[i] & mask) { + anybit++; + ResponseAppend_P(PSTR("%s%d"), (anybit > 1) ? "," : "", j +1); + } + mask <<= 1; + } + } + } + if (!anygroup) { + for (uint32_t j = 1; j <= max_relays; j++) { + ResponseAppend_P(PSTR("%s%d"), (j > 1) ? "," : "", j); + } + } + ResponseAppend_P(PSTR("\"}")); + } else { + Settings.flag.interlock = 0; + ResponseCmndStateText(Settings.flag.interlock); + } +} + +void CmndTeleperiod(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.tele_period = (1 == XdrvMailbox.payload) ? TELE_PERIOD : XdrvMailbox.payload; + if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) Settings.tele_period = 10; + tele_period = Settings.tele_period; + } + ResponseCmndNumber(Settings.tele_period); +} + +void CmndReset(void) +{ + switch (XdrvMailbox.payload) { + case 1: + restart_flag = 211; + ResponseCmndChar(D_JSON_RESET_AND_RESTARTING); + break; + case 2 ... 6: + restart_flag = 210 + XdrvMailbox.payload; + Response_P(PSTR("{\"" D_CMND_RESET "\":\"" D_JSON_ERASE ", " D_JSON_RESET_AND_RESTARTING "\"}")); + break; + case 99: + Settings.bootcount = 0; + Settings.bootcount_reset_time = 0; + ResponseCmndDone(); + break; + default: + ResponseCmndChar(D_JSON_ONE_TO_RESET); + } +} + +void CmndTime(void) +{ + + + + + + + + uint32_t format = Settings.flag2.time_format; + if (XdrvMailbox.data_len > 0) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4)) { + Settings.flag2.time_format = XdrvMailbox.payload -1; + format = Settings.flag2.time_format; + } else { + format = 1; + RtcSetTime(XdrvMailbox.payload); + } + } + mqtt_data[0] = '\0'; + ResponseAppendTimeFormat(format); + ResponseJsonEnd(); +} + +void CmndTimezone(void) +{ + if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.payload >= -13)) { + Settings.timezone = XdrvMailbox.payload; + Settings.timezone_minutes = 0; + if (XdrvMailbox.payload < 15) { + char *p = strtok (XdrvMailbox.data, ":"); + if (p) { + p = strtok (nullptr, ":"); + if (p) { + Settings.timezone_minutes = strtol(p, nullptr, 10); + if (Settings.timezone_minutes > 59) { Settings.timezone_minutes = 59; } + } + } + } else { + Settings.timezone = 99; + } + ntp_force_sync = true; + } + if (99 == Settings.timezone) { + ResponseCmndNumber(Settings.timezone); + } else { + char stemp1[TOPSZ]; + snprintf_P(stemp1, sizeof(stemp1), PSTR("%+03d:%02d"), Settings.timezone, Settings.timezone_minutes); + ResponseCmndChar(stemp1); + } +} + +void CmndTimeStdDst(uint32_t ts) +{ + + if (XdrvMailbox.data_len > 0) { + if (strstr(XdrvMailbox.data, ",") != nullptr) { + uint32_t tpos = 0; + int value = 0; + char *p = XdrvMailbox.data; + char *q = p; + while (p && (tpos < 7)) { + if (p > q) { + if (1 == tpos) { Settings.tflag[ts].hemis = value &1; } + if (2 == tpos) { Settings.tflag[ts].week = (value < 0) ? 0 : (value > 4) ? 4 : value; } + if (3 == tpos) { Settings.tflag[ts].month = (value < 1) ? 1 : (value > 12) ? 12 : value; } + if (4 == tpos) { Settings.tflag[ts].dow = (value < 1) ? 1 : (value > 7) ? 7 : value; } + if (5 == tpos) { Settings.tflag[ts].hour = (value < 0) ? 0 : (value > 23) ? 23 : value; } + if (6 == tpos) { Settings.toffset[ts] = (value < -900) ? -900 : (value > 900) ? 900 : value; } + } + p = Trim(p); + if (tpos && (*p == ',')) { p++; } + p = Trim(p); + q = p; + value = strtol(p, &p, 10); + tpos++; + } + ntp_force_sync = true; + } else { + if (0 == XdrvMailbox.payload) { + if (0 == ts) { + SettingsResetStd(); + } else { + SettingsResetDst(); + } + } + ntp_force_sync = true; + } + } + Response_P(PSTR("{\"%s\":{\"Hemisphere\":%d,\"Week\":%d,\"Month\":%d,\"Day\":%d,\"Hour\":%d,\"Offset\":%d}}"), + XdrvMailbox.command, Settings.tflag[ts].hemis, Settings.tflag[ts].week, Settings.tflag[ts].month, Settings.tflag[ts].dow, Settings.tflag[ts].hour, Settings.toffset[ts]); +} + +void CmndTimeStd(void) +{ + CmndTimeStdDst(0); +} + +void CmndTimeDst(void) +{ + CmndTimeStdDst(1); +} + +void CmndAltitude(void) +{ + if ((XdrvMailbox.data_len > 0) && ((XdrvMailbox.payload >= -30000) && (XdrvMailbox.payload <= 30000))) { + Settings.altitude = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.altitude); +} + +void CmndLedPower(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_LEDS)) { + if (99 == pin[GPIO_LEDLNK]) { XdrvMailbox.index = 1; } + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { + Settings.ledstate &= 8; + uint32_t mask = 1 << (XdrvMailbox.index -1); + switch (XdrvMailbox.payload) { + case 0: + led_power &= (0xFF ^ mask); + Settings.ledstate = 0; + break; + case 1: + led_power |= mask; + Settings.ledstate = 8; + break; + case 2: + led_power ^= mask; + Settings.ledstate ^= 8; + break; + } + blinks = 0; + if (99 == pin[GPIO_LEDLNK]) { + SetLedPower(Settings.ledstate &8); + } else { + SetLedPowerIdx(XdrvMailbox.index -1, (led_power & mask)); + } + } + bool state = bitRead(led_power, XdrvMailbox.index -1); + if (99 == pin[GPIO_LEDLNK]) { + state = bitRead(Settings.ledstate, 3); + } + ResponseCmndIdxChar(GetStateText(state)); + } +} + +void CmndLedState(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_LED_OPTION)) { + Settings.ledstate = XdrvMailbox.payload; + if (!Settings.ledstate) { + SetLedPowerAll(0); + SetLedLink(0); + } + } + ResponseCmndNumber(Settings.ledstate); +} + +void CmndLedMask(void) +{ + if (XdrvMailbox.data_len > 0) { + Settings.ledmask = XdrvMailbox.payload; + } + char stemp1[TOPSZ]; + snprintf_P(stemp1, sizeof(stemp1), PSTR("%d (0x%04X)"), Settings.ledmask, Settings.ledmask); + ResponseCmndChar(stemp1); +} + +void CmndWifiPower(void) +{ + if (XdrvMailbox.data_len > 0) { + Settings.wifi_output_power = (uint8_t)(CharToFloat(XdrvMailbox.data) * 10); + if (Settings.wifi_output_power > 205) { + Settings.wifi_output_power = 205; + } + WifiSetOutputPower(); + } + ResponseCmndChar(WifiGetOutputPower().c_str()); +} + +#ifdef USE_I2C +void CmndI2cScan(void) +{ + if (i2c_flg) { + I2cScan(mqtt_data, sizeof(mqtt_data)); + } +} + +void CmndI2cDriver(void) +{ + if (XdrvMailbox.index < MAX_I2C_DRIVERS) { + if (XdrvMailbox.payload >= 0) { + bitWrite(Settings.i2c_drivers[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1); + restart_flag = 2; + } + } + Response_P(PSTR("{\"" D_CMND_I2CDRIVER "\":")); + I2cDriverState(); + ResponseJsonEnd(); +} +#endif + +#ifdef USE_DEVICE_GROUPS +void CmndDevGroupShare(void) +{ + uint32_t parm[2] = { Settings.device_group_share_in, Settings.device_group_share_out }; + ParseParameters(2, parm); + Settings.device_group_share_in = parm[0]; + Settings.device_group_share_out = parm[1]; + Response_P(PSTR("{\"" D_CMND_DEVGROUP_SHARE "\":{\"In\":%x,\"Out\":%x}}"), Settings.device_group_share_in, Settings.device_group_share_out); +} +#endif + +void CmndSensor(void) +{ + XsnsCall(FUNC_COMMAND_SENSOR); +} + +void CmndDriver(void) +{ + XdrvCall(FUNC_COMMAND_DRIVER); +} +# 1 "S:/Development/Tasmota/tasmota/support_crash_recorder.ino" +# 20 "S:/Development/Tasmota/tasmota/support_crash_recorder.ino" +const uint32_t crash_magic = 0x53415400; +const uint32_t crash_rtc_offset = 32; +const uint32_t crash_dump_max_len = 31; + + + + + + +extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack, uint32_t stack_end ) +{ + uint32_t addr_written = 0; + uint32_t value; + + for (uint32_t i = stack; i < stack_end; i += 4) { + value = *((uint32_t*) i); + if ((value >= 0x40000000) && (value < 0x40300000)) { + ESP.rtcUserMemoryWrite(crash_rtc_offset + addr_written, (uint32_t*)&value, sizeof(value)); + addr_written++; + if (addr_written >= crash_dump_max_len) { break; } + } + } + value = crash_magic + addr_written; + ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); +} + + +void CmndCrash(void) +{ + volatile uint32_t dummy; + dummy = *((uint32_t*) 0x00000000); +} + + +void CmndWDT(void) +{ + volatile uint32_t dummy = 0; + while (1) { + dummy++; + } +} + + +void CmndBlockedLoop(void) +{ + while (1) { + delay(1000); + } +} + + +void CrashDumpClear(void) +{ + uint32_t value = 0; + ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); +} + + + + + +bool CrashFlag(void) +{ + return ((ResetReason() == REASON_EXCEPTION_RST) || (ResetReason() == REASON_SOFT_WDT_RST) || oswatch_blocked_loop); +} + +void CrashDump(void) +{ + ResponseAppend_P(PSTR("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":[\"%08x\",\"%08x\",\"%08x\"],\"EXCVADDR\":\"%08x\",\"DEPC\":\"%08x\""), + resetInfo.exccause, + GetResetReason().c_str(), + resetInfo.epc1, + resetInfo.epc2, + resetInfo.epc3, + resetInfo.excvaddr, + resetInfo.depc); + + uint32_t value; + ESP.rtcUserMemoryRead(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); + if (crash_magic == (value & 0xFFFFFF00)) { + ResponseAppend_P(PSTR(",\"CallChain\":[")); + uint32_t count = value & 0x3F; + for (uint32_t i = 0; i < count; i++) { + ESP.rtcUserMemoryRead(crash_rtc_offset +i, (uint32_t*)&value, sizeof(value)); + if (i > 0) { ResponseAppend_P(PSTR(",")); } + ResponseAppend_P(PSTR("\"%08x\""), value); + } + ResponseAppend_P(PSTR("]")); + } + + ResponseJsonEnd(); +} +# 1 "S:/Development/Tasmota/tasmota/support_device_groups.ino" +# 23 "S:/Development/Tasmota/tasmota/support_device_groups.ino" +#ifdef USE_DEVICE_GROUPS + + + +extern bool udp_connected; + +struct device_group_member { + struct device_group_member * flink; + IPAddress ip_address; + uint32_t timeout_time; + uint16_t received_sequence; + uint16_t acked_sequence; +}; + +struct device_group { + uint32_t next_ack_check_time; + uint16_t last_full_status_sequence; + uint16_t message_length; + uint8_t message_header_length; + uint8_t initial_status_requests_remaining; + bool local; + char group_name[TOPSZ]; + char message[128]; + uint8_t group_member_count; + struct device_group_member * device_group_members; +}; + +struct device_group * device_groups; +uint16_t outgoing_sequence = 0; +bool device_groups_initialized = false; +bool device_groups_initialization_failed = false; +bool building_status_message = false; +bool processing_remote_device_message = false; +bool waiting_for_acks; +bool udp_was_connected = false; + +void DeviceGroupsInit(void) +{ + + device_groups = (struct device_group *)calloc(device_group_count, sizeof(struct device_group)); + if (device_groups == nullptr) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating %u-element device group array"), device_group_count); + device_groups_initialization_failed = true; + return; + } + + for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) { + struct device_group * device_group = &device_groups[device_group_index]; + strcpy(device_group->group_name, SettingsText((device_group_index == 0 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + device_group_index - 1))); + device_group->message_header_length = sprintf_P(device_group->message, PSTR("%s%s HTTP/1.1\n\n"), kDeviceGroupMessage, device_group->group_name); + device_group->last_full_status_sequence = -1; + } + + device_groups[0].local = true; + + + if (!Settings.device_group_share_in && !Settings.device_group_share_out) { + Settings.device_group_share_in = Settings.device_group_share_out = 0xffffffff; + } + + device_groups_initialized = true; +} + +char * IPAddressToString(const IPAddress& ip_address) +{ + static char buffer[16]; + sprintf_P(buffer, PSTR("%u.%u.%u.%u"), ip_address[0], ip_address[1], ip_address[2], ip_address[3]); + return buffer; +} + + +bool DeviceGroupItemShared(bool incoming, uint8_t item) +{ + uint8_t mask = 0; + switch (item) { + case DGR_ITEM_LIGHT_BRI: + mask = DGR_SHARE_LIGHT_BRI; + break; + case DGR_ITEM_POWER: + mask = DGR_SHARE_POWER; + break; + case DGR_ITEM_LIGHT_SCHEME: + mask = DGR_SHARE_LIGHT_SCHEME; + break; + case DGR_ITEM_LIGHT_FIXED_COLOR: + case DGR_ITEM_LIGHT_CHANNELS: + mask = DGR_SHARE_LIGHT_COLOR; + break; + case DGR_ITEM_LIGHT_FADE: + case DGR_ITEM_LIGHT_SPEED: + mask = DGR_SHARE_LIGHT_FADE; + break; + case DGR_ITEM_BRI_MIN: + mask = DGR_SHARE_BRI_MIN; + break; + } + return (!mask || ((incoming ? Settings.device_group_share_in : Settings.device_group_share_out) & mask)); +} + +void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * label) +{ + for (int attempt = 1; attempt <= 5; attempt++) { + if (PortUdp.beginPacket(ip, 1900)) { + PortUdp.write(packet, len); + if (PortUdp.endPacket()) return; + } + delay(10); + } + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error sending %s packet"), label); +} + +void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...) +{ + + if (!Settings.flag4.device_groups_enabled) return; + + + if (!udp_connected) return; + + + if (processing_remote_device_message) return; + + + device_group * device_group = &device_groups[device_group_index]; + + + if (device_group->initial_status_requests_remaining) return; + + + + + char * message_ptr = &device_group->message[device_group->message_header_length]; + if (message_type == DGR_MSGTYP_FULL_STATUS) { + + + + building_status_message = true; + + + if (!++outgoing_sequence) outgoing_sequence = 1; +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s full status packet"), device_group->group_name); +#endif + device_group->message_length = 0; + SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); + XdrvMailbox.command_code = DGR_ITEM_STATUS; + XdrvCall(FUNC_DEVICE_GROUP_REQUEST); + building_status_message = false; + + + if (!device_group->message_length) { + if (!--outgoing_sequence) outgoing_sequence = -1; + return; + } + device_group->last_full_status_sequence = outgoing_sequence; + + + *(message_ptr + 2) |= DGR_FLAG_FULL_STATUS; + } + + else { + bool shared; + uint8_t item; + uint32_t value; + uint8_t * value_ptr; + va_list ap; + +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s packet"), device_group->group_name); +#endif + uint16_t original_sequence = outgoing_sequence; + if (!building_status_message && message_type != DGR_MSGTYP_PARTIAL_UPDATE && !++outgoing_sequence) outgoing_sequence = 1; + *message_ptr++ = outgoing_sequence & 0xff; + *message_ptr++ = outgoing_sequence >> 8; + + value = 0; + if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME) + value |= DGR_FLAG_MORE_TO_COME; + else if (message_type == DGR_MSGTYP_UPDATE_DIRECT) + value |= DGR_FLAG_DIRECT; +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">sequence=%u, flags=%u"), outgoing_sequence, value); +#endif + *message_ptr++ = value & 0xff; + *message_ptr++ = value >> 8; + char * first_item_ptr = message_ptr; + + + + + + if (device_group->message_length) { + uint8_t item_array[32]; + int item_index = 0; + int kept_item_count = 0; + + + va_start(ap, message_type); + while ((item = va_arg(ap, int))) { + item_array[item_index++] = item; + if (item <= DGR_ITEM_MAX_32BIT) + va_arg(ap, int); + else if (item <= DGR_ITEM_MAX_STRING) + va_arg(ap, char *); + else { + switch (item) { + case DGR_ITEM_LIGHT_CHANNELS: + va_arg(ap, uint8_t *) ; + break; + } + } + } + va_end(ap); + item_array[item_index] = 0; + + + + char * previous_message_ptr = message_ptr; + while (item = *previous_message_ptr++) { + + + for (item_index = 0; item_array[item_index]; item_index++) { + if (item_array[item_index] == item) break; + } + + + if (item_array[item_index]) { + if (item <= DGR_ITEM_MAX_32BIT) { + previous_message_ptr++; + if (item > DGR_ITEM_MAX_8BIT) { + previous_message_ptr++; + if (item > DGR_ITEM_MAX_16BIT) { + previous_message_ptr++; + previous_message_ptr++; + } + } + } + else if (item <= DGR_ITEM_MAX_STRING) + previous_message_ptr += *previous_message_ptr++; + else { + switch (item) { + case DGR_ITEM_LIGHT_CHANNELS: + previous_message_ptr += 5; + break; + } + } + } + + + else { + *message_ptr++ = item; + if (item <= DGR_ITEM_MAX_32BIT) { + *message_ptr++ = *previous_message_ptr++; + if (item > DGR_ITEM_MAX_8BIT) { + *message_ptr++ = *previous_message_ptr++; + if (item > DGR_ITEM_MAX_16BIT) { + *message_ptr++ = *previous_message_ptr++; + *message_ptr++ = *previous_message_ptr++; + } + } + } + else if (item <= DGR_ITEM_MAX_STRING) { + *message_ptr++ = value = *previous_message_ptr++; + memmove(message_ptr, previous_message_ptr, value); + previous_message_ptr += value; + message_ptr += value; + } + else { + switch (item) { + case DGR_ITEM_LIGHT_CHANNELS: + memmove(message_ptr, previous_message_ptr, 5); + previous_message_ptr += 5; + message_ptr += 5; + break; + } + } + kept_item_count++; + } + } +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count); +#endif + } + + + va_start(ap, message_type); + while ((item = va_arg(ap, int))) { + shared = DeviceGroupItemShared(false, item); + if (shared) *message_ptr++ = item; + if (item <= DGR_ITEM_MAX_32BIT) { + value = va_arg(ap, int); + if (shared) { +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u"), item, value); +#endif + *message_ptr++ = value & 0xff; + if (item > DGR_ITEM_MAX_8BIT) { + value >>= 8; + *message_ptr++ = value & 0xff; + } + if (item > DGR_ITEM_MAX_16BIT) { + value >>= 8; + *message_ptr++ = value & 0xff; + *message_ptr++ = value >> 8; + } + } + } + else if (item <= DGR_ITEM_MAX_STRING) { + value_ptr = va_arg(ap, uint8_t *); + if (shared) { + value = strlen((const char *)value_ptr); +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%s"), item, value_ptr); +#endif + *message_ptr++ = value; + memcpy(message_ptr, value_ptr, value); + message_ptr += value; + } + } + else { + switch (item) { + case DGR_ITEM_LIGHT_CHANNELS: + value_ptr = va_arg(ap, uint8_t *); + if (shared) { +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u,%u,%u,%u,%u"), item, *value_ptr, *(value_ptr + 1), *(value_ptr + 2), *(value_ptr + 3), *(value_ptr + 4)); +#endif + memmove(message_ptr, value_ptr, 5); + message_ptr += 5; + } + break; + } + } + } + va_end(ap); + + + + if (message_ptr == first_item_ptr) { + outgoing_sequence = original_sequence; + return; + } + + + *message_ptr++ = 0; + device_group->message_length = message_ptr - device_group->message; + + + if (building_status_message || message_type == DGR_MSGTYP_PARTIAL_UPDATE) return; + } + + +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via multicast, sequence=%u"), device_group->message_length, device_group->group_name, device_group->message[device_group->message_header_length] | device_group->message[device_group->message_header_length + 1] << 8); +#endif + SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Multicast")); + device_group->next_ack_check_time = millis() + 100; + + if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME) { + for (struct device_group_member * device_group_member = device_group->device_group_members; device_group_member != nullptr; device_group_member = device_group_member->flink) { + device_group_member->acked_sequence = outgoing_sequence; + } + } + else { + waiting_for_acks = true; + } +} + +void ProcessDeviceGroupMessage(char * packet, int packet_length) +{ + + char * message_group_name = packet + sizeof(DEVICE_GROUP_MESSAGE) - 1; + char * message_ptr = strchr(message_group_name, ' '); + if (message_ptr == nullptr) return; + *message_ptr = 0; + + + struct device_group * device_group; + uint32_t device_group_index = 0; + for (;;) { + device_group = &device_groups[device_group_index]; + if (!strcmp(message_group_name, device_group->group_name)) break; + if (++device_group_index >= device_group_count) return; + } + *message_ptr++ = ' '; + + + IPAddress remote_ip = PortUdp.remoteIP(); + struct device_group_member * * flink = &device_group->device_group_members; + struct device_group_member * device_group_member; + for (;;) { + device_group_member = *flink; + if (!device_group_member) { + device_group_member = (struct device_group_member *)calloc(1, sizeof(struct device_group_member)); + if (device_group_member == nullptr) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating device group member block")); + return; + } +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: adding member %s (%p)"), IPAddressToString(remote_ip), device_group_member); +#endif + device_group_member->ip_address = remote_ip; + *flink = device_group_member; + break; + } + else if (device_group_member->ip_address == remote_ip) { + break; + } + flink = &device_group_member->flink; + } + + + message_ptr = strstr_P(message_ptr, PSTR("\n\n")); + if (message_ptr == nullptr) return; + message_ptr += 2; + + bool light_fade; + uint16_t flags; + uint16_t item; + uint16_t message_sequence; + int32_t value; + + + if (packet_length - (message_ptr - packet) < 4) goto badmsg; + message_sequence = *message_ptr++; + message_sequence |= *message_ptr++ << 8; + flags = *message_ptr++; + flags |= *message_ptr++ << 8; +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Received device group %s packet from %s: sequence=%u, flags=%u"), device_group->group_name, IPAddressToString(remote_ip), message_sequence, flags); +#endif + + + + if (flags == DGR_FLAG_ACK) { + if (message_sequence > device_group_member->acked_sequence || device_group_member->acked_sequence - message_sequence < 64536) { + device_group_member->acked_sequence = message_sequence; + } + device_group_member->timeout_time = 0; +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("acked_sequence != device_group->last_full_status_sequence) { + _SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_FULL_STATUS); + } +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("received_sequence && device_group_member->received_sequence - message_sequence > 64536) { +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("received_sequence = message_sequence; + + + + processing_remote_device_message = true; +# 512 "S:/Development/Tasmota/tasmota/support_device_groups.ino" + XdrvMailbox.command = nullptr; + XdrvMailbox.index = flags; + light_fade = Settings.light_fade; + if (flags & (DGR_FLAG_MORE_TO_COME | DGR_FLAG_DIRECT)) Settings.light_fade = false; + + for (;;) { + if (packet_length - (message_ptr - packet) < 1) goto badmsg; + item = *message_ptr++; + if (!item) break; + +#ifdef DEVICE_GROUPS_DEBUG + switch (item) { + case DGR_ITEM_LIGHT_FADE: + case DGR_ITEM_LIGHT_SPEED: + case DGR_ITEM_LIGHT_BRI: + case DGR_ITEM_LIGHT_SCHEME: + case DGR_ITEM_LIGHT_FIXED_COLOR: + case DGR_ITEM_BRI_MIN: + case DGR_ITEM_BRI_PRESET_LOW: + case DGR_ITEM_BRI_PRESET_HIGH: + case DGR_ITEM_BRI_POWER_ON: + case DGR_ITEM_POWER: + case DGR_ITEM_LIGHT_CHANNELS: + break; + default: + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: ********** invalid item=%u received from device group %s member %s"), item, device_group->group_name, IPAddressToString(remote_ip)); + } +#endif + + XdrvMailbox.command_code = item; + if (item <= DGR_ITEM_LAST_32BIT) { + value = *message_ptr++; + if (item > DGR_ITEM_MAX_8BIT) { + value |= *message_ptr++ << 8; + if (item > DGR_ITEM_MAX_16BIT) { + value |= *message_ptr++ << 16; + value |= *message_ptr++ << 24; + } + } + else if (item == DGR_ITEM_LIGHT_FADE) { + light_fade = value; + } +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("= packet_length - (message_ptr - packet)) goto badmsg; +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("message[device_group->message_header_length]; + if (!++outgoing_sequence) outgoing_sequence = 1; + *message_ptr++ = outgoing_sequence & 0xff; + *message_ptr++ = outgoing_sequence >> 8; + *message_ptr++ = DGR_FLAG_RESET | DGR_FLAG_STATUS_REQUEST; + *message_ptr++ = 0; + device_group->message_length = message_ptr - device_group->message; + device_group->initial_status_requests_remaining = 5; + device_group->next_ack_check_time = millis() + 1000; + } + + waiting_for_acks = true; + } + + if (device_groups_initialization_failed) return; + + if (waiting_for_acks) { + uint32_t now = millis(); + waiting_for_acks = false; + for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) { + device_group * device_group = &device_groups[device_group_index]; + if (device_group->next_ack_check_time) { + if (device_group->next_ack_check_time <= now) { + if (device_group->initial_status_requests_remaining) { +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending initial status request for group %s"), device_group->group_name); +#endif + if (--device_group->initial_status_requests_remaining) { + SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Initial")); + device_group->message[device_group->message_header_length + 2] = DGR_FLAG_STATUS_REQUEST; + device_group->next_ack_check_time = now + 200; + waiting_for_acks = true; + } + else { + device_group->next_ack_check_time = 0; + _SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_FULL_STATUS); + } + } + else { + bool acked = true; + struct device_group_member ** flink = &device_group->device_group_members; + struct device_group_member * device_group_member; + while ((device_group_member = *flink)) { + if (device_group_member->acked_sequence != outgoing_sequence) { + + if (device_group_member->timeout_time && device_group_member->timeout_time < now) { +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: removing member %s (%p)"), IPAddressToString(device_group_member->ip_address), device_group_member); +#endif + *flink = device_group_member->flink; + free(device_group_member); + } + else { +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via unicast to %s, sequence %u, last message acked=%u"), device_group->message_length, device_group->group_name, IPAddressToString(device_group_member->ip_address), outgoing_sequence, device_group_member->acked_sequence); +#endif + SendDeviceGroupPacket(device_group_member->ip_address, device_group->message, device_group->message_length, PSTR("Unicast")); + if (!device_group_member->timeout_time) device_group_member->timeout_time = now + 15000; + acked = false; + flink = &device_group_member->flink; + } + } + else { + flink = &device_group_member->flink; + } + } + if (acked) { + device_group->next_ack_check_time = 0; + device_group->message_length = 0; + } + else { + device_group->next_ack_check_time = now + 500; + waiting_for_acks = true; + } + } + } + else { + waiting_for_acks = true; + } + } + } + } + } + else { + udp_was_connected = false; + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/support_esptool.ino" +# 20 "S:/Development/Tasmota/tasmota/support_esptool.ino" +#define USE_ESPTOOL +#ifdef USE_ESPTOOL + + + + + + + +#define READ_REG(REG) (*((volatile uint32_t *)(REG))) +#define WRITE_REG(REG,VAL) *((volatile uint32_t *)(REG)) = (VAL) +#define REG_SET_MASK(reg,mask) WRITE_REG((reg), (READ_REG(reg)|(mask))) + +#define SPI_BASE_REG 0x60000200 + +#define SPI_CMD_REG (SPI_BASE_REG + 0x00) +#define SPI_FLASH_RDSR (1<<27) +#define SPI_FLASH_SE (1<<24) +#define SPI_FLASH_BE (1<<23) +#define SPI_FLASH_WREN (1<<30) + +#define SPI_ADDR_REG (SPI_BASE_REG + 0x04) +#define SPI_CTRL_REG (SPI_BASE_REG + 0x08) +#define SPI_RD_STATUS_REG (SPI_BASE_REG + 0x10) +#define SPI_W0_REG (SPI_BASE_REG + 0x40) +#define SPI_EXT2_REG (SPI_BASE_REG + 0xF8) + +#define SPI_ST 0x7 + + +#define SECTORS_PER_BLOCK (FLASH_BLOCK_SIZE / SPI_FLASH_SEC_SIZE) + + +static const uint32_t STATUS_WIP_BIT = (1 << 0); + + +inline static void spi_wait_ready(void) +{ + while((READ_REG(SPI_EXT2_REG) & SPI_ST)) { } +} + + + +static bool spiflash_is_ready(void) +{ + spi_wait_ready(); + WRITE_REG(SPI_RD_STATUS_REG, 0); + WRITE_REG(SPI_CMD_REG, SPI_FLASH_RDSR); + while(READ_REG(SPI_CMD_REG) != 0) { } + uint32_t status_value = READ_REG(SPI_RD_STATUS_REG); + return (status_value & STATUS_WIP_BIT) == 0; +} + +static void spi_write_enable(void) +{ + while(!spiflash_is_ready()) { } + WRITE_REG(SPI_CMD_REG, SPI_FLASH_WREN); + while(READ_REG(SPI_CMD_REG) != 0) { } +} + +bool EsptoolEraseSector(uint32_t sector) +{ + spi_write_enable(); + spi_wait_ready(); + + WRITE_REG(SPI_ADDR_REG, (sector * SPI_FLASH_SEC_SIZE) & 0xffffff); + WRITE_REG(SPI_CMD_REG, SPI_FLASH_SE); + while(READ_REG(SPI_CMD_REG) != 0) { } + while(!spiflash_is_ready()) { } + + return true; +} + +void EsptoolErase(uint32_t start_sector, uint32_t end_sector) +{ + int next_erase_sector = start_sector; + int remaining_erase_sector = end_sector - start_sector; + + while (remaining_erase_sector > 0) { + spi_write_enable(); + + uint32_t command = SPI_FLASH_SE; + uint32_t sectors_to_erase = 1; + if (remaining_erase_sector >= SECTORS_PER_BLOCK && + next_erase_sector % SECTORS_PER_BLOCK == 0) { + command = SPI_FLASH_BE; + sectors_to_erase = SECTORS_PER_BLOCK; + } + uint32_t addr = next_erase_sector * SPI_FLASH_SEC_SIZE; + + spi_wait_ready(); + WRITE_REG(SPI_ADDR_REG, addr & 0xffffff); + WRITE_REG(SPI_CMD_REG, command); + while(READ_REG(SPI_CMD_REG) != 0) { } + remaining_erase_sector -= sectors_to_erase; + next_erase_sector += sectors_to_erase; + + while (!spiflash_is_ready()) { } + yield(); + OsWatchLoop(); + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/support_features.ino" +# 24 "S:/Development/Tasmota/tasmota/support_features.ino" +void GetFeatures(void) +{ + feature_drv1 = 0x00000000; + +#ifdef USE_ENERGY_MARGIN_DETECTION + feature_drv1 |= 0x00000001; +#endif +#ifdef USE_LIGHT + feature_drv1 |= 0x00000002; +#endif +#ifdef USE_I2C + feature_drv1 |= 0x00000004; +#endif +#ifdef USE_SPI + feature_drv1 |= 0x00000008; +#endif +#ifdef USE_DISCOVERY + feature_drv1 |= 0x00000010; +#endif +#ifdef USE_ARDUINO_OTA + feature_drv1 |= 0x00000020; +#endif +#ifdef USE_MQTT_TLS + feature_drv1 |= 0x00000040; +#endif +#ifdef USE_WEBSERVER + feature_drv1 |= 0x00000080; +#endif +#ifdef WEBSERVER_ADVERTISE + feature_drv1 |= 0x00000100; +#endif +#ifdef USE_EMULATION_HUE + feature_drv1 |= 0x00000200; +#endif +#if (MQTT_LIBRARY_TYPE == MQTT_PUBSUBCLIENT) + feature_drv1 |= 0x00000400; +#endif +#if (MQTT_LIBRARY_TYPE == MQTT_TASMOTAMQTT) + +#endif +#if (MQTT_LIBRARY_TYPE == MQTT_ESPMQTTARDUINO) + +#endif +#ifdef MQTT_HOST_DISCOVERY + feature_drv1 |= 0x00002000; +#endif +#ifdef USE_ARILUX_RF + feature_drv1 |= 0x00004000; +#endif +#if defined(USE_LIGHT) && defined(USE_WS2812) + feature_drv1 |= 0x00008000; +#endif +#ifdef USE_WS2812_DMA + feature_drv1 |= 0x00010000; +#endif +#if defined(USE_IR_REMOTE) || defined(USE_IR_REMOTE_FULL) + feature_drv1 |= 0x00020000; +#endif +#ifdef USE_IR_HVAC + feature_drv1 |= 0x00040000; +#endif +#ifdef USE_IR_RECEIVE + feature_drv1 |= 0x00080000; +#endif +#ifdef USE_DOMOTICZ + feature_drv1 |= 0x00100000; +#endif +#ifdef USE_DISPLAY + feature_drv1 |= 0x00200000; +#endif +#ifdef USE_HOME_ASSISTANT + feature_drv1 |= 0x00400000; +#endif +#ifdef USE_SERIAL_BRIDGE + feature_drv1 |= 0x00800000; +#endif +#ifdef USE_TIMERS + feature_drv1 |= 0x01000000; +#endif +#ifdef USE_SUNRISE + feature_drv1 |= 0x02000000; +#endif +#ifdef USE_TIMERS_WEB + feature_drv1 |= 0x04000000; +#endif +#ifdef USE_RULES + feature_drv1 |= 0x08000000; +#endif +#ifdef USE_KNX + feature_drv1 |= 0x10000000; +#endif +#ifdef USE_WPS + feature_drv1 |= 0x20000000; +#endif +#ifdef USE_SMARTCONFIG + feature_drv1 |= 0x40000000; +#endif +#ifdef USE_ENERGY_POWER_LIMIT + feature_drv1 |= 0x80000000; +#endif + + + + feature_drv2 = 0x00000000; + +#ifdef USE_CONFIG_OVERRIDE + feature_drv2 |= 0x00000001; +#endif +#ifdef FIRMWARE_MINIMAL + feature_drv2 |= 0x00000002; +#endif +#ifdef FIRMWARE_SENSORS + feature_drv2 |= 0x00000004; +#endif +#ifdef FIRMWARE_CLASSIC + feature_drv2 |= 0x00000008; +#endif +#ifdef FIRMWARE_KNX_NO_EMULATION + feature_drv2 |= 0x00000010; +#endif +#ifdef USE_DISPLAY_MODES1TO5 + feature_drv2 |= 0x00000020; +#endif +#ifdef USE_DISPLAY_GRAPH + feature_drv2 |= 0x00000040; +#endif +#ifdef USE_DISPLAY_LCD + feature_drv2 |= 0x00000080; +#endif +#ifdef USE_DISPLAY_SSD1306 + feature_drv2 |= 0x00000100; +#endif +#ifdef USE_DISPLAY_MATRIX + feature_drv2 |= 0x00000200; +#endif +#ifdef USE_DISPLAY_ILI9341 + feature_drv2 |= 0x00000400; +#endif +#ifdef USE_DISPLAY_EPAPER_29 + feature_drv2 |= 0x00000800; +#endif +#ifdef USE_DISPLAY_SH1106 + feature_drv2 |= 0x00001000; +#endif +#ifdef USE_MP3_PLAYER + feature_drv2 |= 0x00002000; +#endif +#ifdef USE_PCA9685 + feature_drv2 |= 0x00004000; +#endif +#if defined(USE_LIGHT) && defined(USE_TUYA_MCU) + feature_drv2 |= 0x00008000; +#endif +#ifdef USE_RC_SWITCH + feature_drv2 |= 0x00010000; +#endif +#if defined(USE_LIGHT) && defined(USE_ARMTRONIX_DIMMERS) + feature_drv2 |= 0x00020000; +#endif +#if defined(USE_LIGHT) && defined(USE_SM16716) + feature_drv2 |= 0x00040000; +#endif +#ifdef USE_SCRIPT + feature_drv2 |= 0x00080000; +#endif +#ifdef USE_EMULATION_WEMO + feature_drv2 |= 0x00100000; +#endif +#ifdef USE_SONOFF_IFAN + feature_drv2 |= 0x00200000; +#endif +#ifdef USE_ZIGBEE + feature_drv2 |= 0x00400000; +#endif +#ifdef NO_EXTRA_4K_HEAP + feature_drv2 |= 0x00800000; +#endif +#ifdef VTABLES_IN_IRAM + feature_drv2 |= 0x01000000; +#endif +#ifdef VTABLES_IN_DRAM + feature_drv2 |= 0x02000000; +#endif +#ifdef VTABLES_IN_FLASH + feature_drv2 |= 0x04000000; +#endif +#ifdef PIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + feature_drv2 |= 0x08000000; +#endif +#ifdef PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY + feature_drv2 |= 0x10000000; +#endif +#ifdef PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH + feature_drv2 |= 0x20000000; +#endif +#ifdef DEBUG_THEO + feature_drv2 |= 0x40000000; +#endif +#ifdef USE_DEBUG_DRIVER + feature_drv2 |= 0x80000000; +#endif + + + + feature_sns1 = 0x00000000; + +#ifdef USE_COUNTER + feature_sns1 |= 0x00000001; +#endif +#ifdef USE_ADC_VCC + feature_sns1 |= 0x00000002; +#endif +#ifdef USE_ENERGY_SENSOR + feature_sns1 |= 0x00000004; +#endif +#ifdef USE_PZEM004T + feature_sns1 |= 0x00000008; +#endif +#ifdef USE_DS18B20 + feature_sns1 |= 0x00000010; +#endif +#ifdef USE_DS18x20_LEGACY + feature_sns1 |= 0x00000020; +#endif +#ifdef USE_DS18x20 + feature_sns1 |= 0x00000040; +#endif +#ifdef USE_DHT + feature_sns1 |= 0x00000080; +#endif +#ifdef USE_SHT + feature_sns1 |= 0x00000100; +#endif +#ifdef USE_HTU + feature_sns1 |= 0x00000200; +#endif +#ifdef USE_BMP + feature_sns1 |= 0x00000400; +#endif +#ifdef USE_BME680 + feature_sns1 |= 0x00000800; +#endif +#ifdef USE_BH1750 + feature_sns1 |= 0x00001000; +#endif +#ifdef USE_VEML6070 + feature_sns1 |= 0x00002000; +#endif +#ifdef USE_ADS1115_I2CDEV + feature_sns1 |= 0x00004000; +#endif +#ifdef USE_ADS1115 + feature_sns1 |= 0x00008000; +#endif +#ifdef USE_INA219 + feature_sns1 |= 0x00010000; +#endif +#ifdef USE_SHT3X + feature_sns1 |= 0x00020000; +#endif +#ifdef USE_MHZ19 + feature_sns1 |= 0x00040000; +#endif +#ifdef USE_TSL2561 + feature_sns1 |= 0x00080000; +#endif +#ifdef USE_SENSEAIR + feature_sns1 |= 0x00100000; +#endif +#ifdef USE_PMS5003 + feature_sns1 |= 0x00200000; +#endif +#ifdef USE_MGS + feature_sns1 |= 0x00400000; +#endif +#ifdef USE_NOVA_SDS + feature_sns1 |= 0x00800000; +#endif +#ifdef USE_SGP30 + feature_sns1 |= 0x01000000; +#endif +#ifdef USE_SR04 + feature_sns1 |= 0x02000000; +#endif +#ifdef USE_SDM120 + feature_sns1 |= 0x04000000; +#endif +#ifdef USE_SI1145 + feature_sns1 |= 0x08000000; +#endif +#ifdef USE_SDM630 + feature_sns1 |= 0x10000000; +#endif +#ifdef USE_LM75AD + feature_sns1 |= 0x20000000; +#endif +#ifdef USE_APDS9960 + feature_sns1 |= 0x40000000; +#endif +#ifdef USE_TM1638 + feature_sns1 |= 0x80000000; +#endif + + + + feature_sns2 = 0x00000000; + +#ifdef USE_MCP230xx + feature_sns2 |= 0x00000001; +#endif +#ifdef USE_MPR121 + feature_sns2 |= 0x00000002; +#endif +#ifdef USE_CCS811 + feature_sns2 |= 0x00000004; +#endif +#ifdef USE_MPU6050 + feature_sns2 |= 0x00000008; +#endif +#ifdef USE_MCP230xx_OUTPUT + feature_sns2 |= 0x00000010; +#endif +#ifdef USE_MCP230xx_DISPLAYOUTPUT + feature_sns2 |= 0x00000020; +#endif +#ifdef USE_HLW8012 + feature_sns2 |= 0x00000040; +#endif +#ifdef USE_CSE7766 + feature_sns2 |= 0x00000080; +#endif +#ifdef USE_MCP39F501 + feature_sns2 |= 0x00000100; +#endif +#ifdef USE_PZEM_AC + feature_sns2 |= 0x00000200; +#endif +#ifdef USE_DS3231 + feature_sns2 |= 0x00000400; +#endif +#ifdef USE_HX711 + feature_sns2 |= 0x00000800; +#endif +#ifdef USE_PZEM_DC + feature_sns2 |= 0x00001000; +#endif +#if defined(USE_TX20_WIND_SENSOR) || defined(USE_TX23_WIND_SENSOR) + feature_sns2 |= 0x00002000; +#endif +#ifdef USE_MGC3130 + feature_sns2 |= 0x00004000; +#endif +#ifdef USE_RF_SENSOR + feature_sns2 |= 0x00008000; +#endif +#ifdef USE_THEO_V2 + feature_sns2 |= 0x00010000; +#endif +#ifdef USE_ALECTO_V2 + feature_sns2 |= 0x00020000; +#endif +#ifdef USE_AZ7798 + feature_sns2 |= 0x00040000; +#endif +#ifdef USE_MAX31855 + feature_sns2 |= 0x00080000; +#endif +#ifdef USE_PN532_HSU + feature_sns2 |= 0x00100000; +#endif +#ifdef USE_MAX44009 + feature_sns2 |= 0x00200000; +#endif +#ifdef USE_SCD30 + feature_sns2 |= 0x00400000; +#endif +#ifdef USE_HRE + feature_sns2 |= 0x00800000; +#endif +#ifdef USE_ADE7953 + feature_sns2 |= 0x01000000; +#endif +#ifdef USE_SPS30 + feature_sns2 |= 0x02000000; +#endif +#ifdef USE_VL53L0X + feature_sns2 |= 0x04000000; +#endif +#ifdef USE_MLX90614 + feature_sns2 |= 0x08000000; +#endif +#ifdef USE_MAX31865 + feature_sns2 |= 0x10000000; +#endif +#ifdef USE_CHIRP + feature_sns2 |= 0x20000000; +#endif +#ifdef USE_SOLAX_X1 + feature_sns2 |= 0x40000000; +#endif +#ifdef USE_PAJ7620 + feature_sns2 |= 0x80000000; +#endif + + + + feature5 = 0x00000000; + +#ifdef USE_BUZZER + feature5 |= 0x00000001; +#endif +#ifdef USE_RDM6300 + feature5 |= 0x00000002; +#endif +#ifdef USE_IBEACON + feature5 |= 0x00000004; +#endif +#ifdef USE_SML_M + feature5 |= 0x00000008; +#endif +#ifdef USE_INA226 + feature5 |= 0x00000010; +#endif +#ifdef USE_A4988_STEPPER + feature5 |= 0x00000020; +#endif +#ifdef USE_DDS2382 + feature5 |= 0x00000040; +#endif +#ifdef USE_SM2135 + feature5 |= 0x00000080; +#endif +#ifdef USE_SHUTTER + feature5 |= 0x00000100; +#endif +#ifdef USE_PCF8574 + feature5 |= 0x00000200; +#endif +#ifdef USE_DDSU666 + feature5 |= 0x00000400; +#endif +#ifdef USE_DEEPSLEEP + feature5 |= 0x00000800; +#endif +#ifdef USE_SONOFF_SC + feature5 |= 0x00001000; +#endif +#ifdef USE_SONOFF_RF + feature5 |= 0x00002000; +#endif +#ifdef USE_SONOFF_L1 + feature5 |= 0x00004000; +#endif +#ifdef USE_EXS_DIMMER + feature5 |= 0x00008000; +#endif +#ifdef USE_ARDUINO_SLAVE + feature5 |= 0x00010000; +#endif +#ifdef USE_HIH6 + feature5 |= 0x00020000; +#endif +#ifdef USE_HPMA + feature5 |= 0x00040000; +#endif +#ifdef USE_TSL2591 + feature5 |= 0x00080000; +#endif +#ifdef USE_DHT12 + feature5 |= 0x00100000; +#endif +#ifdef USE_DS1624 + feature5 |= 0x00200000; +#endif +#ifdef USE_GPS + feature5 |= 0x00400000; +#endif +#ifdef USE_HOTPLUG + feature5 |= 0x00800000; +#endif +#ifdef USE_NRF24 + feature5 |= 0x01000000; +#endif +#ifdef USE_MIBLE + feature5 |= 0x02000000; +#endif +#ifdef USE_HM10 + feature5 |= 0x04000000; +#endif +#ifdef USE_LE01MR + feature5 |= 0x08000000; +#endif +#ifdef USE_AHT1x + feature5 |= 0x10000000; +#endif +#ifdef USE_WEMOS_MOTOR_V1 + feature5 |= 0x20000000; +#endif + + + + + + + feature6 = 0x00000000; +# 570 "S:/Development/Tasmota/tasmota/support_features.ino" +} +# 1 "S:/Development/Tasmota/tasmota/support_flash_log.ino" +# 39 "S:/Development/Tasmota/tasmota/support_flash_log.ino" +#ifdef USE_FLOG + +class FLOG + +#define MAGIC_WORD_FL 0x464c + +{ + +struct header_t{ + uint16_t magic_word; + uint16_t padding; + uint32_t physical_start_sector:10; + uint32_t number:10; + uint32_t buf_pointer:12; + }; + +private: +void _readSector(uint8_t one_sector); +void _eraseSector(uint8_t one_sector); +void _writeSector(uint8_t one_sector); +void _clearBuffer(void); +void _searchSaves(void); +void _findFirstErasedSector(void); +void _showBuffer(void); +void _initBuffer(void); +void _saveBufferToSector(void); +header_t _saved_header; + +public: + uint32_t size; + uint32_t start; + uint32_t end; + uint16_t num_sectors; + + uint16_t first_erased_sector; + uint16_t current_sector; + + uint16_t bytes_left; + uint16_t sectors_left; + + uint8_t mode = 0; + bool found_saved_data = false; + bool ready = false; + bool running_download = false; + bool recording = false; + + union sector_t{ + uint32_t dword_buffer[FLASH_SECTOR_SIZE/4]; + uint8_t byte_buffer[FLASH_SECTOR_SIZE]; + header_t header; + } sector; + + void init(void); + void addToBuffer(uint8_t src[], uint32_t size); + void startRecording(bool append); + void stopRecording(void); + + typedef void (*CallbackNoArgs) (); + typedef void (*CallbackWithArgs) (uint8_t *_record); + + void startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter); +}; + +extern "C" uint32_t _SPIFFS_start; +extern "C" uint32_t _FS_start; + + + + +void FLOG::init(void) +{ +DEBUG_SENSOR_LOG(PSTR("FLOG: init ...")); +size = ESP.getSketchSize(); + +start = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2) +end = (uint32_t)&_SPIFFS_start - 0x40200000; +#else +end = (uint32_t)&_FS_start - 0x40200000; +#endif +num_sectors = (end - start)/FLASH_SECTOR_SIZE; +DEBUG_SENSOR_LOG(PSTR("FLOG: size: 0x%lx, start: 0x%lx, end: 0x%lx, num_sectors(dec): %lu"), size, start, end, num_sectors ); +_findFirstErasedSector(); +if(first_erased_sector == 0xffff){ + _eraseSector(0); + first_erased_sector = 0; +} +_searchSaves(); +_initBuffer(); +ready = true; +} +# 142 "S:/Development/Tasmota/tasmota/support_flash_log.ino" +void FLOG::_readSector(uint8_t one_sector){ + DEBUG_SENSOR_LOG(PSTR("FLOG: read sector number: %u" ), one_sector); + ESP.flashRead(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)§or.dword_buffer, FLASH_SECTOR_SIZE); +} + + + + + +void FLOG::_eraseSector(uint8_t one_sector){ + DEBUG_SENSOR_LOG(PSTR("FLOG: erasing sector number: %u" ), one_sector); + ESP.flashEraseSector((start/FLASH_SECTOR_SIZE)+one_sector); +} + + + + + +void FLOG::_writeSector(uint8_t one_sector){ + DEBUG_SENSOR_LOG(PSTR("FLOG: write buffer to sector number: %u" ), one_sector); + ESP.flashWrite(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)§or.dword_buffer, FLASH_SECTOR_SIZE); +} + + + + +void FLOG::_clearBuffer(){ + for (uint32_t i = sizeof(sector.header)/4; i<(sizeof(sector.dword_buffer)/4); i++){ + sector.dword_buffer[i] = 0; + } + sector.header.buf_pointer = sizeof(sector.header); + +} + + + + +void FLOG::_saveBufferToSector(){ + DEBUG_SENSOR_LOG(PSTR("FLOG: write buffer to current sector: %u" ),current_sector); + _writeSector(current_sector); + if(current_sector == num_sectors){ + current_sector = 0; + } + else{ + current_sector++; + } + _eraseSector(current_sector); + _clearBuffer(); + sector.header.number++; + DEBUG_SENSOR_LOG(PSTR("FLOG: new sector header number: %u" ),sector.header.number); +} + + + + + +void FLOG::_findFirstErasedSector(){ + for (uint32_t i = 0; i3){ + break; + } + } +} + + + + + + + +void FLOG::addToBuffer(uint8_t src[], uint32_t size){ + if(mode == 0){ + if(sector.header.number == num_sectors && !ready){ + return; + } + } + if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){ + + + + memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size); + sector.header.buf_pointer+=size; + + } + else{ + DEBUG_SENSOR_LOG(PSTR("FLOG: save buffer to flash sector: %u"), current_sector); + DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u"), sector.header.buf_pointer); + _saveBufferToSector(); + sectors_left++; + + if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){ + memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size); + sector.header.buf_pointer+=size; + } + } +} + + + + + + +void FLOG::startRecording(bool append){ + if(recording){ + DEBUG_SENSOR_LOG(PSTR("FLOG: already recording")); + return; + } + recording = true; + DEBUG_SENSOR_LOG(PSTR("FLOG: start recording")); + _initBuffer(); + if(!found_saved_data) { + append = false; + } + if(append){ + sector.header.number = _saved_header.number+1; + sector.header.physical_start_sector = _saved_header.physical_start_sector; + } + else{ + sector.header.physical_start_sector = (uint16_t)first_erased_sector; + found_saved_data = false; + sectors_left = 0; + } + } + + + + + +void FLOG::stopRecording(void){ + _saveBufferToSector(); + _findFirstErasedSector(); + _searchSaves(); + _initBuffer(); + recording = false; + found_saved_data = true; + } +# 381 "S:/Development/Tasmota/tasmota/support_flash_log.ino" + void FLOG::startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter){ + + _readSector(sector.header.physical_start_sector); + uint32_t next_sector = sector.header.physical_start_sector; + bytes_left = sector.header.buf_pointer - sizeof(sector.header); + DEBUG_SENSOR_LOG(PSTR("FLOG: create file for download, will process %u bytes"), bytes_left); + running_download = true; + + sendHeader(); + + while(sectors_left){ + DEBUG_SENSOR_LOG(PSTR("FLOG: next sector: %u"), next_sector); + + uint32_t k = sizeof(sector.header); + while(bytes_left){ + + uint8_t *_record_start = (uint8_t*)§or.byte_buffer[k]; + + sendRecord(_record_start); + if(k%128 == 0){ + + OsWatchLoop(); + delay(sleep); + } + k+=size; + if(bytes_left>7){ + bytes_left-=size; + } + else{ + bytes_left = 0; + DEBUG_SENSOR_LOG(PSTR("FLOG: Flog->bytes_left not dividable by 8 ??????")); + } + } + next_sector++; + if(next_sector>num_sectors){ + next_sector = 0; + } + sectors_left--; + _readSector(next_sector); + bytes_left = sector.header.buf_pointer - sizeof(sector.header); + OsWatchLoop(); + delay(sleep); + } + running_download = false; + + sendFooter(); + + _searchSaves(); + _initBuffer(); + } + + #endif +# 1 "S:/Development/Tasmota/tasmota/support_float.ino" +# 23 "S:/Development/Tasmota/tasmota/support_float.ino" +float fmodf(float x, float y) +{ + + union {float f; uint32_t i;} ux = {x}, uy = {y}; + int ex = ux.i>>23 & 0xff; + int ey = uy.i>>23 & 0xff; + uint32_t sx = ux.i & 0x80000000; + uint32_t i; + uint32_t uxi = ux.i; + + if (uy.i<<1 == 0 || isnan(y) || ex == 0xff) + return (x*y)/(x*y); + if (uxi<<1 <= uy.i<<1) { + if (uxi<<1 == uy.i<<1) + return 0*x; + return x; + } + + + if (!ex) { + for (i = uxi<<9; i>>31 == 0; ex--, i <<= 1); + uxi <<= -ex + 1; + } else { + uxi &= -1U >> 9; + uxi |= 1U << 23; + } + if (!ey) { + for (i = uy.i<<9; i>>31 == 0; ey--, i <<= 1); + uy.i <<= -ey + 1; + } else { + uy.i &= -1U >> 9; + uy.i |= 1U << 23; + } + + + for (; ex > ey; ex--) { + i = uxi - uy.i; + if (i >> 31 == 0) { + if (i == 0) + return 0*x; + uxi = i; + } + uxi <<= 1; + } + i = uxi - uy.i; + if (i >> 31 == 0) { + if (i == 0) + return 0*x; + uxi = i; + } + for (; uxi>>23 == 0; uxi <<= 1, ex--); + + + if (ex > 0) { + uxi -= 1U << 23; + uxi |= (uint32_t)ex << 23; + } else { + uxi >>= -ex + 1; + } + uxi |= sx; + ux.i = uxi; + return ux.f; +} + + +double FastPrecisePow(double a, double b) +{ + + + int e = abs((int)b); + union { + double d; + int x[2]; + } u = { a }; + u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447); + u.x[0] = 0; + + + double r = 1.0; + while (e) { + if (e & 1) { + r *= a; + } + a *= a; + e >>= 1; + } + return r * u.d; +} + +float FastPrecisePowf(const float x, const float y) +{ + + return (float)FastPrecisePow(x, y); +} + +double TaylorLog(double x) +{ + + + if (x <= 0.0) { return NAN; } + double z = (x + 1) / (x - 1); + double step = ((x - 1) * (x - 1)) / ((x + 1) * (x + 1)); + double totalValue = 0; + double powe = 1; + for (uint32_t count = 0; count < 10; count++) { + z *= step; + double y = (1 / powe) * z; + totalValue = totalValue + y; + powe = powe + 2; + } + totalValue *= 2; +# 144 "S:/Development/Tasmota/tasmota/support_float.ino" + return totalValue; +} +# 154 "S:/Development/Tasmota/tasmota/support_float.ino" +inline float sinf(float x) { return sin_52(x); } +inline float cosf(float x) { return cos_52(x); } +inline float tanf(float x) { return tan_56(x); } +inline float atanf(float x) { return atan_66(x); } +inline float asinf(float x) { return asinf1(x); } +inline float acosf(float x) { return acosf1(x); } +inline float sqrtf(float x) { return sqrt1(x); } +inline float powf(float x, float y) { return FastPrecisePow(x, y); } + + +double const f_pi = 3.1415926535897932384626433; +double const f_twopi = 2.0 * f_pi; +double const f_two_over_pi = 2.0 / f_pi; +double const f_halfpi = f_pi / 2.0; +double const f_threehalfpi = 3.0 * f_pi / 2.0; +double const f_four_over_pi = 4.0 / f_pi; +double const f_qtrpi = f_pi / 4.0; +double const f_sixthpi = f_pi / 6.0; +double const f_tansixthpi = tan(f_sixthpi); +double const f_twelfthpi = f_pi / 12.0; +double const f_tantwelfthpi = tan(f_twelfthpi); +float const f_180pi = 180 / f_pi; +# 194 "S:/Development/Tasmota/tasmota/support_float.ino" +float cos_52s(float x) +{ + const float c1 = 0.9999932946; + const float c2 = -0.4999124376; + const float c3 = 0.0414877472; + const float c4 = -0.0012712095; + + float x2 = x * x; + return (c1 + x2 * (c2 + x2 * (c3 + c4 * x2))); +} + + + + + + +float cos_52(float x) +{ + x = fmodf(x, f_twopi); + if (x < 0) { x = -x; } + int quad = int(x * (float)f_two_over_pi); + switch (quad) { + case 0: return cos_52s(x); + case 1: return -cos_52s((float)f_pi - x); + case 2: return -cos_52s(x-(float)f_pi); + case 3: return cos_52s((float)f_twopi - x); + } +} + + + + +float sin_52(float x) +{ + return cos_52((float)f_halfpi - x); +} +# 247 "S:/Development/Tasmota/tasmota/support_float.ino" +float tan_56s(float x) +{ + const float c1 = -3.16783027; + const float c2 = 0.134516124; + const float c3 = -4.033321984; + + float x2 = x * x; + return (x * (c1 + c2 * x2) / (c3 + x2)); +} +# 267 "S:/Development/Tasmota/tasmota/support_float.ino" +float tan_56(float x) +{ + x = fmodf(x, (float)f_twopi); + int octant = int(x * (float)f_four_over_pi); + switch (octant){ + case 0: return tan_56s(x * (float)f_four_over_pi); + case 1: return 1.0f / tan_56s(((float)f_halfpi - x) * (float)f_four_over_pi); + case 2: return -1.0f / tan_56s((x-(float)f_halfpi) * (float)f_four_over_pi); + case 3: return - tan_56s(((float)f_pi - x) * (float)f_four_over_pi); + case 4: return tan_56s((x-(float)f_pi) * (float)f_four_over_pi); + case 5: return 1.0f / tan_56s(((float)f_threehalfpi - x) * (float)f_four_over_pi); + case 6: return -1.0f / tan_56s((x-(float)f_threehalfpi) * (float)f_four_over_pi); + case 7: return - tan_56s(((float)f_twopi - x) * (float)f_four_over_pi); + } +} +# 296 "S:/Development/Tasmota/tasmota/support_float.ino" +float atan_66s(float x) +{ + const float c1 = 1.6867629106; + const float c2 = 0.4378497304; + const float c3 = 1.6867633134; + + float x2 = x * x; + return (x * (c1 + x2 * c2) / (c3 + x2)); +} + + + + + +float atan_66(float x) +{ + float y; + bool complement= false; + bool region= false; + bool sign= false; + + if (x < 0) { + x = -x; + sign = true; + } + if (x > 1.0) { + x = 1.0 / x; + complement = true; + } + if (x > (float)f_tantwelfthpi) { + x = (x - (float)f_tansixthpi) / (1 + (float)f_tansixthpi * x); + region = true; + } + + y = atan_66s(x); + if (region) { y += (float)f_sixthpi; } + if (complement) { y = (float)f_halfpi-y; } + if (sign) { y = -y; } + return (y); +} + +float asinf1(float x) +{ + float d = 1.0f - x * x; + if (d < 0.0f) { return NAN; } + return 2 * atan_66(x / (1 + sqrt1(d))); +} + +float acosf1(float x) +{ + float d = 1.0f - x * x; + if (d < 0.0f) { return NAN; } + float y = asinf1(sqrt1(d)); + if (x >= 0.0f) { + return y; + } else { + return (float)f_pi - y; + } +} + + +float sqrt1(const float x) +{ + union { + int i; + float x; + } u; + u.x = x; + u.i = (1 << 29) + (u.i >> 1) - (1 << 22); + + + + + u.x = u.x + x / u.x; + u.x = 0.25f * u.x + x / u.x; + + return u.x; +} +# 387 "S:/Development/Tasmota/tasmota/support_float.ino" +uint16_t changeUIntScale(uint16_t inum, uint16_t ifrom_min, uint16_t ifrom_max, + uint16_t ito_min, uint16_t ito_max) { + + if (ifrom_min >= ifrom_max) { + if (ito_min > ito_max) { + return ito_max; + } else { + return ito_min; + } + } + + uint32_t num = inum; + uint32_t from_min = ifrom_min; + uint32_t from_max = ifrom_max; + uint32_t to_min = ito_min; + uint32_t to_max = ito_max; + + + num = (num > from_max ? from_max : (num < from_min ? from_min : num)); + + + if (to_min > to_max) { + + num = (from_max - num) + from_min; + to_min = ito_max; + to_max = ito_min; + } + + uint32_t numerator = (num - from_min) * (to_max - to_min); + uint32_t result; + if (numerator >= 0x80000000L) { + + result = numerator / (from_max - from_min) + to_min; + } else { + result = (((numerator * 2) / (from_max - from_min)) + 1) / 2 + to_min; + } + return (uint32_t) (result > to_max ? to_max : (result < to_min ? to_min : result)); +} +# 1 "S:/Development/Tasmota/tasmota/support_legacy_cores.ino" +# 20 "S:/Development/Tasmota/tasmota/support_legacy_cores.ino" +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + + + + + +void* memchr(const void* ptr, int value, size_t num) +{ + unsigned char *p = (unsigned char*)ptr; + while (num--) { + if (*p != (unsigned char)value) { + p++; + } else { + return p; + } + } + return 0; +} + + + +size_t strcspn(const char *str1, const char *str2) +{ + size_t ret = 0; + while (*str1) { + if (strchr(str2, *str1)) { + return ret; + } else { + str1++; + ret++; + } + } + return ret; +} + + + +char* strpbrk(const char *s1, const char *s2) +{ + while(*s1) { + if (strchr(s2, *s1++)) { + return (char*)--s1; + } + } + return 0; +} + + + +#ifndef __LONG_LONG_MAX__ +#define __LONG_LONG_MAX__ 9223372036854775807LL +#endif +#ifndef ULLONG_MAX +#define ULLONG_MAX (__LONG_LONG_MAX__ * 2ULL + 1) +#endif + +unsigned long long strtoull(const char *__restrict nptr, char **__restrict endptr, int base) +{ + const char *s = nptr; + char c; + do { c = *s++; } while (isspace((unsigned char)c)); + + int neg = 0; + if (c == '-') { + neg = 1; + c = *s++; + } else { + if (c == '+') { + c = *s++; + } + } + + if ((base == 0 || base == 16) && (c == '0') && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) { base = (c == '0') ? 8 : 10; } + + unsigned long long acc = 0; + int any = 0; + if (base > 1 && base < 37) { + unsigned long long cutoff = ULLONG_MAX / base; + int cutlim = ULLONG_MAX % base; + for ( ; ; c = *s++) { + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'A' && c <= 'Z') + c -= 'A' - 10; + else if (c >= 'a' && c <= 'z') + c -= 'a' - 10; + else + break; + + if (c >= base) + break; + + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= base; + acc += c; + } + } + if (any < 0) { + acc = ULLONG_MAX; + } + else if (any && neg) { + acc = -acc; + } + } + + if (endptr != nullptr) { *endptr = (char *)(any ? s - 1 : nptr); } + + return acc; +} + +#endif + + + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2) + + + + + +void* memmove_P(void *dest, const void *src, size_t n) +{ + if (src > (void*)0x40000000) { + return memcpy_P(dest, src, n); + } else { + return memmove(dest, src, n); + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/support_rotary.ino" +# 20 "S:/Development/Tasmota/tasmota/support_rotary.ino" +#ifdef USE_LIGHT + +#ifdef ROTARY_V1 + + + + +struct ROTARY { + unsigned long debounce = 0; + uint8_t present = 0; + uint8_t state = 0; + uint8_t position = 128; + uint8_t last_position = 128; + uint8_t interrupts_in_use_count = 0; + uint8_t changed = 0; +} Rotary; + + + +#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +void update_rotary(void) ICACHE_RAM_ATTR; +#endif + +void update_rotary(void) +{ + if (MI_DESK_LAMP == my_module_type) { + if (LightPowerIRAM()) { + + + + + uint8_t s = Rotary.state & 3; + if (digitalRead(pin[GPIO_ROT1A])) { s |= 4; } + if (digitalRead(pin[GPIO_ROT1B])) { s |= 8; } + switch (s) { + case 0: case 5: case 10: case 15: + break; + case 1: case 7: case 8: case 14: + Rotary.position++; break; + case 2: case 4: case 11: case 13: + Rotary.position--; break; + case 3: case 12: + Rotary.position = Rotary.position + 2; break; + default: + Rotary.position = Rotary.position - 2; break; + } + Rotary.state = (s >> 2); + } + } +} + +bool RotaryButtonPressed(void) +{ + if ((MI_DESK_LAMP == my_module_type) && (Rotary.changed) && LightPower()) { + Rotary.changed = 0; + return true; + } + return false; +} + +void RotaryInit(void) +{ + Rotary.present = 0; + if ((pin[GPIO_ROT1A] < 99) && (pin[GPIO_ROT1B] < 99)) { + Rotary.present++; + pinMode(pin[GPIO_ROT1A], INPUT_PULLUP); + pinMode(pin[GPIO_ROT1B], INPUT_PULLUP); + + + + + if ((pin[GPIO_ROT1A] < 6) || (pin[GPIO_ROT1A] > 11)) { + attachInterrupt(digitalPinToInterrupt(pin[GPIO_ROT1A]), update_rotary, CHANGE); + Rotary.interrupts_in_use_count++; + } + if ((pin[GPIO_ROT1B] < 6) || (pin[GPIO_ROT1B] > 11)) { + attachInterrupt(digitalPinToInterrupt(pin[GPIO_ROT1B]), update_rotary, CHANGE); + Rotary.interrupts_in_use_count++; + } + } +} + + + + + +void RotaryHandler(void) +{ + if (Rotary.interrupts_in_use_count < 2) { + noInterrupts(); + update_rotary(); + } else { + noInterrupts(); + } + if (Rotary.last_position != Rotary.position) { + if (MI_DESK_LAMP == my_module_type) { + if (Button.hold_timer[0]) { + Rotary.changed = 1; + + int16_t t = LightGetColorTemp(); + t = t + (Rotary.position - Rotary.last_position); + if (t < 153) { + t = 153; + } + if (t > 500) { + t = 500; + } + DEBUG_CORE_LOG(PSTR("ROT: " D_CMND_COLORTEMPERATURE " %d"), Rotary.position - Rotary.last_position); + LightSetColorTemp((uint16_t)t); + } else { + int8_t d = Settings.light_dimmer; + d = d + (Rotary.position - Rotary.last_position); + if (d < 1) { + d = 1; + } + if (d > 100) { + d = 100; + } + DEBUG_CORE_LOG(PSTR("ROT: " D_CMND_DIMMER " %d"), Rotary.position - Rotary.last_position); + + LightSetDimmer((uint8_t)d); + Settings.light_dimmer = d; + } + } + Rotary.last_position = 128; + Rotary.position = 128; + } + interrupts(); +} + +void RotaryLoop(void) +{ + if (Rotary.present) { + if (TimeReached(Rotary.debounce)) { + SetNextTimeInterval(Rotary.debounce, Settings.button_debounce); + RotaryHandler(); + } + } +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/support_rtc.ino" +# 25 "S:/Development/Tasmota/tasmota/support_rtc.ino" +const uint32_t SECS_PER_MIN = 60UL; +const uint32_t SECS_PER_HOUR = 3600UL; +const uint32_t SECS_PER_DAY = SECS_PER_HOUR * 24UL; +const uint32_t MINS_PER_HOUR = 60UL; + +#define LEAP_YEAR(Y) (((1970+Y)>0) && !((1970+Y)%4) && (((1970+Y)%100) || !((1970+Y)%400))) + +extern "C" { +#include "sntp.h" +} +#include + +Ticker TickerRtc; + +static const uint8_t kDaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; +static const char kMonthNamesEnglish[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; + +struct RTC { + uint32_t utc_time = 0; + uint32_t local_time = 0; + uint32_t daylight_saving_time = 0; + uint32_t standard_time = 0; + uint32_t ntp_time = 0; + uint32_t midnight = 0; + uint32_t restart_time = 0; + int32_t time_timezone = 0; + uint8_t ntp_sync_minute = 0; + bool midnight_now = false; + bool user_time_entry = false; +} Rtc; + +uint32_t UtcTime(void) +{ + return Rtc.utc_time; +} + +uint32_t LocalTime(void) +{ + return Rtc.local_time; +} + +uint32_t Midnight(void) +{ + return Rtc.midnight; +} + +bool MidnightNow(void) +{ + if (Rtc.midnight_now) { + Rtc.midnight_now = false; + return true; + } + return false; +} + +bool IsDst(void) +{ + if (Rtc.time_timezone == Settings.toffset[1]) { + return true; + } + return false; +} + +String GetBuildDateAndTime(void) +{ + + char bdt[21]; + char *p; + char mdate[] = __DATE__; + char *smonth = mdate; + int day = 0; + int year = 0; + + + uint8_t i = 0; + for (char *str = strtok_r(mdate, " ", &p); str && i < 3; str = strtok_r(nullptr, " ", &p)) { + switch (i++) { + case 0: + smonth = str; + break; + case 1: + day = atoi(str); + break; + case 2: + year = atoi(str); + } + } + int month = (strstr(kMonthNamesEnglish, smonth) -kMonthNamesEnglish) /3 +1; + snprintf_P(bdt, sizeof(bdt), PSTR("%d" D_YEAR_MONTH_SEPARATOR "%02d" D_MONTH_DAY_SEPARATOR "%02d" D_DATE_TIME_SEPARATOR "%s"), year, month, day, __TIME__); + return String(bdt); +} + +String GetMinuteTime(uint32_t minutes) +{ + char tm[6]; + snprintf_P(tm, sizeof(tm), PSTR("%02d:%02d"), minutes / 60, minutes % 60); + + return String(tm); +} + +String GetTimeZone(void) +{ + char tz[7]; + snprintf_P(tz, sizeof(tz), PSTR("%+03d:%02d"), Rtc.time_timezone / 60, abs(Rtc.time_timezone % 60)); + + return String(tz); +} + +String GetDuration(uint32_t time) +{ + char dt[16]; + + TIME_T ut; + BreakTime(time, ut); + + + + + + + snprintf_P(dt, sizeof(dt), PSTR("%dT%02d:%02d:%02d"), ut.days, ut.hour, ut.minute, ut.second); + + return String(dt); +} + +String GetDT(uint32_t time) +{ + + + char dt[20]; + TIME_T tmpTime; + + BreakTime(time, tmpTime); + snprintf_P(dt, sizeof(dt), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), + tmpTime.year +1970, tmpTime.month, tmpTime.day_of_month, tmpTime.hour, tmpTime.minute, tmpTime.second); + + return String(dt); +} +# 175 "S:/Development/Tasmota/tasmota/support_rtc.ino" +String GetDateAndTime(uint8_t time_type) +{ + + uint32_t time = Rtc.local_time; + + switch (time_type) { + case DT_UTC: + time = Rtc.utc_time; + break; + + + + case DT_DST: + time = Rtc.daylight_saving_time; + break; + case DT_STD: + time = Rtc.standard_time; + break; + case DT_RESTART: + if (Rtc.restart_time == 0) { + return ""; + } + time = Rtc.restart_time; + break; + case DT_ENERGY: + time = Settings.energy_kWhtotal_time; + break; + case DT_BOOTCOUNT: + time = Settings.bootcount_reset_time; + break; + } + String dt = GetDT(time); + if (Settings.flag3.time_append_timezone && (DT_LOCAL == time_type)) { + dt += GetTimeZone(); + } + return dt; +} + +uint32_t UpTime(void) +{ + if (Rtc.restart_time) { + return Rtc.utc_time - Rtc.restart_time; + } else { + return uptime; + } +} + +uint32_t MinutesUptime(void) +{ + return (UpTime() / 60); +} + +String GetUptime(void) +{ + return GetDuration(UpTime()); +} + +uint32_t MinutesPastMidnight(void) +{ + uint32_t minutes = 0; + + if (RtcTime.valid) { + minutes = (RtcTime.hour *60) + RtcTime.minute; + } + return minutes; +} + +void BreakTime(uint32_t time_input, TIME_T &tm) +{ + + + + + uint8_t year; + uint8_t month; + uint8_t month_length; + uint32_t time; + unsigned long days; + + time = time_input; + tm.second = time % 60; + time /= 60; + tm.minute = time % 60; + time /= 60; + tm.hour = time % 24; + time /= 24; + tm.days = time; + tm.day_of_week = ((time + 4) % 7) + 1; + + year = 0; + days = 0; + while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { + year++; + } + tm.year = year; + + days -= LEAP_YEAR(year) ? 366 : 365; + time -= days; + tm.day_of_year = time; + + for (month = 0; month < 12; month++) { + if (1 == month) { + if (LEAP_YEAR(year)) { + month_length = 29; + } else { + month_length = 28; + } + } else { + month_length = kDaysInMonth[month]; + } + + if (time >= month_length) { + time -= month_length; + } else { + break; + } + } + strlcpy(tm.name_of_month, kMonthNames + (month *3), 4); + tm.month = month + 1; + tm.day_of_month = time + 1; + tm.valid = (time_input > START_VALID_TIME); +} + +uint32_t MakeTime(TIME_T &tm) +{ + + + + int i; + uint32_t seconds; + + + seconds = tm.year * (SECS_PER_DAY * 365); + for (i = 0; i < tm.year; i++) { + if (LEAP_YEAR(i)) { + seconds += SECS_PER_DAY; + } + } + + + for (i = 1; i < tm.month; i++) { + if ((2 == i) && LEAP_YEAR(tm.year)) { + seconds += SECS_PER_DAY * 29; + } else { + seconds += SECS_PER_DAY * kDaysInMonth[i-1]; + } + } + seconds+= (tm.day_of_month - 1) * SECS_PER_DAY; + seconds+= tm.hour * SECS_PER_HOUR; + seconds+= tm.minute * SECS_PER_MIN; + seconds+= tm.second; + return seconds; +} + +uint32_t RuleToTime(TimeRule r, int yr) +{ + TIME_T tm; + uint32_t t; + uint8_t m; + uint8_t w; + + m = r.month; + w = r.week; + if (0 == w) { + if (++m > 12) { + m = 1; + yr++; + } + w = 1; + } + + tm.hour = r.hour; + tm.minute = 0; + tm.second = 0; + tm.day_of_month = 1; + tm.month = m; + tm.year = yr - 1970; + t = MakeTime(tm); + BreakTime(t, tm); + t += (7 * (w - 1) + (r.dow - tm.day_of_week + 7) % 7) * SECS_PER_DAY; + if (0 == r.week) { + t -= 7 * SECS_PER_DAY; + } + return t; +} + +void RtcSecond(void) +{ + TIME_T tmpTime; + + if (!Rtc.user_time_entry && !global_state.wifi_down) { + uint8_t uptime_minute = (uptime / 60) % 60; + if ((Rtc.ntp_sync_minute > 59) && (uptime_minute > 2)) { + Rtc.ntp_sync_minute = 1; + } + uint8_t offset = (uptime < 30) ? RtcTime.second : (((ESP.getChipId() & 0xF) * 3) + 3) ; + if ( (((offset == RtcTime.second) && ( (RtcTime.year < 2016) || + (Rtc.ntp_sync_minute == uptime_minute))) || + ntp_force_sync ) ) { + Rtc.ntp_time = sntp_get_current_timestamp(); + if (Rtc.ntp_time > START_VALID_TIME) { + ntp_force_sync = false; + Rtc.utc_time = Rtc.ntp_time; + Rtc.ntp_sync_minute = 60; + if (Rtc.restart_time == 0) { + Rtc.restart_time = Rtc.utc_time - uptime; + } + BreakTime(Rtc.utc_time, tmpTime); + RtcTime.year = tmpTime.year + 1970; + Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year); + Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year); + + + PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: " D_UTC_TIME " %s, " D_DST_TIME " %s, " D_STD_TIME " %s"), + GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str()); + + if (Rtc.local_time < START_VALID_TIME) { + rules_flag.time_init = 1; + } else { + rules_flag.time_set = 1; + } + } else { + Rtc.ntp_sync_minute++; + } + } + } + + Rtc.utc_time++; + Rtc.local_time = Rtc.utc_time; + if (Rtc.local_time > START_VALID_TIME) { + int16_t timezone_minutes = Settings.timezone_minutes; + if (Settings.timezone < 0) { timezone_minutes *= -1; } + Rtc.time_timezone = (Settings.timezone * SECS_PER_HOUR) + (timezone_minutes * SECS_PER_MIN); + if (99 == Settings.timezone) { + int32_t dstoffset = Settings.toffset[1] * SECS_PER_MIN; + int32_t stdoffset = Settings.toffset[0] * SECS_PER_MIN; + if (Settings.tflag[1].hemis) { + + if ((Rtc.utc_time >= (Rtc.standard_time - dstoffset)) && (Rtc.utc_time < (Rtc.daylight_saving_time - stdoffset))) { + Rtc.time_timezone = stdoffset; + } else { + Rtc.time_timezone = dstoffset; + } + } else { + + if ((Rtc.utc_time >= (Rtc.daylight_saving_time - stdoffset)) && (Rtc.utc_time < (Rtc.standard_time - dstoffset))) { + Rtc.time_timezone = dstoffset; + } else { + Rtc.time_timezone = stdoffset; + } + } + } + Rtc.local_time += Rtc.time_timezone; + Rtc.time_timezone /= 60; + if (!Settings.energy_kWhtotal_time) { + Settings.energy_kWhtotal_time = Rtc.local_time; + } + if (Settings.bootcount_reset_time < START_VALID_TIME) { + Settings.bootcount_reset_time = Rtc.local_time; + } + } + + BreakTime(Rtc.local_time, RtcTime); + if (RtcTime.valid) { + if (!Rtc.midnight) { + Rtc.midnight = Rtc.local_time - (RtcTime.hour * 3600) - (RtcTime.minute * 60) - RtcTime.second; + } + if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second) { + Rtc.midnight = Rtc.local_time; + Rtc.midnight_now = true; + } + } + + RtcTime.year += 1970; +} + +void RtcSetTime(uint32_t epoch) +{ + if (epoch < START_VALID_TIME) { + Rtc.user_time_entry = false; + ntp_force_sync = true; + sntp_init(); + } else { + sntp_stop(); + Rtc.user_time_entry = true; + Rtc.utc_time = epoch -1; + } + RtcSecond(); +} + +void RtcInit(void) +{ + sntp_setservername(0, SettingsText(SET_NTPSERVER1)); + sntp_setservername(1, SettingsText(SET_NTPSERVER2)); + sntp_setservername(2, SettingsText(SET_NTPSERVER3)); + sntp_stop(); + sntp_set_timezone(0); + sntp_init(); + Rtc.utc_time = 0; + BreakTime(Rtc.utc_time, RtcTime); + TickerRtc.attach(1, RtcSecond); +} +# 1 "S:/Development/Tasmota/tasmota/support_static_buffer.ino" +# 20 "S:/Development/Tasmota/tasmota/support_static_buffer.ino" +typedef struct SBuffer_impl { + uint16_t size; + uint16_t len; + uint8_t buf[]; +} SBuffer_impl; + + + +typedef class SBuffer { + +protected: + SBuffer(void) { + + } + +public: + SBuffer(const size_t size) { + _buf = (SBuffer_impl*) new char[size+4]; + _buf->size = size; + _buf->len = 0; + + } + + inline size_t getSize(void) const { return _buf->size; } + inline size_t size(void) const { return _buf->size; } + inline size_t getLen(void) const { return _buf->len; } + inline size_t len(void) const { return _buf->len; } + inline uint8_t *getBuffer(void) const { return _buf->buf; } + inline uint8_t *buf(size_t i = 0) const { return &_buf->buf[i]; } + inline char *charptr(size_t i = 0) const { return (char*) &_buf->buf[i]; } + + virtual ~SBuffer(void) { + delete[] _buf; + } + + inline void setLen(const size_t len) { + uint16_t old_len = _buf->len; + _buf->len = (len <= _buf->size) ? len : _buf->size; + if (old_len < _buf->len) { + memset((void*) &_buf->buf[old_len], 0, _buf->len - old_len); + } + } + + void set8(const size_t offset, const uint8_t data) { + if (offset < _buf->len) { + _buf->buf[offset] = data; + } + } + + size_t add8(const uint8_t data) { + if (_buf->len < _buf->size) { + _buf->buf[_buf->len++] = data; + } + return _buf->len; + } + size_t add16(const uint16_t data) { + if (_buf->len < _buf->size - 1) { + _buf->buf[_buf->len++] = data; + _buf->buf[_buf->len++] = data >> 8; + } + return _buf->len; + } + size_t add32(const uint32_t data) { + if (_buf->len < _buf->size - 3) { + _buf->buf[_buf->len++] = data; + _buf->buf[_buf->len++] = data >> 8; + _buf->buf[_buf->len++] = data >> 16; + _buf->buf[_buf->len++] = data >> 24; + } + return _buf->len; + } + size_t add64(const uint64_t data) { + if (_buf->len < _buf->size - 7) { + _buf->buf[_buf->len++] = data; + _buf->buf[_buf->len++] = data >> 8; + _buf->buf[_buf->len++] = data >> 16; + _buf->buf[_buf->len++] = data >> 24; + _buf->buf[_buf->len++] = data >> 32; + _buf->buf[_buf->len++] = data >> 40; + _buf->buf[_buf->len++] = data >> 48; + _buf->buf[_buf->len++] = data >> 56; + } + return _buf->len; + } + + size_t addBuffer(const SBuffer &buf2) { + if (len() + buf2.len() <= size()) { + for (uint32_t i = 0; i < buf2.len(); i++) { + _buf->buf[_buf->len++] = buf2.buf()[i]; + } + } + return _buf->len; + } + + size_t addBuffer(const uint8_t *buf2, size_t len2) { + if (len() + len2 <= size()) { + for (uint32_t i = 0; i < len2; i++) { + _buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]); + } + } + return _buf->len; + } + + size_t addBuffer(const char *buf2, size_t len2) { + if (len() + len2 <= size()) { + for (uint32_t i = 0; i < len2; i++) { + _buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]); + } + } + return _buf->len; + } + + uint8_t get8(size_t offset) const { + if (offset < _buf->len) { + return _buf->buf[offset]; + } else { + return 0; + } + } + uint8_t read8(const size_t offset) const { + if (offset < len()) { + return _buf->buf[offset]; + } + return 0; + } + uint16_t get16(const size_t offset) const { + if (offset < len() - 1) { + return _buf->buf[offset] | (_buf->buf[offset+1] << 8); + } + return 0; + } + uint32_t get32(const size_t offset) const { + if (offset < len() - 3) { + return _buf->buf[offset] | (_buf->buf[offset+1] << 8) | + (_buf->buf[offset+2] << 16) | (_buf->buf[offset+3] << 24); + } + return 0; + } + uint64_t get64(const size_t offset) const { + if (offset < len() - 7) { + return (uint64_t)_buf->buf[offset] | ((uint64_t)_buf->buf[offset+1] << 8) | + ((uint64_t)_buf->buf[offset+2] << 16) | ((uint64_t)_buf->buf[offset+3] << 24) | + ((uint64_t)_buf->buf[offset+4] << 32) | ((uint64_t)_buf->buf[offset+5] << 40) | + ((uint64_t)_buf->buf[offset+6] << 48) | ((uint64_t)_buf->buf[offset+7] << 56); + } + return 0; + } + + + inline size_t strlen(const size_t offset) const { + return strnlen((const char*) &_buf->buf[offset], len() - offset); + } + + size_t strlen_s(const size_t offset) const { + size_t slen = this->strlen(offset); + if (slen == len() - offset) { + return 0; + } else { + return slen; + } + } + + SBuffer subBuffer(const size_t start, size_t len) const { + if (start >= _buf->len) { + len = 0; + } else if (start + len > _buf->len) { + len = _buf->len - start; + } + + SBuffer buf2(len); + memcpy(buf2.buf(), buf()+start, len); + buf2._buf->len = len; + return buf2; + } + + static SBuffer SBufferFromHex(const char *hex, size_t len) { + size_t buf_len = (len + 3) / 2; + SBuffer buf2(buf_len); + uint8_t val; + + for (; len > 1; len -= 2) { + val = asc2byte(*hex++) << 4; + val |= asc2byte(*hex++); + buf2.add8(val); + } + return buf2; + } + +protected: + + static uint8_t asc2byte(char chr) { + uint8_t rVal = 0; + if (isdigit(chr)) { rVal = chr - '0'; } + else if (chr >= 'A' && chr <= 'F') { rVal = chr + 10 - 'A'; } + else if (chr >= 'a' && chr <= 'f') { rVal = chr + 10 - 'a'; } + return rVal; + } + + static void unHex(const char* in, uint8_t *out, size_t len) { + } + +protected: + SBuffer_impl * _buf; + +} SBuffer; + +typedef class PreAllocatedSBuffer : public SBuffer { + +public: + PreAllocatedSBuffer(const size_t size, void * buffer) { + _buf = (SBuffer_impl*) buffer; + _buf->size = size - 4; + _buf->len = 0; + } + + ~PreAllocatedSBuffer(void) { + + _buf = nullptr; + } +} PreAllocatedSBuffer; +# 1 "S:/Development/Tasmota/tasmota/support_statistics.ino" +# 20 "S:/Development/Tasmota/tasmota/support_statistics.ino" +#define USE_STATS_CODE + +#ifdef USE_STATS_CODE + + + + +String GetStatistics(void) +{ + char data[40]; + snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""), GetSettingsTextLen(), settings_text_size); + return String(data); +} + +#else + +String GetStatistics(void) +{ + return String(""); +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/support_switch.ino" +# 20 "S:/Development/Tasmota/tasmota/support_switch.ino" +#define SWITCH_V2 +#ifdef SWITCH_V2 + + + + + + +const uint8_t SWITCH_PROBE_INTERVAL = 10; + +#include + +Ticker TickerSwitch; + +struct SWITCH { + unsigned long debounce = 0; + uint16_t no_pullup_mask = 0; + uint8_t state[MAX_SWITCHES] = { 0 }; + uint8_t last_state[MAX_SWITCHES]; + uint8_t hold_timer[MAX_SWITCHES] = { 0 }; + uint8_t virtual_state[MAX_SWITCHES]; + uint8_t present = 0; +} Switch; + + + +void SwitchPullupFlag(uint16 switch_bit) +{ + bitSet(Switch.no_pullup_mask, switch_bit); +} + +void SwitchSetVirtual(uint32_t index, uint8_t state) +{ + Switch.virtual_state[index] = state; +} + +uint8_t SwitchGetVirtual(uint32_t index) +{ + return Switch.virtual_state[index]; +} + +uint8_t SwitchLastState(uint32_t index) +{ + return Switch.last_state[index]; +} + +bool SwitchState(uint32_t index) +{ + uint32_t switchmode = Settings.switchmode[index]; + return ((FOLLOW_INV == switchmode) || + (PUSHBUTTON_INV == switchmode) || + (PUSHBUTTONHOLD_INV == switchmode) || + (FOLLOWMULTI_INV == switchmode) || + (PUSHHOLDMULTI_INV == switchmode) + ) ^ Switch.last_state[index]; +} + + + +void SwitchProbe(void) +{ + if (uptime < 4) { return; } + + uint8_t state_filter = Settings.switch_debounce / SWITCH_PROBE_INTERVAL; + uint8_t force_high = (Settings.switch_debounce % 50) &1; + uint8_t force_low = (Settings.switch_debounce % 50) &2; + + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { + if (pin[GPIO_SWT1 +i] < 99) { + + if (1 == digitalRead(pin[GPIO_SWT1 +i])) { + + if (force_high) { + if (1 == Switch.virtual_state[i]) { + Switch.state[i] = state_filter; + } + } + + if (Switch.state[i] < state_filter) { + Switch.state[i]++; + if (state_filter == Switch.state[i]) { + Switch.virtual_state[i] = 1; + } + } + } else { + + if (force_low) { + if (0 == Switch.virtual_state[i]) { + Switch.state[i] = 0; + } + } + + if (Switch.state[i] > 0) { + Switch.state[i]--; + if (0 == Switch.state[i]) { + Switch.virtual_state[i] = 0; + } + } + } + } + } + TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe); +} + +void SwitchInit(void) +{ + Switch.present = 0; + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { + Switch.last_state[i] = 1; + if (pin[GPIO_SWT1 +i] < 99) { + Switch.present++; + pinMode(pin[GPIO_SWT1 +i], bitRead(Switch.no_pullup_mask, i) ? INPUT : ((16 == pin[GPIO_SWT1 +i]) ? INPUT_PULLDOWN_16 : INPUT_PULLUP)); + Switch.last_state[i] = digitalRead(pin[GPIO_SWT1 +i]); + } + Switch.virtual_state[i] = Switch.last_state[i]; + } + if (Switch.present) { TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe); } +} + + + + + +void SwitchHandler(uint8_t mode) +{ + if (uptime < 4) { return; } + + uint16_t loops_per_second = 1000 / Settings.switch_debounce; + + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { + if ((pin[GPIO_SWT1 +i] < 99) || (mode)) { + uint8_t button = Switch.virtual_state[i]; + uint8_t switchflag = POWER_TOGGLE +1; + + if (Switch.hold_timer[i]) { + Switch.hold_timer[i]--; + if (0 == Switch.hold_timer[i]) { + + switch (Settings.switchmode[i]) { + case TOGGLEMULTI: + switchflag = POWER_TOGGLE; + break; + case FOLLOWMULTI: + switchflag = button &1; + break; + case FOLLOWMULTI_INV: + switchflag = ~button &1; + break; + case PUSHHOLDMULTI: + if (NOT_PRESSED == button){ + Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 25; + SendKey(KEY_SWITCH, i +1, POWER_INCREMENT); + } + else + SendKey(KEY_SWITCH, i +1, POWER_CLEAR); + break; + case PUSHHOLDMULTI_INV: + if (PRESSED == button){ + Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 25; + SendKey(KEY_SWITCH, i +1, POWER_INCREMENT); + } + else + SendKey(KEY_SWITCH, i +1, POWER_CLEAR); + break; + default: + SendKey(KEY_SWITCH, i +1, POWER_HOLD); + break; + } + } + } + + + + if (button != Switch.last_state[i]) { + switch (Settings.switchmode[i]) { + case TOGGLE: + case PUSHBUTTON_TOGGLE: + switchflag = POWER_TOGGLE; + break; + case FOLLOW: + switchflag = button &1; + break; + case FOLLOW_INV: + switchflag = ~button &1; + break; + case PUSHBUTTON: + if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) { + switchflag = POWER_TOGGLE; + } + break; + case PUSHBUTTON_INV: + if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) { + switchflag = POWER_TOGGLE; + } + break; + case PUSHBUTTONHOLD: + if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) { + Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; + } + if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i]) && (Switch.hold_timer[i])) { + Switch.hold_timer[i] = 0; + switchflag = POWER_TOGGLE; + } + break; + case PUSHBUTTONHOLD_INV: + if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) { + Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; + } + if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i]) && (Switch.hold_timer[i])) { + Switch.hold_timer[i] = 0; + switchflag = POWER_TOGGLE; + } + break; + case TOGGLEMULTI: + case FOLLOWMULTI: + case FOLLOWMULTI_INV: + if (Switch.hold_timer[i]) { + Switch.hold_timer[i] = 0; + SendKey(KEY_SWITCH, i +1, POWER_HOLD); + } else { + Switch.hold_timer[i] = loops_per_second / 2; + } + break; + case PUSHHOLDMULTI: + if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) { + if(Switch.hold_timer[i]!=0) + SendKey(KEY_SWITCH, i +1, POWER_INV); + Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; + } + if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) { + if(Switch.hold_timer[i] > loops_per_second * Settings.param[P_HOLD_TIME] / 25) + switchflag = POWER_TOGGLE; + Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; + } + break; + case PUSHHOLDMULTI_INV: + if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) { + if(Switch.hold_timer[i]!=0) + SendKey(KEY_SWITCH, i +1, POWER_INV); + Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; + } + if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) { + if(Switch.hold_timer[i] > loops_per_second * Settings.param[P_HOLD_TIME] / 25) + switchflag = POWER_TOGGLE; + Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; + } + break; + } + Switch.last_state[i] = button; + } + if (switchflag <= POWER_TOGGLE) { + if (!SendKey(KEY_SWITCH, i +1, switchflag)) { + ExecuteCommandPower(i +1, switchflag, SRC_SWITCH); + } + } + } + } +} + +void SwitchLoop(void) +{ + if (Switch.present) { + if (TimeReached(Switch.debounce)) { + SetNextTimeInterval(Switch.debounce, Settings.switch_debounce); + SwitchHandler(0); + } + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/support_tasmota.ino" +# 20 "S:/Development/Tasmota/tasmota/support_tasmota.ino" +const char kSleepMode[] PROGMEM = "Dynamic|Normal"; +const char kPrefixes[] PROGMEM = D_CMND "|" D_STAT "|" D_TELE; + +char* Format(char* output, const char* input, int size) +{ + char *token; + uint32_t digits = 0; + + if (strstr(input, "%") != nullptr) { + strlcpy(output, input, size); + token = strtok(output, "%"); + if (strstr(input, "%") == input) { + output[0] = '\0'; + } else { + token = strtok(nullptr, ""); + } + if (token != nullptr) { + digits = atoi(token); + if (digits) { + char tmp[size]; + if (strchr(token, 'd')) { + snprintf_P(tmp, size, PSTR("%s%c0%dd"), output, '%', digits); + snprintf_P(output, size, tmp, ESP.getChipId() & 0x1fff); + } else { + snprintf_P(tmp, size, PSTR("%s%c0%dX"), output, '%', digits); + snprintf_P(output, size, tmp, ESP.getChipId()); + } + } else { + if (strchr(token, 'd')) { + snprintf_P(output, size, PSTR("%s%d"), output, ESP.getChipId()); + digits = 8; + } + } + } + } + if (!digits) { + strlcpy(output, input, size); + } + return output; +} + +char* GetOtaUrl(char *otaurl, size_t otaurl_size) +{ + if (strstr(SettingsText(SET_OTAURL), "%04d") != nullptr) { + snprintf(otaurl, otaurl_size, SettingsText(SET_OTAURL), ESP.getChipId() & 0x1fff); + } + else if (strstr(SettingsText(SET_OTAURL), "%d") != nullptr) { + snprintf_P(otaurl, otaurl_size, SettingsText(SET_OTAURL), ESP.getChipId()); + } + else { + strlcpy(otaurl, SettingsText(SET_OTAURL), otaurl_size); + } + + return otaurl; +} + +char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopic) +{ +# 88 "S:/Development/Tasmota/tasmota/support_tasmota.ino" + char romram[CMDSZ]; + String fulltopic; + + snprintf_P(romram, sizeof(romram), subtopic); + if (fallback_topic_flag || (prefix > 3)) { + bool fallback = (prefix < 8); + prefix &= 3; + char stemp[11]; + fulltopic = GetTextIndexed(stemp, sizeof(stemp), prefix, kPrefixes); + fulltopic += F("/"); + if (fallback) { + fulltopic += mqtt_client; + fulltopic += F("_fb"); + } else { + fulltopic += topic; + } + } else { + fulltopic = SettingsText(SET_MQTT_FULLTOPIC); + if ((0 == prefix) && (-1 == fulltopic.indexOf(FPSTR(MQTT_TOKEN_PREFIX)))) { + fulltopic += F("/"); + fulltopic += FPSTR(MQTT_TOKEN_PREFIX); + } + for (uint32_t i = 0; i < MAX_MQTT_PREFIXES; i++) { + if (!strlen(SettingsText(SET_MQTTPREFIX1 + i))) { + char temp[TOPSZ]; + SettingsUpdateText(SET_MQTTPREFIX1 + i, GetTextIndexed(temp, sizeof(temp), i, kPrefixes)); + } + } + fulltopic.replace(FPSTR(MQTT_TOKEN_PREFIX), SettingsText(SET_MQTTPREFIX1 + prefix)); + + fulltopic.replace(FPSTR(MQTT_TOKEN_TOPIC), topic); + fulltopic.replace(F("%hostname%"), my_hostname); + String token_id = WiFi.macAddress(); + token_id.replace(":", ""); + fulltopic.replace(F("%id%"), token_id); + } + fulltopic.replace(F("#"), ""); + fulltopic.replace(F("//"), "/"); + if (!fulltopic.endsWith("/")) { + fulltopic += "/"; + } + snprintf_P(stopic, TOPSZ, PSTR("%s%s"), fulltopic.c_str(), romram); + return stopic; +} + +char* GetGroupTopic_P(char *stopic, const char* subtopic) +{ + + + return GetTopic_P(stopic, (Settings.flag3.grouptopic_mode) ? CMND +8 : CMND, SettingsText(SET_MQTT_GRP_TOPIC), subtopic); +} + +char* GetFallbackTopic_P(char *stopic, const char* subtopic) +{ + return GetTopic_P(stopic, CMND +4, nullptr, subtopic); +} + +char* GetStateText(uint32_t state) +{ + if (state >= MAX_STATE_TEXT) { + state = 1; + } + return SettingsText(SET_STATE_TXT1 + state); +} + + + +void SetLatchingRelay(power_t lpower, uint32_t state) +{ + + + + + + if (state && !latching_relay_pulse) { + latching_power = lpower; + latching_relay_pulse = 2; + } + + for (uint32_t i = 0; i < devices_present; i++) { + uint32_t port = (i << 1) + ((latching_power >> i) &1); + DigitalWrite(GPIO_REL1 +port, bitRead(rel_inverted, port) ? !state : state); + } +} + +void SetDevicePower(power_t rpower, uint32_t source) +{ + ShowSource(source); + last_source = source; + + if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) { + power = (1 << devices_present) -1; + rpower = power; + } + + if (Settings.flag.interlock) { + for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { + power_t mask = 1; + uint32_t count = 0; + for (uint32_t j = 0; j < devices_present; j++) { + if ((Settings.interlock[i] & mask) && (rpower & mask)) { + count++; + } + mask <<= 1; + } + if (count > 1) { + mask = ~Settings.interlock[i]; + power &= mask; + rpower &= mask; + } + } + } + + if (rpower) { + last_power = rpower; + } + + XdrvMailbox.index = rpower; + XdrvCall(FUNC_SET_POWER); + + XdrvMailbox.index = rpower; + XdrvMailbox.payload = source; + if (XdrvCall(FUNC_SET_DEVICE_POWER)) { + + } + else if ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { + Serial.write(0xA0); + Serial.write(0x04); + Serial.write(rpower &0xFF); + Serial.write(0xA1); + Serial.write('\n'); + Serial.flush(); + } + else if (EXS_RELAY == my_module_type) { + SetLatchingRelay(rpower, 1); + } + else { + for (uint32_t i = 0; i < devices_present; i++) { + power_t state = rpower &1; + if (i < MAX_RELAYS) { + DigitalWrite(GPIO_REL1 +i, bitRead(rel_inverted, i) ? !state : state); + } + rpower >>= 1; + } + } +} + +void RestorePower(bool publish_power, uint32_t source) +{ + if (power != last_power) { + power = last_power; + SetDevicePower(power, source); + if (publish_power) { + MqttPublishAllPowerState(); + } + } +} + +void SetAllPower(uint32_t state, uint32_t source) +{ +# 256 "S:/Development/Tasmota/tasmota/support_tasmota.ino" + bool publish_power = true; + if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) { + state &= 3; + publish_power = false; + } + if ((state >= POWER_OFF) && (state <= POWER_TOGGLE)) { + power_t all_on = (1 << devices_present) -1; + switch (state) { + case POWER_OFF: + power = 0; + break; + case POWER_ON: + power = all_on; + break; + case POWER_TOGGLE: + power ^= all_on; + } + SetDevicePower(power, source); + } + if (publish_power) { + MqttPublishAllPowerState(); + } +} + +void SetPowerOnState(void) +{ + if (MOTOR == my_module_type) { + Settings.poweronstate = POWER_ALL_ON; + } + if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) { + SetDevicePower(1, SRC_RESTART); + } else { + if ((ResetReason() == REASON_DEFAULT_RST) || (ResetReason() == REASON_EXT_SYS_RST)) { + switch (Settings.poweronstate) { + case POWER_ALL_OFF: + case POWER_ALL_OFF_PULSETIME_ON: + power = 0; + SetDevicePower(power, SRC_RESTART); + break; + case POWER_ALL_ON: + power = (1 << devices_present) -1; + SetDevicePower(power, SRC_RESTART); + break; + case POWER_ALL_SAVED_TOGGLE: + power = (Settings.power & ((1 << devices_present) -1)) ^ POWER_MASK; + if (Settings.flag.save_state) { + SetDevicePower(power, SRC_RESTART); + } + break; + case POWER_ALL_SAVED: + power = Settings.power & ((1 << devices_present) -1); + if (Settings.flag.save_state) { + SetDevicePower(power, SRC_RESTART); + } + break; + } + } else { + power = Settings.power & ((1 << devices_present) -1); + if (Settings.flag.save_state) { + SetDevicePower(power, SRC_RESTART); + } + } + } + + + for (uint32_t i = 0; i < devices_present; i++) { + if (!Settings.flag3.no_power_feedback) { + if ((i < MAX_RELAYS) && (pin[GPIO_REL1 +i] < 99)) { + bitWrite(power, i, digitalRead(pin[GPIO_REL1 +i]) ^ bitRead(rel_inverted, i)); + } + } + if ((i < MAX_PULSETIMERS) && (bitRead(power, i) || (POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate))) { + SetPulseTimer(i, Settings.pulse_timer[i]); + } + } + blink_powersave = power; +} + +void SetLedPowerIdx(uint32_t led, uint32_t state) +{ + if ((99 == pin[GPIO_LEDLNK]) && (0 == led)) { + if (pin[GPIO_LED2] < 99) { + led = 1; + } + } + if (pin[GPIO_LED1 + led] < 99) { + uint32_t mask = 1 << led; + if (state) { + state = 1; + led_power |= mask; + } else { + led_power &= (0xFF ^ mask); + } + DigitalWrite(GPIO_LED1 + led, bitRead(led_inverted, led) ? !state : state); + } +#ifdef USE_BUZZER + if (led == 0) { + BuzzerSetStateToLed(state); + } +#endif +} + +void SetLedPower(uint32_t state) +{ + if (99 == pin[GPIO_LEDLNK]) { + SetLedPowerIdx(0, state); + } else { + power_t mask = 1; + for (uint32_t i = 0; i < leds_present; i++) { + bool tstate = (power & mask); + SetLedPowerIdx(i, tstate); + mask <<= 1; + } + } +} + +void SetLedPowerAll(uint32_t state) +{ + for (uint32_t i = 0; i < leds_present; i++) { + SetLedPowerIdx(i, state); + } +} + +void SetLedLink(uint32_t state) +{ + uint32_t led_pin = pin[GPIO_LEDLNK]; + uint32_t led_inv = ledlnk_inverted; + if (99 == led_pin) { + led_pin = pin[GPIO_LED1]; + led_inv = bitRead(led_inverted, 0); + } + if (led_pin < 99) { + if (state) { state = 1; } + digitalWrite(led_pin, (led_inv) ? !state : state); + } +#ifdef USE_BUZZER + BuzzerSetStateToLed(state); +#endif +} + +void SetPulseTimer(uint32_t index, uint32_t time) +{ + pulse_timer[index] = (time > 111) ? millis() + (1000 * (time - 100)) : (time > 0) ? millis() + (100 * time) : 0L; +} + +uint32_t GetPulseTimer(uint32_t index) +{ + long time = TimePassedSince(pulse_timer[index]); + if (time < 0) { + time *= -1; + return (time > 11100) ? (time / 1000) + 100 : (time > 0) ? time / 100 : 0; + } + return 0; +} + + + +bool SendKey(uint32_t key, uint32_t device, uint32_t state) +{ +# 423 "S:/Development/Tasmota/tasmota/support_tasmota.ino" + char stopic[TOPSZ]; + char scommand[CMDSZ]; + char key_topic[TOPSZ]; + bool result = false; + + char *tmp = (key) ? SettingsText(SET_MQTT_SWITCH_TOPIC) : SettingsText(SET_MQTT_BUTTON_TOPIC); + Format(key_topic, tmp, sizeof(key_topic)); + if (Settings.flag.mqtt_enabled && MqttIsConnected() && (strlen(key_topic) != 0) && strcmp(key_topic, "0")) { + if (!key && (device > devices_present)) { + device = 1; + } + GetTopic_P(stopic, CMND, key_topic, + GetPowerDevice(scommand, device, sizeof(scommand), (key + Settings.flag.device_index_enable))); + if (CLEAR_RETAIN == state) { + mqtt_data[0] = '\0'; + } else { + if ((Settings.flag3.button_switch_force_local || + !strcmp(mqtt_topic, key_topic) || + !strcmp(SettingsText(SET_MQTT_GRP_TOPIC), key_topic)) && + (POWER_TOGGLE == state)) { + state = ~(power >> (device -1)) &1; + } + snprintf_P(mqtt_data, sizeof(mqtt_data), GetStateText(state)); + } +#ifdef USE_DOMOTICZ + if (!(DomoticzSendKey(key, device, state, strlen(mqtt_data)))) { +#endif + MqttPublish(stopic, ((key) ? Settings.flag.mqtt_switch_retain + : Settings.flag.mqtt_button_retain) && + (state != POWER_HOLD || !Settings.flag3.no_hold_retain)); +#ifdef USE_DOMOTICZ + } +#endif + result = !Settings.flag3.button_switch_force_local; + } else { + Response_P(PSTR("{\"%s%d\":{\"State\":%d}}"), (key) ? "Switch" : "Button", device, state); + result = XdrvRulesProcess(); + } + int32_t payload_save = XdrvMailbox.payload; + XdrvMailbox.payload = key << 16 | state << 8 | device; + XdrvCall(FUNC_ANY_KEY); + XdrvMailbox.payload = payload_save; + return result; +} + +void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source) +{ +# 483 "S:/Development/Tasmota/tasmota/support_tasmota.ino" +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { + blink_mask &= 1; + Settings.flag.interlock = 0; + Settings.pulse_timer[1] = 0; + Settings.pulse_timer[2] = 0; + Settings.pulse_timer[3] = 0; + } +#endif + + bool publish_power = true; + if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) { + state &= 3; + publish_power = false; + } + + if ((device < 1) || (device > devices_present)) { + device = 1; + } + active_device = device; + + if (device <= MAX_PULSETIMERS) { + SetPulseTimer(device -1, 0); + } + power_t mask = 1 << (device -1); + if (state <= POWER_TOGGLE) { + if ((blink_mask & mask)) { + blink_mask &= (POWER_MASK ^ mask); + MqttPublishPowerBlinkState(device); + } + + if (Settings.flag.interlock && + !interlock_mutex && + ((POWER_ON == state) || ((POWER_TOGGLE == state) && !(power & mask))) + ) { + interlock_mutex = true; + for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { + if (Settings.interlock[i] & mask) { + for (uint32_t j = 0; j < devices_present; j++) { + power_t imask = 1 << j; + if ((Settings.interlock[i] & imask) && (power & imask) && (mask != imask)) { + ExecuteCommandPower(j +1, POWER_OFF, SRC_IGNORE); + delay(50); + } + } + break; + } + } + interlock_mutex = false; + } + + switch (state) { + case POWER_OFF: { + power &= (POWER_MASK ^ mask); + break; } + case POWER_ON: + power |= mask; + break; + case POWER_TOGGLE: + power ^= mask; + } +#ifdef USE_DEVICE_GROUPS + if (SRC_REMOTE != source && SRC_RETRY != source) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, power); +#endif + SetDevicePower(power, source); +#ifdef USE_DOMOTICZ + DomoticzUpdatePowerState(device); +#endif +#ifdef USE_KNX + KnxUpdatePowerState(device, power); +#endif + if (publish_power && Settings.flag3.hass_tele_on_power) { + MqttPublishTeleState(); + } + if (device <= MAX_PULSETIMERS) { + SetPulseTimer(device -1, (((POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate) ? ~power : power) & mask) ? Settings.pulse_timer[device -1] : 0); + } + } + else if (POWER_BLINK == state) { + if (!(blink_mask & mask)) { + blink_powersave = (blink_powersave & (POWER_MASK ^ mask)) | (power & mask); + blink_power = (power >> (device -1))&1; + } + blink_timer = millis() + 100; + blink_counter = ((!Settings.blinkcount) ? 64000 : (Settings.blinkcount *2)) +1; + blink_mask |= mask; + MqttPublishPowerBlinkState(device); + return; + } + else if (POWER_BLINK_STOP == state) { + bool flag = (blink_mask & mask); + blink_mask &= (POWER_MASK ^ mask); + MqttPublishPowerBlinkState(device); + if (flag) { + ExecuteCommandPower(device, (blink_powersave >> (device -1))&1, SRC_IGNORE); + } + return; + } + if (publish_power) { + MqttPublishPowerState(device); + } +} + +void StopAllPowerBlink(void) +{ + power_t mask; + + for (uint32_t i = 1; i <= devices_present; i++) { + mask = 1 << (i -1); + if (blink_mask & mask) { + blink_mask &= (POWER_MASK ^ mask); + MqttPublishPowerBlinkState(i); + ExecuteCommandPower(i, (blink_powersave >> (i -1))&1, SRC_IGNORE); + } + } +} + +void MqttShowPWMState(void) +{ + ResponseAppend_P(PSTR("\"" D_CMND_PWM "\":{")); + bool first = true; + for (uint32_t i = 0; i < MAX_PWMS; i++) { + if (pin[GPIO_PWM1 + i] < 99) { + ResponseAppend_P(PSTR("%s\"" D_CMND_PWM "%d\":%d"), first ? "" : ",", i+1, Settings.pwm_value[i]); + first = false; + } + } + ResponseJsonEnd(); +} + +void MqttShowState(void) +{ + char stemp1[TOPSZ]; + + ResponseAppendTime(); + ResponseAppend_P(PSTR(",\"" D_JSON_UPTIME "\":\"%s\",\"UptimeSec\":%u"), GetUptime().c_str(), UpTime()); + +#ifdef USE_ADC_VCC + dtostrfd((double)ESP.getVcc()/1000, 3, stemp1); + ResponseAppend_P(PSTR(",\"" D_JSON_VCC "\":%s"), stemp1); +#endif + + ResponseAppend_P(PSTR(",\"" D_JSON_HEAPSIZE "\":%d,\"SleepMode\":\"%s\",\"Sleep\":%u,\"LoadAvg\":%u,\"MqttCount\":%u"), + ESP.getFreeHeap()/1024, GetTextIndexed(stemp1, sizeof(stemp1), Settings.flag3.sleep_normal, kSleepMode), + sleep, loop_load_avg, MqttConnectCount()); + + for (uint32_t i = 1; i <= devices_present; i++) { +#ifdef USE_LIGHT + if ((LightDevice()) && (i >= LightDevice())) { + if (i == LightDevice()) { LightState(1); } + } else { +#endif + ResponseAppend_P(PSTR(",\"%s\":\"%s\""), GetPowerDevice(stemp1, i, sizeof(stemp1), Settings.flag.device_index_enable), + GetStateText(bitRead(power, i-1))); +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { + ResponseAppend_P(PSTR(",\"" D_CMND_FANSPEED "\":%d"), GetFanspeed()); + break; + } +#endif +#ifdef USE_LIGHT + } +#endif + } + + if (pwm_present) { + ResponseAppend_P(PSTR(",")); + MqttShowPWMState(); + } + + int32_t rssi = WiFi.RSSI(); + ResponseAppend_P(PSTR(",\"" D_JSON_WIFI "\":{\"" D_JSON_AP "\":%d,\"" D_JSON_SSID "\":\"%s\",\"" D_JSON_BSSID "\":\"%s\",\"" D_JSON_CHANNEL "\":%d,\"" D_JSON_RSSI "\":%d,\"" D_JSON_SIGNAL "\":%d,\"" D_JSON_LINK_COUNT "\":%d,\"" D_JSON_DOWNTIME "\":\"%s\"}}"), + Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), WiFi.BSSIDstr().c_str(), WiFi.channel(), + WifiGetRssiAsQuality(rssi), rssi, WifiLinkCount(), WifiDowntime().c_str()); +} + +void MqttPublishTeleState(void) +{ + mqtt_data[0] = '\0'; + MqttShowState(); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN); +#if defined(USE_RULES) || defined(USE_SCRIPT) + RulesTeleperiod(); +#endif +} + +bool MqttShowSensor(void) +{ + ResponseAppendTime(); + + int json_data_start = strlen(mqtt_data); + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { +#ifdef USE_TM1638 + if ((pin[GPIO_SWT1 +i] < 99) || ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99))) { +#else + if (pin[GPIO_SWT1 +i] < 99) { +#endif + ResponseAppend_P(PSTR(",\"" D_JSON_SWITCH "%d\":\"%s\""), i +1, GetStateText(SwitchState(i))); + } + } + XsnsCall(FUNC_JSON_APPEND); + XdrvCall(FUNC_JSON_APPEND); + + bool json_data_available = (strlen(mqtt_data) - json_data_start); + if (strstr_P(mqtt_data, PSTR(D_JSON_PRESSURE)) != nullptr) { + ResponseAppend_P(PSTR(",\"" D_JSON_PRESSURE_UNIT "\":\"%s\""), PressureUnit().c_str()); + } + if (strstr_P(mqtt_data, PSTR(D_JSON_TEMPERATURE)) != nullptr) { + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE_UNIT "\":\"%c\""), TempUnit()); + } + ResponseJsonEnd(); + + if (json_data_available) { XdrvCall(FUNC_SHOW_SENSOR); } + return json_data_available; +} + +void MqttPublishSensor(void) +{ + mqtt_data[0] = '\0'; + if (MqttShowSensor()) { + MqttPublishTeleSensor(); + } +} +# 714 "S:/Development/Tasmota/tasmota/support_tasmota.ino" +void PerformEverySecond(void) +{ + uptime++; + + if (POWER_CYCLE_TIME == uptime) { + UpdateQuickPowerCycle(false); + } + + if (BOOT_LOOP_TIME == uptime) { + RtcRebootReset(); + +#ifdef USE_DEEPSLEEP + if (!(DeepSleepEnabled() && !Settings.flag3.bootcount_update)) { +#endif + Settings.bootcount++; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BOOT_COUNT " %d"), Settings.bootcount); +#ifdef USE_DEEPSLEEP + } +#endif + } + + if (mqtt_cmnd_blocked_reset) { + mqtt_cmnd_blocked_reset--; + if (!mqtt_cmnd_blocked_reset) { + mqtt_cmnd_blocked = 0; + } + } + + if (seriallog_timer) { + seriallog_timer--; + if (!seriallog_timer) { + if (seriallog_level) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SERIAL_LOGGING_DISABLED)); + } + seriallog_level = 0; + } + } + + if (syslog_timer) { + syslog_timer--; + if (!syslog_timer) { + syslog_level = Settings.syslog_level; + if (Settings.syslog_level) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SYSLOG_LOGGING_REENABLED)); + } + } + } + + ResetGlobalValues(); + + if (Settings.tele_period) { + if (tele_period >= 9999) { + if (!global_state.wifi_down) { + tele_period = 0; + } + } else { + tele_period++; + if (tele_period >= Settings.tele_period) { + tele_period = 0; + + MqttPublishTeleState(); + + mqtt_data[0] = '\0'; + if (MqttShowSensor()) { + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); +#if defined(USE_RULES) || defined(USE_SCRIPT) + RulesTeleperiod(); +#endif + } + + XdrvCall(FUNC_AFTER_TELEPERIOD); + } + } + } +} + + + + + +void Every100mSeconds(void) +{ + + power_t power_now; + + if (prepped_loglevel) { + AddLog(prepped_loglevel); + prepped_loglevel = 0; + } + + if (latching_relay_pulse) { + latching_relay_pulse--; + if (!latching_relay_pulse) SetLatchingRelay(0, 0); + } + + for (uint32_t i = 0; i < MAX_PULSETIMERS; i++) { + if (pulse_timer[i] != 0L) { + if (TimeReached(pulse_timer[i])) { + pulse_timer[i] = 0L; + ExecuteCommandPower(i +1, (POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate) ? POWER_ON : POWER_OFF, SRC_PULSETIMER); + } + } + } + + if (blink_mask) { + if (TimeReached(blink_timer)) { + SetNextTimeInterval(blink_timer, 100 * Settings.blinktime); + blink_counter--; + if (!blink_counter) { + StopAllPowerBlink(); + } else { + blink_power ^= 1; + power_now = (power & (POWER_MASK ^ blink_mask)) | ((blink_power) ? blink_mask : 0); + SetDevicePower(power_now, SRC_IGNORE); + } + } + } +} + + + + + +void Every250mSeconds(void) +{ + + + uint32_t blinkinterval = 1; + + state_250mS++; + state_250mS &= 0x3; + + if (!Settings.flag.global_state) { + if (global_state.data) { + if (global_state.mqtt_down) { blinkinterval = 7; } + if (global_state.wifi_down) { blinkinterval = 3; } + blinks = 201; + } + } + if (blinks || restart_flag || ota_state_flag) { + if (restart_flag || ota_state_flag) { + blinkstate = true; + } else { + blinkspeed--; + if (!blinkspeed) { + blinkspeed = blinkinterval; + blinkstate ^= 1; + } + } + if ((!(Settings.ledstate &0x08)) && ((Settings.ledstate &0x06) || (blinks > 200) || (blinkstate))) { + SetLedLink(blinkstate); + } + if (!blinkstate) { + blinks--; + if (200 == blinks) blinks = 0; + } + } + if (Settings.ledstate &1 && (pin[GPIO_LEDLNK] < 99 || !(blinks || restart_flag || ota_state_flag)) ) { + bool tstate = power & Settings.ledmask; + if ((SONOFF_TOUCH == my_module_type) || (SONOFF_T11 == my_module_type) || (SONOFF_T12 == my_module_type) || (SONOFF_T13 == my_module_type)) { + tstate = (!power) ? 1 : 0; + } + SetLedPower(tstate); + } + + + + + + switch (state_250mS) { + case 0: + if (ota_state_flag && BACKLOG_EMPTY) { + ota_state_flag--; + if (2 == ota_state_flag) { + RtcSettings.ota_loader = 0; + ota_retry_counter = OTA_ATTEMPTS; + ESPhttpUpdate.rebootOnUpdate(false); + SettingsSave(1); + } + if (ota_state_flag <= 0) { +#ifdef USE_WEBSERVER + if (Settings.webserver) StopWebserver(); +#endif +#ifdef USE_ARILUX_RF + AriluxRfDisable(); +#endif + ota_state_flag = 92; + ota_result = 0; + ota_retry_counter--; + if (ota_retry_counter) { + strlcpy(mqtt_data, GetOtaUrl(log_data, sizeof(log_data)), sizeof(mqtt_data)); +#ifndef FIRMWARE_MINIMAL + if (RtcSettings.ota_loader) { +# 919 "S:/Development/Tasmota/tasmota/support_tasmota.ino" + char *bch = strrchr(mqtt_data, '/'); + if (bch == nullptr) { bch = mqtt_data; } + + char *ech = strrchr(bch, '.'); + if ((ech != nullptr) && (0 == strncasecmp_P(ech, PSTR(".GZ"), 3))) { + char *fch = ech; + *fch = '\0'; + ech = strrchr(bch, '.'); + *fch = '.'; + } + if (ech == nullptr) { ech = mqtt_data + strlen(mqtt_data); } + char ota_url_type[strlen(ech) +1]; + strncpy(ota_url_type, ech, sizeof(ota_url_type)); + + char *pch = strrchr(bch, '-'); + if (pch == nullptr) { pch = ech; } + *pch = '\0'; + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s-" D_JSON_MINIMAL "%s"), mqtt_data, ota_url_type); + } +#endif + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "%s"), mqtt_data); +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) + ota_result = (HTTP_UPDATE_FAILED != ESPhttpUpdate.update(mqtt_data)); +#else + + WiFiClient OTAclient; + ota_result = (HTTP_UPDATE_FAILED != ESPhttpUpdate.update(OTAclient, mqtt_data)); +#endif + if (!ota_result) { +#ifndef FIRMWARE_MINIMAL + int ota_error = ESPhttpUpdate.getLastError(); + DEBUG_CORE_LOG(PSTR("OTA: Error %d"), ota_error); + if ((HTTP_UE_TOO_LESS_SPACE == ota_error) || (HTTP_UE_BIN_FOR_WRONG_FLASH == ota_error)) { + RtcSettings.ota_loader = 1; + } +#endif + ota_state_flag = 2; + } + } + } + if (90 == ota_state_flag) { + ota_state_flag = 0; + Response_P(PSTR("{\"" D_CMND_UPGRADE "\":\"")); + if (ota_result) { + + if (!VersionCompatible()) { + ResponseAppend_P(PSTR(D_JSON_FAILED " " D_UPLOAD_ERR_14)); + } else { + ResponseAppend_P(PSTR(D_JSON_SUCCESSFUL ". " D_JSON_RESTARTING)); + restart_flag = 2; + } + } else { + ResponseAppend_P(PSTR(D_JSON_FAILED " %s"), ESPhttpUpdate.getLastErrorString().c_str()); + } + ResponseAppend_P(PSTR("\"}")); + + MqttPublishPrefixTopic_P(STAT, PSTR(D_CMND_UPGRADE)); + } + } + break; + case 1: + if (MidnightNow()) { + XsnsCall(FUNC_SAVE_AT_MIDNIGHT); + } + if (save_data_counter && BACKLOG_EMPTY) { + save_data_counter--; + if (save_data_counter <= 0) { + if (Settings.flag.save_state) { + power_t mask = POWER_MASK; + for (uint32_t i = 0; i < MAX_PULSETIMERS; i++) { + if ((Settings.pulse_timer[i] > 0) && (Settings.pulse_timer[i] < 30)) { + mask &= ~(1 << i); + } + } + if (!((Settings.power &mask) == (power &mask))) { + Settings.power = power; + } + } else { + Settings.power = 0; + } + SettingsSave(0); + save_data_counter = Settings.save_data; + } + } + if (restart_flag && BACKLOG_EMPTY) { + if ((214 == restart_flag) || (215 == restart_flag) || (216 == restart_flag)) { + + char storage_ssid1[strlen(SettingsText(SET_STASSID1)) +1]; + strncpy(storage_ssid1, SettingsText(SET_STASSID1), sizeof(storage_ssid1)); + char storage_ssid2[strlen(SettingsText(SET_STASSID2)) +1]; + strncpy(storage_ssid2, SettingsText(SET_STASSID2), sizeof(storage_ssid2)); + char storage_pass1[strlen(SettingsText(SET_STAPWD1)) +1]; + strncpy(storage_pass1, SettingsText(SET_STAPWD1), sizeof(storage_pass1)); + char storage_pass2[strlen(SettingsText(SET_STAPWD2)) +1]; + strncpy(storage_pass2, SettingsText(SET_STAPWD2), sizeof(storage_pass2)); + + char storage_mqtthost[strlen(SettingsText(SET_MQTT_HOST)) +1]; + strncpy(storage_mqtthost, SettingsText(SET_MQTT_HOST), sizeof(storage_mqtthost)); + char storage_mqttuser[strlen(SettingsText(SET_MQTT_USER)) +1]; + strncpy(storage_mqttuser, SettingsText(SET_MQTT_USER), sizeof(storage_mqttuser)); + char storage_mqttpwd[strlen(SettingsText(SET_MQTT_PWD)) +1]; + strncpy(storage_mqttpwd, SettingsText(SET_MQTT_PWD), sizeof(storage_mqttpwd)); + char storage_mqtttopic[strlen(SettingsText(SET_MQTT_TOPIC)) +1]; + strncpy(storage_mqtttopic, SettingsText(SET_MQTT_TOPIC), sizeof(storage_mqtttopic)); + uint16_t mqtt_port = Settings.mqtt_port; + + + + + if ((215 == restart_flag) || (216 == restart_flag)) { + SettingsErase(0); + } + SettingsDefault(); + + SettingsUpdateText(SET_STASSID1, storage_ssid1); + SettingsUpdateText(SET_STASSID2, storage_ssid2); + SettingsUpdateText(SET_STAPWD1, storage_pass1); + SettingsUpdateText(SET_STAPWD2, storage_pass2); + if (216 == restart_flag) { + + SettingsUpdateText(SET_MQTT_HOST, storage_mqtthost); + SettingsUpdateText(SET_MQTT_USER, storage_mqttuser); + SettingsUpdateText(SET_MQTT_PWD, storage_mqttpwd); + SettingsUpdateText(SET_MQTT_TOPIC, storage_mqtttopic); + Settings.mqtt_port = mqtt_port; + } + restart_flag = 2; + } + else if (213 == restart_flag) { + SettingsSdkErase(); + restart_flag = 2; + } + else if (212 == restart_flag) { + SettingsErase(0); + restart_flag = 211; + } + if (211 == restart_flag) { + SettingsDefault(); + restart_flag = 2; + } + if (2 == restart_flag) { + SettingsSaveAll(); + } + restart_flag--; + if (restart_flag <= 0) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING)); + EspRestart(); + } + } + break; + case 2: + WifiCheck(wifi_state_flag); + wifi_state_flag = WIFI_RESTART; + break; + case 3: + if (!global_state.wifi_down) { MqttCheck(); } + break; + } +} + +#ifdef USE_ARDUINO_OTA + + + + + + + +bool arduino_ota_triggered = false; +uint16_t arduino_ota_progress_dot_count = 0; + +void ArduinoOTAInit(void) +{ + ArduinoOTA.setPort(8266); + ArduinoOTA.setHostname(my_hostname); + if (strlen(SettingsText(SET_WEBPWD))) { + ArduinoOTA.setPassword(SettingsText(SET_WEBPWD)); + } + + ArduinoOTA.onStart([]() + { + SettingsSave(1); +#ifdef USE_WEBSERVER + if (Settings.webserver) { StopWebserver(); } +#endif +#ifdef USE_ARILUX_RF + AriluxRfDisable(); +#endif + if (Settings.flag.mqtt_enabled) { + MqttDisconnect(); + } + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_UPLOAD_STARTED)); + arduino_ota_triggered = true; + arduino_ota_progress_dot_count = 0; + delay(100); + }); + + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) + { + if ((LOG_LEVEL_DEBUG <= seriallog_level)) { + arduino_ota_progress_dot_count++; + Serial.printf("."); + if (!(arduino_ota_progress_dot_count % 80)) { Serial.println(); } + } + }); + + ArduinoOTA.onError([](ota_error_t error) + { + + + + + char error_str[100]; + + if ((LOG_LEVEL_DEBUG <= seriallog_level) && arduino_ota_progress_dot_count) { Serial.println(); } + switch (error) { + case OTA_BEGIN_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_2), sizeof(error_str)); break; + case OTA_RECEIVE_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_5), sizeof(error_str)); break; + case OTA_END_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_7), sizeof(error_str)); break; + default: + snprintf_P(error_str, sizeof(error_str), PSTR(D_UPLOAD_ERROR_CODE " %d"), error); + } + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA %s. " D_RESTARTING), error_str); + EspRestart(); + }); + + ArduinoOTA.onEnd([]() + { + if ((LOG_LEVEL_DEBUG <= seriallog_level)) { Serial.println(); } + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_SUCCESSFUL ". " D_RESTARTING)); + EspRestart(); + }); + + ArduinoOTA.begin(); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_ENABLED " " D_PORT " 8266")); +} + +void ArduinoOtaLoop(void) +{ + MDNS.update(); + ArduinoOTA.handle(); + + while (arduino_ota_triggered) { ArduinoOTA.handle(); } +} +#endif + + + +void SerialInput(void) +{ + while (Serial.available()) { + + delay(0); + serial_in_byte = Serial.read(); + + + + + if ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { + serial_in_byte = ButtonSerial(serial_in_byte); + } + + + + if (XdrvCall(FUNC_SERIAL)) { + serial_in_byte_counter = 0; + Serial.flush(); + return; + } + + + + if (serial_in_byte > 127 && !Settings.flag.mqtt_serial_raw) { + serial_in_byte_counter = 0; + Serial.flush(); + return; + } + if (!Settings.flag.mqtt_serial) { + if (isprint(serial_in_byte)) { + if (serial_in_byte_counter < INPUT_BUFFER_SIZE -1) { + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + } else { + serial_in_byte_counter = 0; + } + } + } else { + if (serial_in_byte || Settings.flag.mqtt_serial_raw) { + if ((serial_in_byte_counter < INPUT_BUFFER_SIZE -1) && + ((isprint(serial_in_byte) && (128 == Settings.serial_delimiter)) || + ((serial_in_byte != Settings.serial_delimiter) && (128 != Settings.serial_delimiter)) || + Settings.flag.mqtt_serial_raw)) { + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + serial_polling_window = millis(); + } else { + serial_polling_window = 0; + break; + } + } + } + +#ifdef USE_SONOFF_SC + + + + if (SONOFF_SC == my_module_type) { + if (serial_in_byte == '\x1B') { + serial_in_buffer[serial_in_byte_counter] = 0; + SonoffScSerialInput(serial_in_buffer); + serial_in_byte_counter = 0; + Serial.flush(); + return; + } + } else +#endif + + + if (!Settings.flag.mqtt_serial && (serial_in_byte == '\n')) { + serial_in_buffer[serial_in_byte_counter] = 0; + seriallog_level = (Settings.seriallog_level < LOG_LEVEL_INFO) ? (uint8_t)LOG_LEVEL_INFO : Settings.seriallog_level; + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), serial_in_buffer); + ExecuteCommand(serial_in_buffer, SRC_SERIAL); + serial_in_byte_counter = 0; + serial_polling_window = 0; + Serial.flush(); + return; + } + } + + if (Settings.flag.mqtt_serial && serial_in_byte_counter && (millis() > (serial_polling_window + SERIAL_POLLING))) { + serial_in_buffer[serial_in_byte_counter] = 0; + char hex_char[(serial_in_byte_counter * 2) + 2]; + bool assume_json = (!Settings.flag.mqtt_serial_raw && (serial_in_buffer[0] == '{')); + Response_P(PSTR("{\"" D_JSON_SERIALRECEIVED "\":%s%s%s}"), + (assume_json) ? "" : """", + (Settings.flag.mqtt_serial_raw) ? ToHex_P((unsigned char*)serial_in_buffer, serial_in_byte_counter, hex_char, sizeof(hex_char)) : serial_in_buffer, + (assume_json) ? "" : """"); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_SERIALRECEIVED)); + XdrvRulesProcess(); + serial_in_byte_counter = 0; + } +} + + + +void GpioInit(void) +{ + uint32_t mpin; + + if (!ValidModule(Settings.module)) { + uint32_t module = MODULE; + if (!ValidModule(MODULE)) { module = SONOFF_BASIC; } + Settings.module = module; + Settings.last_module = module; + } + SetModuleType(); + + if (Settings.module != Settings.last_module) { + Settings.baudrate = APP_BAUDRATE / 300; + Settings.serial_config = TS_SERIAL_8N1; + } + + for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) { + if ((Settings.user_template.gp.io[i] >= GPIO_SENSOR_END) && (Settings.user_template.gp.io[i] < GPIO_USER)) { + Settings.user_template.gp.io[i] = GPIO_USER; + } + } + + myio def_gp; + ModuleGpios(&def_gp); + for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { + if ((Settings.my_gp.io[i] >= GPIO_SENSOR_END) && (Settings.my_gp.io[i] < GPIO_USER)) { + Settings.my_gp.io[i] = GPIO_NONE; + } + else if (Settings.my_gp.io[i] > GPIO_NONE) { + my_module.io[i] = Settings.my_gp.io[i]; + } + if ((def_gp.io[i] > GPIO_NONE) && (def_gp.io[i] < GPIO_USER)) { + my_module.io[i] = def_gp.io[i]; + } + } + if ((Settings.my_adc0 >= ADC0_END) && (Settings.my_adc0 < ADC0_USER)) { + Settings.my_adc0 = ADC0_NONE; + } + else if (Settings.my_adc0 > ADC0_NONE) { + my_adc0 = Settings.my_adc0; + } + my_module_flag = ModuleFlag(); + uint32_t template_adc0 = my_module_flag.data &15; + if ((template_adc0 > ADC0_NONE) && (template_adc0 < ADC0_USER)) { + my_adc0 = template_adc0; + } + + for (uint32_t i = 0; i < GPIO_MAX; i++) { + pin[i] = 99; + } + for (uint32_t i = 0; i < sizeof(my_module.io); i++) { + mpin = ValidPin(i, my_module.io[i]); + + DEBUG_CORE_LOG(PSTR("INI: gpio pin %d, mpin %d"), i, mpin); + + if (mpin) { + XdrvMailbox.index = mpin; + XdrvMailbox.payload = i; + + if ((mpin >= GPIO_SWT1_NP) && (mpin < (GPIO_SWT1_NP + MAX_SWITCHES))) { + SwitchPullupFlag(mpin - GPIO_SWT1_NP); + mpin -= (GPIO_SWT1_NP - GPIO_SWT1); + } + else if ((mpin >= GPIO_KEY1_NP) && (mpin < (GPIO_KEY1_NP + MAX_KEYS))) { + ButtonPullupFlag(mpin - GPIO_KEY1_NP); + mpin -= (GPIO_KEY1_NP - GPIO_KEY1); + } + else if ((mpin >= GPIO_KEY1_INV) && (mpin < (GPIO_KEY1_INV + MAX_KEYS))) { + ButtonInvertFlag(mpin - GPIO_KEY1_INV); + mpin -= (GPIO_KEY1_INV - GPIO_KEY1); + } + else if ((mpin >= GPIO_KEY1_INV_NP) && (mpin < (GPIO_KEY1_INV_NP + MAX_KEYS))) { + ButtonPullupFlag(mpin - GPIO_KEY1_INV_NP); + ButtonInvertFlag(mpin - GPIO_KEY1_INV_NP); + mpin -= (GPIO_KEY1_INV_NP - GPIO_KEY1); + } + else if ((mpin >= GPIO_REL1_INV) && (mpin < (GPIO_REL1_INV + MAX_RELAYS))) { + bitSet(rel_inverted, mpin - GPIO_REL1_INV); + mpin -= (GPIO_REL1_INV - GPIO_REL1); + } + else if ((mpin >= GPIO_LED1_INV) && (mpin < (GPIO_LED1_INV + MAX_LEDS))) { + bitSet(led_inverted, mpin - GPIO_LED1_INV); + mpin -= (GPIO_LED1_INV - GPIO_LED1); + } + else if (mpin == GPIO_LEDLNK_INV) { + ledlnk_inverted = 1; + mpin -= (GPIO_LEDLNK_INV - GPIO_LEDLNK); + } + else if ((mpin >= GPIO_PWM1_INV) && (mpin < (GPIO_PWM1_INV + MAX_PWMS))) { + bitSet(pwm_inverted, mpin - GPIO_PWM1_INV); + mpin -= (GPIO_PWM1_INV - GPIO_PWM1); + } + else if (XdrvCall(FUNC_PIN_STATE)) { + mpin = XdrvMailbox.index; + } + else if (XsnsCall(FUNC_PIN_STATE)) { + mpin = XdrvMailbox.index; + }; + } + if (mpin) pin[mpin] = i; + } + + if ((2 == pin[GPIO_TXD]) || (H801 == my_module_type)) { Serial.set_tx(2); } + + analogWriteRange(Settings.pwm_range); + analogWriteFreq(Settings.pwm_frequency); + +#ifdef USE_SPI + spi_flg = ((((pin[GPIO_SPI_CS] < 99) && (pin[GPIO_SPI_CS] > 14)) || (pin[GPIO_SPI_CS] < 12)) || (((pin[GPIO_SPI_DC] < 99) && (pin[GPIO_SPI_DC] > 14)) || (pin[GPIO_SPI_DC] < 12))); + if (spi_flg) { + for (uint32_t i = 0; i < GPIO_MAX; i++) { + if ((pin[i] >= 12) && (pin[i] <=14)) pin[i] = 99; + } + my_module.io[12] = GPIO_SPI_MISO; + pin[GPIO_SPI_MISO] = 12; + my_module.io[13] = GPIO_SPI_MOSI; + pin[GPIO_SPI_MOSI] = 13; + my_module.io[14] = GPIO_SPI_CLK; + pin[GPIO_SPI_CLK] = 14; + } + soft_spi_flg = ((pin[GPIO_SSPI_CS] < 99) && (pin[GPIO_SSPI_SCLK] < 99) && ((pin[GPIO_SSPI_MOSI] < 99) || (pin[GPIO_SSPI_MOSI] < 99))); +#endif + +#ifdef USE_I2C + i2c_flg = ((pin[GPIO_I2C_SCL] < 99) && (pin[GPIO_I2C_SDA] < 99)); + if (i2c_flg) { + Wire.begin(pin[GPIO_I2C_SDA], pin[GPIO_I2C_SCL]); + } +#endif + + devices_present = 0; + light_type = LT_BASIC; + if (XdrvCall(FUNC_MODULE_INIT)) { + + } + else if (YTF_IR_BRIDGE == my_module_type) { + ClaimSerial(); + + } + else if (SONOFF_DUAL == my_module_type) { + devices_present = 2; + SetSerial(19200, TS_SERIAL_8N1); + } + else if (CH4 == my_module_type) { + devices_present = 4; + SetSerial(19200, TS_SERIAL_8N1); + } +#ifdef USE_SONOFF_SC + else if (SONOFF_SC == my_module_type) { + SetSerial(19200, TS_SERIAL_8N1); + } +#endif + + for (uint32_t i = 0; i < MAX_PWMS; i++) { + if (pin[GPIO_PWM1 +i] < 99) { + pinMode(pin[GPIO_PWM1 +i], OUTPUT); + if (light_type) { + + analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range : 0); + } else { + pwm_present = true; + analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - Settings.pwm_value[i] : Settings.pwm_value[i]); + } + } + } + + for (uint32_t i = 0; i < MAX_RELAYS; i++) { + if (pin[GPIO_REL1 +i] < 99) { + pinMode(pin[GPIO_REL1 +i], OUTPUT); + devices_present++; + if (EXS_RELAY == my_module_type) { + digitalWrite(pin[GPIO_REL1 +i], bitRead(rel_inverted, i) ? 1 : 0); + if (i &1) { devices_present--; } + } + } + } + + for (uint32_t i = 0; i < MAX_LEDS; i++) { + if (pin[GPIO_LED1 +i] < 99) { +#ifdef USE_ARILUX_RF + if ((3 == i) && (leds_present < 2) && (99 == pin[GPIO_ARIRFSEL])) { + pin[GPIO_ARIRFSEL] = pin[GPIO_LED4]; + pin[GPIO_LED4] = 99; + } else { +#endif + pinMode(pin[GPIO_LED1 +i], OUTPUT); + leds_present++; + digitalWrite(pin[GPIO_LED1 +i], bitRead(led_inverted, i)); +#ifdef USE_ARILUX_RF + } +#endif + } + } + if (pin[GPIO_LEDLNK] < 99) { + pinMode(pin[GPIO_LEDLNK], OUTPUT); + digitalWrite(pin[GPIO_LEDLNK], ledlnk_inverted); + } + + ButtonInit(); + SwitchInit(); +#ifdef ROTARY_V1 + RotaryInit(); +#endif + + SetLedPower(Settings.ledstate &8); + SetLedLink(Settings.ledstate &8); + + XdrvCall(FUNC_PRE_INIT); +} +# 1 "S:/Development/Tasmota/tasmota/support_udp.ino" +# 20 "S:/Development/Tasmota/tasmota/support_udp.ino" +#ifdef USE_EMULATION + +#define UDP_BUFFER_SIZE 200 +#define UDP_MSEARCH_SEND_DELAY 1500 + +#include +Ticker TickerMSearch; + +IPAddress udp_remote_ip; +uint16_t udp_remote_port; + +bool udp_connected = false; +bool udp_response_mutex = false; + + + + + +const char URN_BELKIN_DEVICE[] PROGMEM = "urn:belkin:device:**"; +const char URN_BELKIN_DEVICE_CAP[] PROGMEM = "urn:Belkin:device:**"; +const char UPNP_ROOTDEVICE[] PROGMEM = "upnp:rootdevice"; +const char SSDPSEARCH_ALL[] PROGMEM = "ssdpsearch:all"; +const char SSDP_ALL[] PROGMEM = "ssdp:all"; + + + + + +bool UdpDisconnect(void) +{ + if (udp_connected) { + PortUdp.flush(); + WiFiUDP::stopAll(); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED)); + udp_connected = false; + } + return udp_connected; +} + +bool UdpConnect(void) +{ + if (!udp_connected) { + + if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED)); + udp_response_mutex = false; + udp_connected = true; + } else { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED)); + udp_connected = false; + } + } + return udp_connected; +} + +void PollUdp(void) +{ + if (udp_connected) { + while (PortUdp.parsePacket()) { + char packet_buffer[UDP_BUFFER_SIZE]; + + int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1); + packet_buffer[len] = 0; + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); + + + + if (Settings.flag2.emulation) { +#ifdef USE_SCRIPT_HUE + if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { +#else + if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { +#endif + udp_response_mutex = true; + + udp_remote_ip = PortUdp.remoteIP(); + udp_remote_port = PortUdp.remotePort(); + + + + + uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); + + LowerCase(packet_buffer, packet_buffer); + RemoveSpace(packet_buffer); + +#ifdef USE_EMULATION_WEMO + if (EMUL_WEMO == Settings.flag2.emulation) { + if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { + TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1); + return; + } + else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || + (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || + (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { + TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2); + return; + } + } +#endif + +#ifdef USE_EMULATION_HUE + if (EMUL_HUE == Settings.flag2.emulation) { + if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) || + (strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || + (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || + (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { + TickerMSearch.attach_ms(response_delay, HueRespondToMSearch); + return; + } + } +#endif + + udp_response_mutex = false; + continue; + } + } + +#ifdef USE_DEVICE_GROUPS + if (Settings.flag4.device_groups_enabled && !strncmp_P(packet_buffer, kDeviceGroupMessage, sizeof(DEVICE_GROUP_MESSAGE) - 1)) { + ProcessDeviceGroupMessage(packet_buffer, len); + } +#endif + } + optimistic_yield(100); + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/support_wifi.ino" +# 24 "S:/Development/Tasmota/tasmota/support_wifi.ino" +#ifndef WIFI_RSSI_THRESHOLD +#define WIFI_RSSI_THRESHOLD 10 +#endif +#ifndef WIFI_RESCAN_MINUTES +#define WIFI_RESCAN_MINUTES 44 +#endif + +const uint8_t WIFI_CONFIG_SEC = 180; +const uint8_t WIFI_CHECK_SEC = 20; +const uint8_t WIFI_RETRY_OFFSET_SEC = 20; + +#include +#if LWIP_IPV6 +#include +#endif + +struct WIFI { + uint32_t last_event = 0; + uint32_t downtime = 0; + uint16_t link_count = 0; + uint8_t counter; + uint8_t retry_init; + uint8_t retry; + uint8_t status; + uint8_t config_type = 0; + uint8_t config_counter = 0; + uint8_t mdns_begun = 0; + uint8_t scan_state; + uint8_t bssid[6]; + int8_t best_network_db; +} Wifi; + +int WifiGetRssiAsQuality(int rssi) +{ + int quality = 0; + + if (rssi <= -100) { + quality = 0; + } else if (rssi >= -50) { + quality = 100; + } else { + quality = 2 * (rssi + 100); + } + return quality; +} + +bool WifiConfigCounter(void) +{ + if (Wifi.config_counter) { + Wifi.config_counter = WIFI_CONFIG_SEC; + } + return (Wifi.config_counter); +} + +void WifiConfig(uint8_t type) +{ + if (!Wifi.config_type) { + if ((WIFI_RETRY == type) || (WIFI_WAIT == type)) { return; } +#ifdef USE_EMULATION + UdpDisconnect(); +#endif + WiFi.disconnect(); + Wifi.config_type = type; + +#ifndef USE_WEBSERVER + if (WIFI_MANAGER == Wifi.config_type) { + Wifi.config_type = WIFI_SERIAL; + } +#endif + + Wifi.config_counter = WIFI_CONFIG_SEC; + Wifi.counter = Wifi.config_counter +5; + blinks = 1999; + if (WIFI_RESTART == Wifi.config_type) { + restart_flag = 2; + } + else if (WIFI_SERIAL == Wifi.config_type) { + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_6_SERIAL " " D_ACTIVE_FOR_3_MINUTES)); + } +#ifdef USE_WEBSERVER + else if (WIFI_MANAGER == Wifi.config_type || WIFI_MANAGER_RESET_ONLY == Wifi.config_type) { + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_2_WIFIMANAGER " " D_ACTIVE_FOR_3_MINUTES)); + WifiManagerBegin(WIFI_MANAGER_RESET_ONLY == Wifi.config_type); + } +#endif + } +} + +void WifiSetMode(WiFiMode_t wifi_mode) +{ + if (WiFi.getMode() == wifi_mode) { return; } + + if (wifi_mode != WIFI_OFF) { + + WiFi.forceSleepWake(); + delay(100); + } + + uint32_t retry = 2; + while (!WiFi.mode(wifi_mode) && retry--) { + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR("Retry set Mode...")); + delay(100); + } + + if (wifi_mode == WIFI_OFF) { + delay(1000); + WiFi.forceSleepBegin(); + delay(1); + } else { + delay(30); + } +} + +void WiFiSetSleepMode(void) +{ +# 152 "S:/Development/Tasmota/tasmota/support_wifi.ino" +#if defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) +#else + if (sleep && Settings.flag3.sleep_normal) { + WiFi.setSleepMode(WIFI_LIGHT_SLEEP); + } else { + WiFi.setSleepMode(WIFI_MODEM_SLEEP); + } +#endif + WifiSetOutputPower(); +} + +void WifiBegin(uint8_t flag, uint8_t channel) +{ + const char kWifiPhyMode[] = " BGN"; + +#ifdef USE_EMULATION + UdpDisconnect(); +#endif + +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR(D_PATCH_ISSUE_2186)); + + WifiSetMode(WIFI_OFF); +#endif + + WiFi.persistent(false); + WiFi.disconnect(true); + delay(200); + + WifiSetMode(WIFI_STA); + WiFiSetSleepMode(); + + + if (!WiFi.getAutoConnect()) { WiFi.setAutoConnect(true); } + + switch (flag) { + case 0: + case 1: + Settings.sta_active = flag; + break; + case 2: + Settings.sta_active ^= 1; + } + if (!strlen(SettingsText(SET_STASSID1 + Settings.sta_active))) { + Settings.sta_active ^= 1; + } + if (Settings.ip_address[0]) { + WiFi.config(Settings.ip_address[0], Settings.ip_address[1], Settings.ip_address[2], Settings.ip_address[3]); + } + WiFi.hostname(my_hostname); + + char stemp[40] = { 0 }; + if (channel) { + WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active), channel, Wifi.bssid); + + char hex_char[18]; + snprintf_P(stemp, sizeof(stemp), PSTR(" Channel %d BSSId %s"), channel, ToHex_P((unsigned char*)Wifi.bssid, 6, hex_char, sizeof(hex_char), ':')); + } else { + WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active)); + } + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CONNECTING_TO_AP "%d %s%s " D_IN_MODE " 11%c " D_AS " %s..."), + Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), stemp, kWifiPhyMode[WiFi.getPhyMode() & 0x3], my_hostname); + +#if LWIP_IPV6 + for (bool configured = false; !configured;) { + uint16_t cfgcnt = 0; + for (auto addr : addrList) { + if ((configured = !addr.isLocal() && addr.isV6()) || cfgcnt==30) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI "Got IPv6 global address %s"), addr.toString().c_str()); + break; + } + delay(500); + cfgcnt++; + } + } +#endif +} + +void WifiBeginAfterScan(void) +{ + + if (0 == Wifi.scan_state) { return; } + + if (1 == Wifi.scan_state) { + memset((void*) &Wifi.bssid, 0, sizeof(Wifi.bssid)); + Wifi.best_network_db = -127; + Wifi.scan_state = 3; + } + + if (2 == Wifi.scan_state) { + uint8_t* bssid = WiFi.BSSID(); + memcpy((void*) &Wifi.bssid, (void*) bssid, sizeof(Wifi.bssid)); + Wifi.best_network_db = WiFi.RSSI(); + if (Wifi.best_network_db < -WIFI_RSSI_THRESHOLD) { + Wifi.best_network_db += WIFI_RSSI_THRESHOLD; + } + Wifi.scan_state = 3; + } + + if (3 == Wifi.scan_state) { + if (WiFi.scanComplete() != WIFI_SCAN_RUNNING) { + WiFi.scanNetworks(true); + Wifi.scan_state++; + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR("Network (re)scan started...")); + return; + } + } + int8_t wifi_scan_result = WiFi.scanComplete(); + + if (4 == Wifi.scan_state) { + if (wifi_scan_result != WIFI_SCAN_RUNNING) { + Wifi.scan_state++; + } + } + + if (5 == Wifi.scan_state) { + int32_t channel = 0; + int8_t ap = 3; + uint8_t last_bssid[6]; + memcpy((void*) &last_bssid, (void*) &Wifi.bssid, sizeof(last_bssid)); + + if (wifi_scan_result > 0) { + + for (uint32_t i = 0; i < wifi_scan_result; ++i) { + + String ssid_scan; + int32_t rssi_scan; + uint8_t sec_scan; + uint8_t* bssid_scan; + int32_t chan_scan; + bool hidden_scan; + + WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, bssid_scan, chan_scan, hidden_scan); + + bool known = false; + uint32_t j; + for (j = 0; j < MAX_SSIDS; j++) { + if (ssid_scan == SettingsText(SET_STASSID1 + j)) { + known = true; + if (rssi_scan > Wifi.best_network_db) { + if (sec_scan == ENC_TYPE_NONE || SettingsText(SET_STAPWD1 + j)) { + Wifi.best_network_db = (int8_t)rssi_scan; + channel = chan_scan; + ap = j; + memcpy((void*) &Wifi.bssid, (void*) bssid_scan, sizeof(Wifi.bssid)); + } + } + break; + } + } + char hex_char[18]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "Network %d, AP%c, SSId %s, Channel %d, BSSId %s, RSSI %d, Encryption %d"), + i, + (known) ? (j) ? '2' : '1' : '-', + ssid_scan.c_str(), + chan_scan, + ToHex_P((unsigned char*)bssid_scan, 6, hex_char, sizeof(hex_char), ':'), + rssi_scan, + (sec_scan == ENC_TYPE_NONE) ? 0 : 1); + delay(0); + } + WiFi.scanDelete(); + delay(0); + } + Wifi.scan_state = 0; + + for (uint32_t i = 0; i < sizeof(Wifi.bssid); i++) { + if (last_bssid[i] != Wifi.bssid[i]) { + WifiBegin(ap, channel); + break; + } + } + } +} + +uint16_t WifiLinkCount(void) +{ + return Wifi.link_count; +} + +String WifiDowntime(void) +{ + return GetDuration(Wifi.downtime); +} + +void WifiSetState(uint8_t state) +{ + if (state == global_state.wifi_down) { + if (state) { + rules_flag.wifi_connected = 1; + Wifi.link_count++; + Wifi.downtime += UpTime() - Wifi.last_event; + } else { + rules_flag.wifi_disconnected = 1; + Wifi.last_event = UpTime(); + } + } + global_state.wifi_down = state ^1; +} + +#if LWIP_IPV6 +bool WifiCheckIPv6(void) +{ + bool ipv6_global=false; + + for (auto a : addrList) { + if(!a.isLocal() && a.isV6()) ipv6_global=true; + } + return ipv6_global; +} + +String WifiGetIPv6(void) +{ + for (auto a : addrList) { + if(!a.isLocal() && a.isV6()) return a.toString(); + } + return ""; +} + +bool WifiCheckIPAddrStatus(void) +{ + bool ip_global=false; + + for (auto a : addrList) { + if(!a.isLocal()) ip_global=true; + } + return ip_global; +} +#endif + +void WifiCheckIp(void) +{ +#if LWIP_IPV6 + if(WifiCheckIPAddrStatus()) { + Wifi.status = WL_CONNECTED; +#else + if ((WL_CONNECTED == WiFi.status()) && (static_cast(WiFi.localIP()) != 0)) { +#endif + WifiSetState(1); + Wifi.counter = WIFI_CHECK_SEC; + Wifi.retry = Wifi.retry_init; + if (Wifi.status != WL_CONNECTED) { + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECTED)); + + Settings.ip_address[1] = (uint32_t)WiFi.gatewayIP(); + Settings.ip_address[2] = (uint32_t)WiFi.subnetMask(); + Settings.ip_address[3] = (uint32_t)WiFi.dnsIP(); + } + Wifi.status = WL_CONNECTED; +#ifdef USE_DISCOVERY +#ifdef WEBSERVER_ADVERTISE + if (2 == Wifi.mdns_begun) { + MDNS.update(); + AddLog_P(LOG_LEVEL_DEBUG_MORE, D_LOG_MDNS, "MDNS.update"); + } +#endif +#endif + } else { + WifiSetState(0); + uint8_t wifi_config_tool = Settings.sta_config; + Wifi.status = WiFi.status(); + switch (Wifi.status) { + case WL_CONNECTED: + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_NO_IP_ADDRESS)); + Wifi.status = 0; + Wifi.retry = Wifi.retry_init; + break; + case WL_NO_SSID_AVAIL: + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_NOT_REACHED)); + if (WIFI_WAIT == Settings.sta_config) { + Wifi.retry = Wifi.retry_init; + } else { + if (Wifi.retry > (Wifi.retry_init / 2)) { + Wifi.retry = Wifi.retry_init / 2; + } + else if (Wifi.retry) { + Wifi.retry = 0; + } + } + break; + case WL_CONNECT_FAILED: + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_WRONG_PASSWORD)); + if (Wifi.retry > (Wifi.retry_init / 2)) { + Wifi.retry = Wifi.retry_init / 2; + } + else if (Wifi.retry) { + Wifi.retry = 0; + } + break; + default: + if (!Wifi.retry || ((Wifi.retry_init / 2) == Wifi.retry)) { + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_TIMEOUT)); + } else { + if (!strlen(SettingsText(SET_STASSID1)) && !strlen(SettingsText(SET_STASSID2))) { + wifi_config_tool = WIFI_MANAGER; + Wifi.retry = 0; + } else { + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR(D_ATTEMPTING_CONNECTION)); + } + } + } + if (Wifi.retry) { + if (Settings.flag3.use_wifi_scan) { + if (Wifi.retry_init == Wifi.retry) { + Wifi.scan_state = 1; + } + } else { + if (Wifi.retry_init == Wifi.retry) { + WifiBegin(3, 0); + } + if ((Settings.sta_config != WIFI_WAIT) && ((Wifi.retry_init / 2) == Wifi.retry)) { + WifiBegin(2, 0); + } + } + Wifi.counter = 1; + Wifi.retry--; + } else { + WifiConfig(wifi_config_tool); + Wifi.counter = 1; + Wifi.retry = Wifi.retry_init; + } + } +} + +void WifiCheck(uint8_t param) +{ + Wifi.counter--; + switch (param) { + case WIFI_SERIAL: + case WIFI_MANAGER: + WifiConfig(param); + break; + default: + if (Wifi.config_counter) { + Wifi.config_counter--; + Wifi.counter = Wifi.config_counter +5; + if (Wifi.config_counter) { + if (!Wifi.config_counter) { + if (strlen(WiFi.SSID().c_str())) { + SettingsUpdateText(SET_STASSID1, WiFi.SSID().c_str()); + } + if (strlen(WiFi.psk().c_str())) { + SettingsUpdateText(SET_STAPWD1, WiFi.psk().c_str()); + } + Settings.sta_active = 0; + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_WCFG_2_WIFIMANAGER D_CMND_SSID "1 %s"), SettingsText(SET_STASSID1)); + } + } + if (!Wifi.config_counter) { + + restart_flag = 2; + } + } else { + if (Wifi.scan_state) { WifiBeginAfterScan(); } + + if (Wifi.counter <= 0) { + AddLog_P(LOG_LEVEL_DEBUG_MORE, S_LOG_WIFI, PSTR(D_CHECKING_CONNECTION)); + Wifi.counter = WIFI_CHECK_SEC; + WifiCheckIp(); + } +#if LWIP_IPV6 + if (WifiCheckIPAddrStatus()) { +#else + if ((WL_CONNECTED == WiFi.status()) && (static_cast(WiFi.localIP()) != 0) && !Wifi.config_type) { +#endif + WifiSetState(1); + + if (Settings.flag3.use_wifi_rescan) { + if (!(uptime % (60 * WIFI_RESCAN_MINUTES))) { + Wifi.scan_state = 2; + } + } + +#ifdef FIRMWARE_MINIMAL + if (1 == RtcSettings.ota_loader) { + RtcSettings.ota_loader = 0; + ota_state_flag = 3; + } +#endif + +#ifdef USE_DISCOVERY + if (Settings.flag3.mdns_enabled) { + if (!Wifi.mdns_begun) { + + + + + + Wifi.mdns_begun = (uint8_t)MDNS.begin(my_hostname); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS "%s"), (Wifi.mdns_begun) ? D_INITIALIZED : D_FAILED); + + } + } +#endif + +#ifdef USE_WEBSERVER + if (Settings.webserver) { + StartWebserver(Settings.webserver, WiFi.localIP()); +#ifdef USE_DISCOVERY +#ifdef WEBSERVER_ADVERTISE + if (1 == Wifi.mdns_begun) { + Wifi.mdns_begun = 2; + MDNS.addService("http", "tcp", WEB_PORT); + } +#endif +#endif + } else { + StopWebserver(); + } +#ifdef USE_EMULATION +#ifdef USE_DEVICE_GROUPS + if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { UdpConnect(); } +#else + if (Settings.flag2.emulation) { UdpConnect(); } +#endif +#endif +#endif + +#ifdef USE_KNX + if (!knx_started && Settings.flag.knx_enabled) { + KNXStart(); + knx_started = true; + } +#endif + + } else { + WifiSetState(0); +#ifdef USE_EMULATION + UdpDisconnect(); +#endif + Wifi.mdns_begun = 0; +#ifdef USE_KNX + knx_started = false; +#endif + } + } + } +} + +int WifiState(void) +{ + int state = -1; + + if (!global_state.wifi_down) { state = WIFI_RESTART; } + if (Wifi.config_type) { state = Wifi.config_type; } + return state; +} + +String WifiGetOutputPower(void) +{ + char stemp1[TOPSZ]; + dtostrfd((float)(Settings.wifi_output_power) / 10, 1, stemp1); + return String(stemp1); +} + +void WifiSetOutputPower(void) +{ + WiFi.setOutputPower((float)(Settings.wifi_output_power) / 10); +} + +void WifiConnect(void) +{ + WifiSetState(0); + WifiSetOutputPower(); + WiFi.persistent(false); + Wifi.status = 0; + Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2); + Wifi.retry = Wifi.retry_init; + Wifi.counter = 1; +} + + + +void WifiDisconnect(void) +{ +#ifdef USE_WIFI_SDK_ERASE + SettingsSdkWifiErase(); +#else + + WiFi.persistent(true); + ETS_UART_INTR_DISABLE(); + wifi_station_disconnect(); + ETS_UART_INTR_ENABLE(); + WiFi.persistent(false); + delay(100); +#endif +} + +void WifiShutdown(void) +{ + delay(100); + if (Settings.flag.mqtt_enabled) { + MqttDisconnect(); + } + WifiDisconnect(); +} + +void EspRestart(void) +{ + WifiShutdown(); + CrashDumpClear(); + + ESP.reset(); +} +# 1 "S:/Development/Tasmota/tasmota/tasmota_ca.ino" +# 24 "S:/Development/Tasmota/tasmota/tasmota_ca.ino" +#ifdef USE_MQTT_TLS_CA_CERT + +#ifndef USE_MQTT_AWS_IOT +# 38 "S:/Development/Tasmota/tasmota/tasmota_ca.ino" +static const unsigned char PROGMEM TA0_DN[] = { + 0x30, 0x4A, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0A, + 0x13, 0x0D, 0x4C, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6E, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x1A, 0x4C, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6E, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, + 0x79, 0x20, 0x58, 0x33 +}; + +static const unsigned char PROGMEM TA0_RSA_N[] = { + 0x9C, 0xD3, 0x0C, 0xF0, 0x5A, 0xE5, 0x2E, 0x47, 0xB7, 0x72, 0x5D, 0x37, + 0x83, 0xB3, 0x68, 0x63, 0x30, 0xEA, 0xD7, 0x35, 0x26, 0x19, 0x25, 0xE1, + 0xBD, 0xBE, 0x35, 0xF1, 0x70, 0x92, 0x2F, 0xB7, 0xB8, 0x4B, 0x41, 0x05, + 0xAB, 0xA9, 0x9E, 0x35, 0x08, 0x58, 0xEC, 0xB1, 0x2A, 0xC4, 0x68, 0x87, + 0x0B, 0xA3, 0xE3, 0x75, 0xE4, 0xE6, 0xF3, 0xA7, 0x62, 0x71, 0xBA, 0x79, + 0x81, 0x60, 0x1F, 0xD7, 0x91, 0x9A, 0x9F, 0xF3, 0xD0, 0x78, 0x67, 0x71, + 0xC8, 0x69, 0x0E, 0x95, 0x91, 0xCF, 0xFE, 0xE6, 0x99, 0xE9, 0x60, 0x3C, + 0x48, 0xCC, 0x7E, 0xCA, 0x4D, 0x77, 0x12, 0x24, 0x9D, 0x47, 0x1B, 0x5A, + 0xEB, 0xB9, 0xEC, 0x1E, 0x37, 0x00, 0x1C, 0x9C, 0xAC, 0x7B, 0xA7, 0x05, + 0xEA, 0xCE, 0x4A, 0xEB, 0xBD, 0x41, 0xE5, 0x36, 0x98, 0xB9, 0xCB, 0xFD, + 0x6D, 0x3C, 0x96, 0x68, 0xDF, 0x23, 0x2A, 0x42, 0x90, 0x0C, 0x86, 0x74, + 0x67, 0xC8, 0x7F, 0xA5, 0x9A, 0xB8, 0x52, 0x61, 0x14, 0x13, 0x3F, 0x65, + 0xE9, 0x82, 0x87, 0xCB, 0xDB, 0xFA, 0x0E, 0x56, 0xF6, 0x86, 0x89, 0xF3, + 0x85, 0x3F, 0x97, 0x86, 0xAF, 0xB0, 0xDC, 0x1A, 0xEF, 0x6B, 0x0D, 0x95, + 0x16, 0x7D, 0xC4, 0x2B, 0xA0, 0x65, 0xB2, 0x99, 0x04, 0x36, 0x75, 0x80, + 0x6B, 0xAC, 0x4A, 0xF3, 0x1B, 0x90, 0x49, 0x78, 0x2F, 0xA2, 0x96, 0x4F, + 0x2A, 0x20, 0x25, 0x29, 0x04, 0xC6, 0x74, 0xC0, 0xD0, 0x31, 0xCD, 0x8F, + 0x31, 0x38, 0x95, 0x16, 0xBA, 0xA8, 0x33, 0xB8, 0x43, 0xF1, 0xB1, 0x1F, + 0xC3, 0x30, 0x7F, 0xA2, 0x79, 0x31, 0x13, 0x3D, 0x2D, 0x36, 0xF8, 0xE3, + 0xFC, 0xF2, 0x33, 0x6A, 0xB9, 0x39, 0x31, 0xC5, 0xAF, 0xC4, 0x8D, 0x0D, + 0x1D, 0x64, 0x16, 0x33, 0xAA, 0xFA, 0x84, 0x29, 0xB6, 0xD4, 0x0B, 0xC0, + 0xD8, 0x7D, 0xC3, 0x93 +}; + +static const unsigned char TA0_RSA_E[] = { + 0x01, 0x00, 0x01 +}; + +static const br_x509_trust_anchor PROGMEM LetsEncryptX3CrossSigned_TA = { + { (unsigned char *)TA0_DN, sizeof TA0_DN }, + BR_X509_TA_CA, + { + BR_KEYTYPE_RSA, + { .rsa = { + (unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N, + (unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E, + } } + } +}; + +#define TAs_NUM 1 + +#endif + +#ifdef USE_MQTT_AWS_IOT +# 106 "S:/Development/Tasmota/tasmota/tasmota_ca.ino" +const unsigned char PROGMEM TA0_DN[] = { + 0x30, 0x39, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x0F, 0x30, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x0A, + 0x13, 0x06, 0x41, 0x6D, 0x61, 0x7A, 0x6F, 0x6E, 0x31, 0x19, 0x30, 0x17, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x41, 0x6D, 0x61, 0x7A, 0x6F, + 0x6E, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31 +}; + +const unsigned char PROGMEM TA0_RSA_N[] = { + 0xB2, 0x78, 0x80, 0x71, 0xCA, 0x78, 0xD5, 0xE3, 0x71, 0xAF, 0x47, 0x80, + 0x50, 0x74, 0x7D, 0x6E, 0xD8, 0xD7, 0x88, 0x76, 0xF4, 0x99, 0x68, 0xF7, + 0x58, 0x21, 0x60, 0xF9, 0x74, 0x84, 0x01, 0x2F, 0xAC, 0x02, 0x2D, 0x86, + 0xD3, 0xA0, 0x43, 0x7A, 0x4E, 0xB2, 0xA4, 0xD0, 0x36, 0xBA, 0x01, 0xBE, + 0x8D, 0xDB, 0x48, 0xC8, 0x07, 0x17, 0x36, 0x4C, 0xF4, 0xEE, 0x88, 0x23, + 0xC7, 0x3E, 0xEB, 0x37, 0xF5, 0xB5, 0x19, 0xF8, 0x49, 0x68, 0xB0, 0xDE, + 0xD7, 0xB9, 0x76, 0x38, 0x1D, 0x61, 0x9E, 0xA4, 0xFE, 0x82, 0x36, 0xA5, + 0xE5, 0x4A, 0x56, 0xE4, 0x45, 0xE1, 0xF9, 0xFD, 0xB4, 0x16, 0xFA, 0x74, + 0xDA, 0x9C, 0x9B, 0x35, 0x39, 0x2F, 0xFA, 0xB0, 0x20, 0x50, 0x06, 0x6C, + 0x7A, 0xD0, 0x80, 0xB2, 0xA6, 0xF9, 0xAF, 0xEC, 0x47, 0x19, 0x8F, 0x50, + 0x38, 0x07, 0xDC, 0xA2, 0x87, 0x39, 0x58, 0xF8, 0xBA, 0xD5, 0xA9, 0xF9, + 0x48, 0x67, 0x30, 0x96, 0xEE, 0x94, 0x78, 0x5E, 0x6F, 0x89, 0xA3, 0x51, + 0xC0, 0x30, 0x86, 0x66, 0xA1, 0x45, 0x66, 0xBA, 0x54, 0xEB, 0xA3, 0xC3, + 0x91, 0xF9, 0x48, 0xDC, 0xFF, 0xD1, 0xE8, 0x30, 0x2D, 0x7D, 0x2D, 0x74, + 0x70, 0x35, 0xD7, 0x88, 0x24, 0xF7, 0x9E, 0xC4, 0x59, 0x6E, 0xBB, 0x73, + 0x87, 0x17, 0xF2, 0x32, 0x46, 0x28, 0xB8, 0x43, 0xFA, 0xB7, 0x1D, 0xAA, + 0xCA, 0xB4, 0xF2, 0x9F, 0x24, 0x0E, 0x2D, 0x4B, 0xF7, 0x71, 0x5C, 0x5E, + 0x69, 0xFF, 0xEA, 0x95, 0x02, 0xCB, 0x38, 0x8A, 0xAE, 0x50, 0x38, 0x6F, + 0xDB, 0xFB, 0x2D, 0x62, 0x1B, 0xC5, 0xC7, 0x1E, 0x54, 0xE1, 0x77, 0xE0, + 0x67, 0xC8, 0x0F, 0x9C, 0x87, 0x23, 0xD6, 0x3F, 0x40, 0x20, 0x7F, 0x20, + 0x80, 0xC4, 0x80, 0x4C, 0x3E, 0x3B, 0x24, 0x26, 0x8E, 0x04, 0xAE, 0x6C, + 0x9A, 0xC8, 0xAA, 0x0D +}; + +static const unsigned char PROGMEM TA0_RSA_E[] = { + 0x01, 0x00, 0x01 +}; + +const br_x509_trust_anchor PROGMEM AmazonRootCA1_TA = { + { (unsigned char *)TA0_DN, sizeof TA0_DN }, + BR_X509_TA_CA, + { + BR_KEYTYPE_RSA, + { .rsa = { + (unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N, + (unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E, + } } + } +}; + +#define TAs_NUM 1 + +#endif + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_01_webserver.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_01_webserver.ino" +#ifdef USE_WEBSERVER + + + + + + + +#define XDRV_01 1 + +#ifndef WIFI_SOFT_AP_CHANNEL +#define WIFI_SOFT_AP_CHANNEL 1 +#endif + +const uint16_t CHUNKED_BUFFER_SIZE = 400; + +const uint16_t HTTP_REFRESH_TIME = 2345; +#define HTTP_RESTART_RECONNECT_TIME 9000 +#define HTTP_OTA_RESTART_RECONNECT_TIME 20000 + +#include +#include + +#ifdef USE_RF_FLASH +uint8_t *efm8bb1_update = nullptr; +#endif + +enum UploadTypes { UPL_TASMOTA, UPL_SETTINGS, UPL_EFM8BB1, UPL_TASMOTASLAVE }; + +static const char * HEADER_KEYS[] = { "User-Agent", }; + +const char HTTP_HEADER[] PROGMEM = + "" + "" + "" + "" + "%s - %s" + + ""; + +const char HTTP_HEAD_STYLE1[] PROGMEM = + "" + + "" + "" + "

" +#ifdef FIRMWARE_MINIMAL + "

" D_MINIMAL_FIRMWARE_PLEASE_UPGRADE "

" +#endif + "
" +#ifdef LANGUAGE_MODULE_NAME + "

" D_MODULE " %s

" +#else + "

%s " D_MODULE "

" +#endif + "

%s

"; + +const char HTTP_MSG_SLIDER_GRADIENT[] PROGMEM = + "
" + "" + "
"; +const char HTTP_MSG_SLIDER_SHUTTER[] PROGMEM = + "
" D_CLOSE "" D_OPEN "
" + "
"; + +const char HTTP_MSG_RSTRT[] PROGMEM = + "
" D_DEVICE_WILL_RESTART "

"; + +const char HTTP_FORM_LOGIN[] PROGMEM = + "
" + "
" + "

" D_USER "

" + "

" D_PASSWORD "

" + "
" + "" + "
"; + +const char HTTP_FORM_TEMPLATE[] PROGMEM = + "
 " D_TEMPLATE_PARAMETERS " " + "
"; +const char HTTP_FORM_TEMPLATE_FLAG[] PROGMEM = + "

" + "
 " D_TEMPLATE_FLAGS " 

" + + "

"; + +const char HTTP_FORM_MODULE[] PROGMEM = + "
 " D_MODULE_PARAMETERS " " + "" + "

" D_MODULE_TYPE " (%s)

" + "
"; + +const char HTTP_FORM_WIFI[] PROGMEM = + "
 " D_WIFI_PARAMETERS " " + "" + "

" D_AP1_SSID " (" STA_SSID1 ")

" + "

" D_AP1_PASSWORD "

" + "

" D_AP2_SSID " (" STA_SSID2 ")

" + "

" D_AP2_PASSWORD "

" + "

" D_HOSTNAME " (%s)

" + "

" D_CORS_DOMAIN "

"; + +const char HTTP_FORM_LOG1[] PROGMEM = + "
 " D_LOGGING_PARAMETERS " " + ""; +const char HTTP_FORM_LOG2[] PROGMEM = + "

" D_SYSLOG_HOST " (" SYS_LOG_HOST ")

" + "

" D_SYSLOG_PORT " (" STR(SYS_LOG_PORT) ")

" + "

" D_TELEMETRY_PERIOD " (" STR(TELE_PERIOD) ")

"; + +const char HTTP_FORM_OTHER[] PROGMEM = + "
 " D_OTHER_PARAMETERS " " + "" + "

" + "
 " D_TEMPLATE " " + "

" + "

" D_ACTIVATE "

" + "
" + "
" + "" D_WEB_ADMIN_PASSWORD "

" + "
" + "" D_MQTT_ENABLE "
" + "
"; + +const char HTTP_FORM_END[] PROGMEM = + "
" + "" + "
"; + +const char HTTP_FORM_RST[] PROGMEM = + "
" + "
 " D_RESTORE_CONFIGURATION " "; +const char HTTP_FORM_UPG[] PROGMEM = + "
" + "
 " D_UPGRADE_BY_WEBSERVER " " + "
" + "
" D_OTA_URL "

" + "
" + "


" + "
 " D_UPGRADE_BY_FILE_UPLOAD " "; +const char HTTP_FORM_RST_UPG[] PROGMEM = + "
" + "

" + "
" + "
" + "
" + ""; + +const char HTTP_FORM_CMND[] PROGMEM = + "


" + "
" + "
" + + ""; + +const char HTTP_TABLE100[] PROGMEM = + "
"; + +const char HTTP_COUNTER[] PROGMEM = + "
"; + +const char HTTP_END[] PROGMEM = + "" + "" + "" + ""; + +const char HTTP_DEVICE_CONTROL[] PROGMEM = ""; +const char HTTP_DEVICE_STATE[] PROGMEM = ""; + +enum ButtonTitle { + BUTTON_RESTART, BUTTON_RESET_CONFIGURATION, + BUTTON_MAIN, BUTTON_CONFIGURATION, BUTTON_INFORMATION, BUTTON_FIRMWARE_UPGRADE, BUTTON_CONSOLE, + BUTTON_MODULE, BUTTON_WIFI, BUTTON_LOGGING, BUTTON_OTHER, BUTTON_TEMPLATE, BUTTON_BACKUP, BUTTON_RESTORE }; +const char kButtonTitle[] PROGMEM = + D_RESTART "|" D_RESET_CONFIGURATION "|" + D_MAIN_MENU "|" D_CONFIGURATION "|" D_INFORMATION "|" D_FIRMWARE_UPGRADE "|" D_CONSOLE "|" + D_CONFIGURE_MODULE "|" D_CONFIGURE_WIFI"|" D_CONFIGURE_LOGGING "|" D_CONFIGURE_OTHER "|" D_CONFIGURE_TEMPLATE "|" D_BACKUP_CONFIGURATION "|" D_RESTORE_CONFIGURATION; +const char kButtonAction[] PROGMEM = + ".|rt|" + ".|cn|in|up|cs|" + "md|wi|lg|co|tp|dl|rs"; +const char kButtonConfirm[] PROGMEM = D_CONFIRM_RESTART "|" D_CONFIRM_RESET_CONFIGURATION; + +enum CTypes { CT_HTML, CT_PLAIN, CT_XML, CT_JSON, CT_STREAM }; +const char kContentTypes[] PROGMEM = "text/html|text/plain|text/xml|application/json|application/octet-stream"; + +const char kLoggingOptions[] PROGMEM = D_SERIAL_LOG_LEVEL "|" D_WEB_LOG_LEVEL "|" D_MQTT_LOG_LEVEL "|" D_SYS_LOG_LEVEL; +const char kLoggingLevels[] PROGMEM = D_NONE "|" D_ERROR "|" D_INFO "|" D_DEBUG "|" D_MORE_DEBUG; + +const char kEmulationOptions[] PROGMEM = D_NONE "|" D_BELKIN_WEMO "|" D_HUE_BRIDGE; + +const char kUploadErrors[] PROGMEM = + D_UPLOAD_ERR_1 "|" D_UPLOAD_ERR_2 "|" D_UPLOAD_ERR_3 "|" D_UPLOAD_ERR_4 "|" D_UPLOAD_ERR_5 "|" D_UPLOAD_ERR_6 "|" D_UPLOAD_ERR_7 "|" D_UPLOAD_ERR_8 "|" D_UPLOAD_ERR_9 +#ifdef USE_RF_FLASH + "|" D_UPLOAD_ERR_10 "|" D_UPLOAD_ERR_11 "|" D_UPLOAD_ERR_12 "|" D_UPLOAD_ERR_13 +#endif + "|" D_UPLOAD_ERR_14 + ; + +const uint16_t DNS_PORT = 53; +enum HttpOptions {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER, HTTP_MANAGER_RESET_ONLY}; + +DNSServer *DnsServer; +ESP8266WebServer *WebServer; + +struct WEB { + String chunk_buffer = ""; + bool reset_web_log_flag = false; + uint8_t state = HTTP_OFF; + uint8_t upload_error = 0; + uint8_t upload_file_type; + uint8_t upload_progress_dot_count; + uint8_t config_block_count = 0; + uint8_t config_xor_on = 0; + uint8_t config_xor_on_set = CONFIG_FILE_XOR; +} Web; + + +static void WebGetArg(const char* arg, char* out, size_t max) +{ + String s = WebServer->arg(arg); + strlcpy(out, s.c_str(), max); + +} + +static bool WifiIsInManagerMode(){ + return (HTTP_MANAGER == Web.state || HTTP_MANAGER_RESET_ONLY == Web.state); +} + +void ShowWebSource(uint32_t source) +{ + if ((source > 0) && (source < SRC_MAX)) { + char stemp1[20]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SRC: %s from %s"), GetTextIndexed(stemp1, sizeof(stemp1), source, kCommandSource), WebServer->client().remoteIP().toString().c_str()); + } +} + +void ExecuteWebCommand(char* svalue, uint32_t source) +{ + ShowWebSource(source); + last_source = source; + ExecuteCommand(svalue, SRC_IGNORE); +} + +void StartWebserver(int type, IPAddress ipweb) +{ + if (!Settings.web_refresh) { Settings.web_refresh = HTTP_REFRESH_TIME; } + if (!Web.state) { + if (!WebServer) { + WebServer = new ESP8266WebServer((HTTP_MANAGER == type || HTTP_MANAGER_RESET_ONLY == type) ? 80 : WEB_PORT); + WebServer->on("/", HandleRoot); + WebServer->onNotFound(HandleNotFound); + WebServer->on("/up", HandleUpgradeFirmware); + WebServer->on("/u1", HandleUpgradeFirmwareStart); + WebServer->on("/u2", HTTP_POST, HandleUploadDone, HandleUploadLoop); + WebServer->on("/u2", HTTP_OPTIONS, HandlePreflightRequest); + WebServer->on("/cs", HTTP_GET, HandleConsole); + WebServer->on("/cs", HTTP_OPTIONS, HandlePreflightRequest); + WebServer->on("/cm", HandleHttpCommand); +#ifndef FIRMWARE_MINIMAL + WebServer->on("/cn", HandleConfiguration); + WebServer->on("/md", HandleModuleConfiguration); + WebServer->on("/wi", HandleWifiConfiguration); + WebServer->on("/lg", HandleLoggingConfiguration); + WebServer->on("/tp", HandleTemplateConfiguration); + WebServer->on("/co", HandleOtherConfiguration); + WebServer->on("/dl", HandleBackupConfiguration); + WebServer->on("/rs", HandleRestoreConfiguration); + WebServer->on("/rt", HandleResetConfiguration); + WebServer->on("/in", HandleInformation); + XdrvCall(FUNC_WEB_ADD_HANDLER); + XsnsCall(FUNC_WEB_ADD_HANDLER); +#endif + } + Web.reset_web_log_flag = false; + + + + WebServer->collectHeaders(HEADER_KEYS, sizeof(HEADER_KEYS)/sizeof(char*)); + + WebServer->begin(); + } + if (Web.state != type) { +#if LWIP_IPV6 + String ipv6_addr = WifiGetIPv6(); + if(ipv6_addr!="") AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s and IPv6 global address %s "), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str(),ipv6_addr.c_str()); + else AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str()); +#else + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str()); +#endif + rules_flag.http_init = 1; + } + if (type) { Web.state = type; } +} + +void StopWebserver(void) +{ + if (Web.state) { + WebServer->close(); + Web.state = HTTP_OFF; + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_STOPPED)); + } +} + +void WifiManagerBegin(bool reset_only) +{ + + if (!global_state.wifi_down) { + + WifiSetMode(WIFI_AP_STA); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT_AND_STATION)); + } else { + + WifiSetMode(WIFI_AP); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT)); + } + + StopWebserver(); + + DnsServer = new DNSServer(); + + int channel = WIFI_SOFT_AP_CHANNEL; + if ((channel < 1) || (channel > 13)) { channel = 1; } + +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + + WiFi.softAP(my_hostname, WIFI_AP_PASSPHRASE, channel, 0); +#else + + WiFi.softAP(my_hostname, WIFI_AP_PASSPHRASE, channel, 0, 1); +#endif + + delay(500); + + DnsServer->setErrorReplyCode(DNSReplyCode::NoError); + DnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); + + StartWebserver((reset_only ? HTTP_MANAGER_RESET_ONLY : HTTP_MANAGER), WiFi.softAPIP()); +} + +void PollDnsWebserver(void) +{ + if (DnsServer) { DnsServer->processNextRequest(); } + if (WebServer) { WebServer->handleClient(); } +} + + + +bool WebAuthenticate(void) +{ + if (strlen(SettingsText(SET_WEBPWD)) && (HTTP_MANAGER_RESET_ONLY != Web.state)) { + return WebServer->authenticate(WEB_USERNAME, SettingsText(SET_WEBPWD)); + } else { + return true; + } +} + +bool HttpCheckPriviledgedAccess(bool autorequestauth = true) +{ + if (HTTP_USER == Web.state) { + HandleRoot(); + return false; + } + if (autorequestauth && !WebAuthenticate()) { + WebServer->requestAuthentication(); + return false; + } + return true; +} + +void HttpHeaderCors(void) +{ + if (strlen(SettingsText(SET_CORS))) { + WebServer->sendHeader(F("Access-Control-Allow-Origin"), SettingsText(SET_CORS)); + } +} + +void WSHeaderSend(void) +{ + WebServer->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); + WebServer->sendHeader(F("Pragma"), F("no-cache")); + WebServer->sendHeader(F("Expires"), F("-1")); + HttpHeaderCors(); +} + + + + + +void WSSend(int code, int ctype, const String& content) +{ + char ct[25]; + WebServer->send(code, GetTextIndexed(ct, sizeof(ct), ctype, kContentTypes), content); +} + + + + + +void WSContentBegin(int code, int ctype) +{ + WebServer->client().flush(); + WSHeaderSend(); +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + WebServer->sendHeader(F("Accept-Ranges"),F("none")); + WebServer->sendHeader(F("Transfer-Encoding"),F("chunked")); +#endif + WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN); + WSSend(code, ctype, ""); + Web.chunk_buffer = ""; +} + +void _WSContentSend(const String& content) +{ + size_t len = content.length(); + +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + const char * footer = "\r\n"; + char chunk_size[11]; + sprintf(chunk_size, "%x\r\n", len); + WebServer->sendContent(String() + chunk_size + content + footer); +#else + WebServer->sendContent(content); +#endif + +#ifdef USE_DEBUG_DRIVER + ShowFreeMem(PSTR("WSContentSend")); +#endif + DEBUG_CORE_LOG(PSTR("WEB: Chunk size %d"), len); +} + +void WSContentFlush(void) +{ + if (Web.chunk_buffer.length() > 0) { + _WSContentSend(Web.chunk_buffer); + Web.chunk_buffer = ""; + } +} + +void _WSContentSendBuffer(void) +{ + int len = strlen(mqtt_data); + + if (0 == len) { + return; + } + else if (len == sizeof(mqtt_data)) { + AddLog_P(LOG_LEVEL_INFO, PSTR("HTP: Content too large")); + } + else if (len < CHUNKED_BUFFER_SIZE) { + Web.chunk_buffer += mqtt_data; + len = Web.chunk_buffer.length(); + } + + if (len >= CHUNKED_BUFFER_SIZE) { + WSContentFlush(); + } + if (strlen(mqtt_data) >= CHUNKED_BUFFER_SIZE) { + _WSContentSend(mqtt_data); + } +} + +void WSContentSend_P(const char* formatP, ...) +{ + + va_list arg; + va_start(arg, formatP); + int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg); + va_end(arg); + +#ifdef DEBUG_TASMOTA_CORE + if (len > (sizeof(mqtt_data) -1)) { + mqtt_data[33] = '\0'; + DEBUG_CORE_LOG(PSTR("ERROR: WSContentSend_P size %d > mqtt_data size %d. Start of data [%s...]"), len, sizeof(mqtt_data), mqtt_data); + } +#endif + + _WSContentSendBuffer(); +} + +void WSContentSend_PD(const char* formatP, ...) +{ + + va_list arg; + va_start(arg, formatP); + int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg); + va_end(arg); + +#ifdef DEBUG_TASMOTA_CORE + if (len > (sizeof(mqtt_data) -1)) { + mqtt_data[33] = '\0'; + DEBUG_CORE_LOG(PSTR("ERROR: WSContentSend_PD size %d > mqtt_data size %d. Start of data [%s...]"), len, sizeof(mqtt_data), mqtt_data); + } +#endif + + if (D_DECIMAL_SEPARATOR[0] != '.') { + for (uint32_t i = 0; i < len; i++) { + if ('.' == mqtt_data[i]) { + mqtt_data[i] = D_DECIMAL_SEPARATOR[0]; + } + } + } + + _WSContentSendBuffer(); +} + +void WSContentStart_P(const char* title, bool auth) +{ + if (auth && strlen(SettingsText(SET_WEBPWD)) && !WebServer->authenticate(WEB_USERNAME, SettingsText(SET_WEBPWD))) { + return WebServer->requestAuthentication(); + } + + WSContentBegin(200, CT_HTML); + + if (title != nullptr) { + char ctitle[strlen_P(title) +1]; + strcpy_P(ctitle, title); + WSContentSend_P(HTTP_HEADER, SettingsText(SET_FRIENDLYNAME1), ctitle); + } +} + +void WSContentStart_P(const char* title) +{ + WSContentStart_P(title, true); +} + +void WSContentSendStyle_P(const char* formatP, ...) +{ + if (WifiIsInManagerMode()) { + if (WifiConfigCounter()) { + WSContentSend_P(HTTP_SCRIPT_COUNTER); + } + } + WSContentSend_P(HTTP_HEAD_LAST_SCRIPT); + + WSContentSend_P(HTTP_HEAD_STYLE1, WebColor(COL_FORM), WebColor(COL_INPUT), WebColor(COL_INPUT_TEXT), WebColor(COL_INPUT), + WebColor(COL_INPUT_TEXT), WebColor(COL_CONSOLE), WebColor(COL_CONSOLE_TEXT), WebColor(COL_BACKGROUND)); + WSContentSend_P(HTTP_HEAD_STYLE2, WebColor(COL_BUTTON), WebColor(COL_BUTTON_TEXT), WebColor(COL_BUTTON_HOVER), + WebColor(COL_BUTTON_RESET), WebColor(COL_BUTTON_RESET_HOVER), WebColor(COL_BUTTON_SAVE), WebColor(COL_BUTTON_SAVE_HOVER), + WebColor(COL_BUTTON)); + if (formatP != nullptr) { + + va_list arg; + va_start(arg, formatP); + int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg); + va_end(arg); + +#ifdef DEBUG_TASMOTA_CORE + if (len > (sizeof(mqtt_data) -1)) { + mqtt_data[33] = '\0'; + DEBUG_CORE_LOG(PSTR("ERROR: WSContentSendStyle_P size %d > mqtt_data size %d. Start of data [%s...]"), len, sizeof(mqtt_data), mqtt_data); + } +#endif + + _WSContentSendBuffer(); + } + WSContentSend_P(HTTP_HEAD_STYLE3, WebColor(COL_TEXT), +#ifdef FIRMWARE_MINIMAL + WebColor(COL_TEXT_WARNING), +#endif + WebColor(COL_TITLE), + ModuleName().c_str(), SettingsText(SET_FRIENDLYNAME1)); + if (Settings.flag3.gui_hostname_ip) { + bool lip = (static_cast(WiFi.localIP()) != 0); + bool sip = (static_cast(WiFi.softAPIP()) != 0); + WSContentSend_P(PSTR("

%s%s (%s%s%s)

"), + my_hostname, + (Wifi.mdns_begun) ? ".local" : "", + (lip) ? WiFi.localIP().toString().c_str() : "", + (lip && sip) ? ", " : "", + (sip) ? WiFi.softAPIP().toString().c_str() : ""); + } + WSContentSend_P(PSTR("")); +} + +void WSContentSendStyle(void) +{ + WSContentSendStyle_P(nullptr); +} + +void WSContentButton(uint32_t title_index) +{ + char action[4]; + char title[100]; + + if (title_index <= BUTTON_RESET_CONFIGURATION) { + char confirm[100]; + WSContentSend_P(PSTR("

"), + GetTextIndexed(action, sizeof(action), title_index, kButtonAction), + GetTextIndexed(confirm, sizeof(confirm), title_index, kButtonConfirm), + (!title_index) ? "rst" : "non", + GetTextIndexed(title, sizeof(title), title_index, kButtonTitle)); + } else { + WSContentSend_P(PSTR("

"), + GetTextIndexed(action, sizeof(action), title_index, kButtonAction), + GetTextIndexed(title, sizeof(title), title_index, kButtonTitle)); + } +} + +void WSContentSpaceButton(uint32_t title_index) +{ + WSContentSend_P(PSTR("
")); + WSContentButton(title_index); +} + +void WSContentEnd(void) +{ + WSContentFlush(); + _WSContentSend(""); + WebServer->client().stop(); +} + +void WSContentStop(void) +{ + if (WifiIsInManagerMode()) { + if (WifiConfigCounter()) { + WSContentSend_P(HTTP_COUNTER); + } + } + WSContentSend_P(HTTP_END, my_version); + WSContentEnd(); +} + + + +void WebRestart(uint32_t type) +{ + + + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTART); + + bool reset_only = (HTTP_MANAGER_RESET_ONLY == Web.state); + + WSContentStart_P((type) ? S_SAVE_CONFIGURATION : S_RESTART, !reset_only); + WSContentSend_P(HTTP_SCRIPT_RELOAD); + WSContentSendStyle(); + if (type) { + WSContentSend_P(PSTR("
" D_CONFIGURATION_SAVED "
")); + if (2 == type) { + WSContentSend_P(PSTR("
" D_TRYING_TO_CONNECT "
")); + } + WSContentSend_P(PSTR("
")); + } + WSContentSend_P(HTTP_MSG_RSTRT); + if (HTTP_MANAGER == Web.state || reset_only) { + Web.state = HTTP_ADMIN; + } else { + WSContentSpaceButton(BUTTON_MAIN); + } + WSContentStop(); + + ShowWebSource(SRC_WEBGUI); + restart_flag = 2; +} + + + +void HandleWifiLogin(void) +{ + WSContentStart_P(S_CONFIGURE_WIFI, false); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_LOGIN); + + if (HTTP_MANAGER_RESET_ONLY == Web.state) { + WSContentSpaceButton(BUTTON_RESTART); +#ifndef FIRMWARE_MINIMAL + WSContentSpaceButton(BUTTON_RESET_CONFIGURATION); +#endif + } + + WSContentStop(); +} + +void WebSliderColdWarm(void) +{ + WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, + "a", + "#fff", "#ff0", + 1, + 153, 500, + LightGetColorTemp(), + 't', 0); +} + +void HandleRoot(void) +{ + if (CaptivePortal()) { return; } + + if (WebServer->hasArg("rst")) { + WebRestart(0); + return; + } + + if (WifiIsInManagerMode()) { +#ifndef FIRMWARE_MINIMAL + if (strlen(SettingsText(SET_WEBPWD)) && !(WebServer->hasArg("USER1")) && !(WebServer->hasArg("PASS1")) && HTTP_MANAGER_RESET_ONLY != Web.state) { + HandleWifiLogin(); + } else { + if (!strlen(SettingsText(SET_WEBPWD)) || (((WebServer->arg("USER1") == WEB_USERNAME ) && (WebServer->arg("PASS1") == SettingsText(SET_WEBPWD) )) || HTTP_MANAGER_RESET_ONLY == Web.state)) { + HandleWifiConfiguration(); + } else { + + HandleWifiLogin(); + } + } +#endif + return; + } + + if (HandleRootStatusRefresh()) { + return; + } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_MAIN_MENU); + + char stemp[33]; + + WSContentStart_P(S_MAIN_MENU); +#ifdef USE_SCRIPT_WEB_DISPLAY + WSContentSend_P(HTTP_SCRIPT_ROOT, Settings.web_refresh, Settings.web_refresh); +#else + WSContentSend_P(HTTP_SCRIPT_ROOT, Settings.web_refresh); +#endif + WSContentSend_P(HTTP_SCRIPT_ROOT_PART2); + + WSContentSendStyle(); + + WSContentSend_P(PSTR("
")); + if (devices_present) { +#ifdef USE_LIGHT + if (light_type) { + uint8_t light_subtype = light_type &7; + if (!Settings.flag3.pwm_multi_channels) { + bool split_white = ((LST_RGBW <= light_subtype) && (devices_present > 1)); + + if ((LST_COLDWARM == light_subtype) || ((LST_RGBCW == light_subtype) && !split_white)) { + WebSliderColdWarm(); + } + + if (light_subtype > 2) { + uint16_t hue; + uint8_t sat; + LightGetHSB(&hue, &sat, nullptr); + + WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, + "b", + "#800", "#f00 5%,#ff0 20%,#0f0 35%,#0ff 50%,#00f 65%,#f0f 80%,#f00 95%,#800", + 2, + 1, 359, + hue, + 'h', 0); + + uint8_t dcolor = changeUIntScale(Settings.light_dimmer, 0, 100, 0, 255); + char scolor[8]; + snprintf_P(scolor, sizeof(scolor), PSTR("#%02X%02X%02X"), dcolor, dcolor, dcolor); + uint8_t red, green, blue; + LightHsToRgb(hue, 255, &red, &green, &blue); + snprintf_P(stemp, sizeof(stemp), PSTR("#%02X%02X%02X"), red, green, blue); + + WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, + "s", + scolor, stemp, + 3, + 0, 100, + changeUIntScale(sat, 0, 255, 0, 100), + 'n', 0); + } + + WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, + "c", + "#000", "#fff", + 4, + Settings.flag3.slider_dimmer_stay_on, 100, + Settings.light_dimmer, + 'd', 0); + + if (split_white) { + if (LST_RGBCW == light_subtype) { + WebSliderColdWarm(); + } + WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, + "f", + "#000", "#fff", + 5, + Settings.flag3.slider_dimmer_stay_on, 100, + LightGetDimmer(2), + 'w', 0); + } + } else { + uint32_t pwm_channels = light_subtype > LST_MAX ? LST_MAX : light_subtype; + stemp[0] = 'e'; stemp[1] = '0'; stemp[2] = '\0'; + for (uint32_t i = 0; i < pwm_channels; i++) { + stemp[1]++; + + WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, + stemp, + "#000", "#fff", + i+1, + 1, 100, + changeUIntScale(Settings.light_color[i], 0, 255, 0, 100), + 'e', i+1); + } + } + } +#endif +#ifdef USE_SHUTTER + if (Settings.flag3.shutter_mode) { + for (uint32_t i = 0; i < shutters_present; i++) { + WSContentSend_P(HTTP_MSG_SLIDER_SHUTTER, Settings.shutter_position[i], i+1); + } + } +#endif + WSContentSend_P(HTTP_TABLE100); + WSContentSend_P(PSTR("
")); +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { + WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1, + (strlen(SettingsText(SET_BUTTON1))) ? SettingsText(SET_BUTTON1) : D_BUTTON_TOGGLE, + ""); + for (uint32_t i = 0; i < MaxFanspeed(); i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i); + WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2, + (strlen(SettingsText(SET_BUTTON2 + i))) ? SettingsText(SET_BUTTON2 + i) : stemp, + ""); + } + } else { +#endif + for (uint32_t idx = 1; idx <= devices_present; idx++) { + bool set_button = ((idx <= MAX_BUTTON_TEXT) && strlen(SettingsText(SET_BUTTON1 + idx -1))); +#ifdef USE_SHUTTER + int32_t ShutterWebButton; + if (ShutterWebButton = IsShutterWebButton(idx)) { + WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, + (set_button) ? SettingsText(SET_BUTTON1 + idx -1) : ((Settings.shutter_options[abs(ShutterWebButton)-1] & 2) ? "-" : ((ShutterWebButton>0) ? "▲" : "▼")), + ""); + continue; + } +#endif + snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx); + WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, + (set_button) ? SettingsText(SET_BUTTON1 + idx -1) : (devices_present < 5) ? D_BUTTON_TOGGLE : "", + (set_button) ? "" : (devices_present > 1) ? stemp : ""); + } +#ifdef USE_SONOFF_IFAN + } +#endif + WSContentSend_P(PSTR("
%s
")); + } +#ifdef USE_SONOFF_RF + if (SONOFF_BRIDGE == my_module_type) { + WSContentSend_P(HTTP_TABLE100); + WSContentSend_P(PSTR("")); + uint32_t idx = 0; + for (uint32_t i = 0; i < 4; i++) { + if (idx > 0) { WSContentSend_P(PSTR("")); } + for (uint32_t j = 0; j < 4; j++) { + idx++; + snprintf_P(stemp, sizeof(stemp), PSTR("%d"), idx); + WSContentSend_P(PSTR(""), idx, + (strlen(SettingsText(SET_BUTTON1 + idx -1))) ? SettingsText(SET_BUTTON1 + idx -1) : stemp); + } + } + WSContentSend_P(PSTR("")); + } +#endif + +#ifndef FIRMWARE_MINIMAL + XdrvCall(FUNC_WEB_ADD_MAIN_BUTTON); + XsnsCall(FUNC_WEB_ADD_MAIN_BUTTON); +#endif + + if (HTTP_ADMIN == Web.state) { +#ifdef FIRMWARE_MINIMAL + WSContentSpaceButton(BUTTON_FIRMWARE_UPGRADE); +#else + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentButton(BUTTON_INFORMATION); + WSContentButton(BUTTON_FIRMWARE_UPGRADE); +#endif + WSContentButton(BUTTON_CONSOLE); + WSContentButton(BUTTON_RESTART); + } + WSContentStop(); +} + +bool HandleRootStatusRefresh(void) +{ + if (!WebAuthenticate()) { + WebServer->requestAuthentication(); + return true; + } + + if (!WebServer->hasArg("m")) { + return false; + } + + #ifdef USE_SCRIPT_WEB_DISPLAY + Script_Check_HTML_Setvars(); + #endif + + char tmp[8]; + char svalue[32]; + char webindex[5]; + + WebGetArg("o", tmp, sizeof(tmp)); + if (strlen(tmp)) { + ShowWebSource(SRC_WEBGUI); + uint32_t device = atoi(tmp); +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { + if (device < 2) { + ExecuteCommandPower(1, POWER_TOGGLE, SRC_IGNORE); + } else { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_FANSPEED " %d"), device -2); + ExecuteCommand(svalue, SRC_WEBGUI); + } + } else { +#endif +#ifdef USE_SHUTTER + int32_t ShutterWebButton; + if (ShutterWebButton = IsShutterWebButton(device)) { + snprintf_P(svalue, sizeof(svalue), PSTR("ShutterPosition%d %s"), abs(ShutterWebButton), (ShutterWebButton>0) ? PSTR(D_CMND_SHUTTER_TOGGLEUP) : PSTR(D_CMND_SHUTTER_TOGGLEDOWN)); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } else { +#endif + ExecuteCommandPower(device, POWER_TOGGLE, SRC_IGNORE); +#ifdef USE_SHUTTER + } +#endif +#ifdef USE_SONOFF_IFAN + } +#endif + } + WebGetArg("d0", tmp, sizeof(tmp)); + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_DIMMER " %s"), tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } + WebGetArg("w0", tmp, sizeof(tmp)); + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_WHITE " %s"), tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } + uint32_t pwm_channels = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7); + for (uint32_t j = 1; j <= pwm_channels; j++) { + snprintf_P(webindex, sizeof(webindex), PSTR("e%d"), j); + WebGetArg(webindex, tmp, sizeof(tmp)); + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_CHANNEL "%d %s"), j, tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } + } + WebGetArg("t0", tmp, sizeof(tmp)); + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_COLORTEMPERATURE " %s"), tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } + WebGetArg("h0", tmp, sizeof(tmp)); + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_HSBCOLOR "1 %s"), tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } + WebGetArg("n0", tmp, sizeof(tmp)); + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_HSBCOLOR "2 %s"), tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } +#ifdef USE_SHUTTER + for (uint32_t j = 1; j <= shutters_present; j++) { + snprintf_P(webindex, sizeof(webindex), PSTR("u%d"), j); + WebGetArg(webindex, tmp, sizeof(tmp)); + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR("ShutterPosition%d %s"), j, tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } + } +#endif +#ifdef USE_SONOFF_RF + WebGetArg("k", tmp, sizeof(tmp)); + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RFKEY "%s"), tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } +#endif + WSContentBegin(200, CT_HTML); + WSContentSend_P(PSTR("{t}")); + XsnsCall(FUNC_WEB_SENSOR); +#ifdef USE_SCRIPT_WEB_DISPLAY + XdrvCall(FUNC_WEB_SENSOR); +#endif + + WSContentSend_P(PSTR("")); + + if (devices_present) { + WSContentSend_P(PSTR("{t}")); + uint32_t fsize = (devices_present < 5) ? 70 - (devices_present * 8) : 32; +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { + WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(power, 0)) ? "bold" : "normal", 54, GetStateText(bitRead(power, 0))); + uint32_t fanspeed = GetFanspeed(); + snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed); + WSContentSend_P(HTTP_DEVICE_STATE, 64, (fanspeed) ? "bold" : "normal", 54, (fanspeed) ? svalue : GetStateText(0)); + } else { +#endif + for (uint32_t idx = 1; idx <= devices_present; idx++) { + snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(power, idx -1)); + WSContentSend_P(HTTP_DEVICE_STATE, 100 / devices_present, (bitRead(power, idx -1)) ? "bold" : "normal", fsize, (devices_present < 5) ? GetStateText(bitRead(power, idx -1)) : svalue); + } +#ifdef USE_SONOFF_IFAN + } +#endif + WSContentSend_P(PSTR("")); + } + WSContentEnd(); + + return true; +} + +#ifdef USE_SHUTTER +int32_t IsShutterWebButton(uint32_t idx) { + + int32_t ShutterWebButton = 0; + if (Settings.flag3.shutter_mode) { + for (uint32_t i = 0; i < MAX_SHUTTERS; i++) { + if (Settings.shutter_startrelay[i] && ((Settings.shutter_startrelay[i] == idx) || (Settings.shutter_startrelay[i] == (idx-1)))) { + ShutterWebButton = (Settings.shutter_startrelay[i] == idx) ? (i+1): (-1-i); + break; + } + } + } + return ShutterWebButton; +} +#endif + + + +#ifndef FIRMWARE_MINIMAL + +void HandleConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURATION); + + WSContentStart_P(S_CONFIGURATION); + WSContentSendStyle(); + + WSContentButton(BUTTON_MODULE); + WSContentButton(BUTTON_WIFI); + + XdrvCall(FUNC_WEB_ADD_BUTTON); + XsnsCall(FUNC_WEB_ADD_BUTTON); + + WSContentButton(BUTTON_LOGGING); + WSContentButton(BUTTON_OTHER); + WSContentButton(BUTTON_TEMPLATE); + + WSContentSpaceButton(BUTTON_RESET_CONFIGURATION); + WSContentButton(BUTTON_BACKUP); + WSContentButton(BUTTON_RESTORE); + + WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); +} + + + +void HandleTemplateConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + if (WebServer->hasArg("save")) { + TemplateSaveSettings(); + WebRestart(1); + return; + } + + char stemp[30]; + + if (WebServer->hasArg("m")) { + WSContentBegin(200, CT_PLAIN); + for (uint32_t i = 0; i < sizeof(kModuleNiceList); i++) { + uint32_t midx = pgm_read_byte(kModuleNiceList + i); + WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, midx, AnyModuleName(midx).c_str(), midx +1); + } + WSContentEnd(); + return; + } + + WebGetArg("t", stemp, sizeof(stemp)); + if (strlen(stemp)) { + uint32_t module = atoi(stemp); + uint32_t module_save = Settings.module; + Settings.module = module; + myio cmodule; + ModuleGpios(&cmodule); + gpio_flag flag = ModuleFlag(); + Settings.module = module_save; + + WSContentBegin(200, CT_PLAIN); + WSContentSend_P(PSTR("%s}1"), AnyModuleName(module).c_str()); + for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) { + if (1 == i) { + WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, 255, D_SENSOR_USER, 255); + } + uint32_t midx = pgm_read_byte(kGpioNiceList + i); + WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, midx, GetTextIndexed(stemp, sizeof(stemp), midx, kSensorNames), midx); + } + WSContentSend_P(PSTR("}1")); + + for (uint32_t i = 0; i < ADC0_END; i++) { + if (1 == i) { + WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, ADC0_USER, D_SENSOR_USER, ADC0_USER); + } + WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, i, GetTextIndexed(stemp, sizeof(stemp), i, kAdc0Names), i); + } + WSContentSend_P(PSTR("}1")); + + for (uint32_t i = 0; i < sizeof(cmodule); i++) { + if ((i < 6) || ((i > 8) && (i != 11))) { + WSContentSend_P(PSTR("%s%d"), (i>0)?",":"", cmodule.io[i]); + } + } + WSContentSend_P(PSTR("}1%d}1%d"), flag, Settings.user_template_base); + WSContentEnd(); + return; + } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_TEMPLATE); + + WSContentStart_P(S_CONFIGURE_TEMPLATE); + WSContentSend_P(HTTP_SCRIPT_MODULE_TEMPLATE); + WSContentSend_P(HTTP_SCRIPT_TEMPLATE); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_TEMPLATE); + WSContentSend_P(HTTP_TABLE100); + WSContentSend_P(PSTR("" D_TEMPLATE_NAME "" + "" D_BASE_TYPE "" + "" + "
")); + WSContentSend_P(HTTP_TABLE100); + for (uint32_t i = 0; i < 17; i++) { + if ((i < 6) || ((i > 8) && (i != 11))) { + WSContentSend_P(PSTR("" D_GPIO "%d"), + ((9==i)||(10==i)) ? WebColor(COL_TEXT_WARNING) : WebColor(COL_TEXT), i, (0==i) ? " style='width:200px'" : "", i); + } + } + WSContentSend_P(PSTR("" D_ADC "0"), WebColor(COL_TEXT)); + WSContentSend_P(PSTR("")); + gpio_flag flag = ModuleFlag(); + if (flag.data > ADC0_USER) { + WSContentSend_P(HTTP_FORM_TEMPLATE_FLAG); + } + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void TemplateSaveSettings(void) +{ + char tmp[sizeof(Settings.user_template.name)]; + char webindex[5]; + char svalue[128]; + + WebGetArg("s1", tmp, sizeof(tmp)); + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_TEMPLATE " {\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), tmp); + + uint32_t j = 0; + for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) { + if (6 == i) { j = 9; } + if (8 == i) { j = 12; } + snprintf_P(webindex, sizeof(webindex), PSTR("g%d"), j); + WebGetArg(webindex, tmp, sizeof(tmp)); + uint8_t gpio = atoi(tmp); + snprintf_P(svalue, sizeof(svalue), PSTR("%s%s%d"), svalue, (i>0)?",":"", gpio); + j++; + } + + WebGetArg("g17", tmp, sizeof(tmp)); + uint32_t flag = atoi(tmp); + for (uint32_t i = 0; i < GPIO_FLAG_USED; i++) { + snprintf_P(webindex, sizeof(webindex), PSTR("c%d"), i); + uint32_t state = WebServer->hasArg(webindex) << i +4; + flag += state; + } + WebGetArg("g99", tmp, sizeof(tmp)); + uint32_t base = atoi(tmp) +1; + + snprintf_P(svalue, sizeof(svalue), PSTR("%s],\"" D_JSON_FLAG "\":%d,\"" D_JSON_BASE "\":%d}"), svalue, flag, base); + ExecuteWebCommand(svalue, SRC_WEBGUI); +} + + + +void HandleModuleConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + if (WebServer->hasArg("save")) { + ModuleSaveSettings(); + WebRestart(1); + return; + } + + char stemp[30]; + uint32_t midx; + myio cmodule; + ModuleGpios(&cmodule); + + if (WebServer->hasArg("m")) { + WSContentBegin(200, CT_PLAIN); + uint32_t vidx = 0; + for (uint32_t i = 0; i <= sizeof(kModuleNiceList); i++) { + if (0 == i) { + midx = USER_MODULE; + vidx = 0; + } else { + midx = pgm_read_byte(kModuleNiceList + i -1); + vidx = midx +1; + } + WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, midx, AnyModuleName(midx).c_str(), vidx); + } + WSContentEnd(); + return; + } + + if (WebServer->hasArg("g")) { + WSContentBegin(200, CT_PLAIN); + for (uint32_t j = 0; j < sizeof(kGpioNiceList); j++) { + midx = pgm_read_byte(kGpioNiceList + j); + if (!GetUsedInModule(midx, cmodule.io)) { + WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, midx, GetTextIndexed(stemp, sizeof(stemp), midx, kSensorNames), midx); + } + } + WSContentEnd(); + return; + } + +#ifndef USE_ADC_VCC + if (WebServer->hasArg("a")) { + WSContentBegin(200, CT_PLAIN); + for (uint32_t j = 0; j < ADC0_END; j++) { + WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, j, GetTextIndexed(stemp, sizeof(stemp), j, kAdc0Names), j); + } + WSContentEnd(); + return; + } +#endif + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_MODULE); + + WSContentStart_P(S_CONFIGURE_MODULE); + WSContentSend_P(HTTP_SCRIPT_MODULE_TEMPLATE); + WSContentSend_P(HTTP_SCRIPT_MODULE1, Settings.module); + for (uint32_t i = 0; i < sizeof(cmodule); i++) { + if (ValidGPIO(i, cmodule.io[i])) { + WSContentSend_P(PSTR("sk(%d,%d);"), my_module.io[i], i); + } + } + WSContentSend_P(HTTP_SCRIPT_MODULE2, Settings.my_adc0); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_MODULE, AnyModuleName(MODULE).c_str()); + for (uint32_t i = 0; i < sizeof(cmodule); i++) { + if (ValidGPIO(i, cmodule.io[i])) { + snprintf_P(stemp, 3, PINS_WEMOS +i*2); + char sesp8285[40]; + snprintf_P(sesp8285, sizeof(sesp8285), PSTR("ESP8285"), WebColor(COL_TEXT_WARNING)); + WSContentSend_P(PSTR("%s " D_GPIO "%d %s"), + (WEMOS==my_module_type)?stemp:"", i, (0==i)? D_SENSOR_BUTTON "1":(1==i)? D_SERIAL_OUT :(3==i)? D_SERIAL_IN :((9==i)||(10==i))? sesp8285 :(12==i)? D_SENSOR_RELAY "1":(13==i)? D_SENSOR_LED "1i":(14==i)? D_SENSOR :"", i); + } + } +#ifndef USE_ADC_VCC + if (ValidAdc()) { + WSContentSend_P(PSTR("%s " D_ADC "0"), (WEMOS==my_module_type)?"A0":""); + } +#endif + WSContentSend_P(PSTR("")); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void ModuleSaveSettings(void) +{ + char tmp[8]; + char webindex[5]; + + WebGetArg("g99", tmp, sizeof(tmp)); + uint32_t new_module = (!strlen(tmp)) ? MODULE : atoi(tmp); + Settings.last_module = Settings.module; + Settings.module = new_module; + SetModuleType(); + myio cmodule; + ModuleGpios(&cmodule); + String gpios = ""; + for (uint32_t i = 0; i < sizeof(cmodule); i++) { + if (Settings.last_module != new_module) { + Settings.my_gp.io[i] = GPIO_NONE; + } else { + if (ValidGPIO(i, cmodule.io[i])) { + snprintf_P(webindex, sizeof(webindex), PSTR("g%d"), i); + WebGetArg(webindex, tmp, sizeof(tmp)); + Settings.my_gp.io[i] = (!strlen(tmp)) ? 0 : atoi(tmp); + gpios += F(", " D_GPIO ); gpios += String(i); gpios += F(" "); gpios += String(Settings.my_gp.io[i]); + } + } + } +#ifndef USE_ADC_VCC + WebGetArg("g17", tmp, sizeof(tmp)); + Settings.my_adc0 = (!strlen(tmp)) ? 0 : atoi(tmp); + gpios += F(", " D_ADC "0 "); gpios += String(Settings.my_adc0); +#endif + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MODULE "%s " D_CMND_MODULE "%s"), ModuleName().c_str(), gpios.c_str()); +} + + + +const char kUnescapeCode[] = "&><\"\'"; +const char kEscapeCode[] PROGMEM = "&|>|<|"|'"; + +String HtmlEscape(const String unescaped) { + char escaped[10]; + size_t ulen = unescaped.length(); + String result = ""; + for (size_t i = 0; i < ulen; i++) { + char c = unescaped[i]; + char *p = strchr(kUnescapeCode, c); + if (p != nullptr) { + result += GetTextIndexed(escaped, sizeof(escaped), p - kUnescapeCode, kEscapeCode); + } else { + result += c; + } + } + return result; +} + + +const char kEncryptionType[] PROGMEM = "|||" D_WPA_PSK "||" D_WPA2_PSK "|" D_WEP "||" D_NONE "|" D_AUTO; + +void HandleWifiConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess(!WifiIsInManagerMode())) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_WIFI); + + if (WebServer->hasArg("save") && HTTP_MANAGER_RESET_ONLY != Web.state) { + WifiSaveSettings(); + WebRestart(2); + return; + } + + WSContentStart_P(S_CONFIGURE_WIFI, !WifiIsInManagerMode()); + WSContentSend_P(HTTP_SCRIPT_WIFI); + WSContentSendStyle(); + + if (HTTP_MANAGER_RESET_ONLY != Web.state) { + if (WebServer->hasArg("scan")) { +#ifdef USE_EMULATION + UdpDisconnect(); +#endif + int n = WiFi.scanNetworks(); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_SCAN_DONE)); + + if (0 == n) { + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, S_NO_NETWORKS_FOUND); + WSContentSend_P(S_NO_NETWORKS_FOUND); + WSContentSend_P(PSTR(". " D_REFRESH_TO_SCAN_AGAIN ".")); + } else { + + int indices[n]; + for (uint32_t i = 0; i < n; i++) { + indices[i] = i; + } + + + for (uint32_t i = 0; i < n; i++) { + for (uint32_t j = i + 1; j < n; j++) { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { + std::swap(indices[i], indices[j]); + } + } + } + + + String cssid; + for (uint32_t i = 0; i < n; i++) { + if (-1 == indices[i]) { continue; } + cssid = WiFi.SSID(indices[i]); + uint32_t cschn = WiFi.channel(indices[i]); + for (uint32_t j = i + 1; j < n; j++) { + if ((cssid == WiFi.SSID(indices[j])) && (cschn == WiFi.channel(indices[j]))) { + DEBUG_CORE_LOG(PSTR(D_LOG_WIFI D_DUPLICATE_ACCESSPOINT " %s"), WiFi.SSID(indices[j]).c_str()); + indices[j] = -1; + } + } + } + + + for (uint32_t i = 0; i < n; i++) { + if (-1 == indices[i]) { continue; } + int32_t rssi = WiFi.RSSI(indices[i]) + DEBUG_CORE_LOG(PSTR(D_LOG_WIFI D_SSID " %s, " D_BSSID " %s, " D_CHANNEL " %d, " D_RSSI " %d"), + WiFi.SSID(indices[i]).c_str(), WiFi.BSSIDstr(indices[i]).c_str(), WiFi.channel(indices[i]), rssi); + int quality = WifiGetRssiAsQuality(rssi); + int auth = WiFi.encryptionType(indices[i]); + char encryption[20]; + WSContentSend_P(PSTR("
%s (%d) %s %d%% (%d dBm)
"), + HtmlEscape(WiFi.SSID(indices[i])).c_str(), + WiFi.channel(indices[i]), + GetTextIndexed(encryption, sizeof(encryption), auth +1, kEncryptionType), + quality, rssi + ); + delay(0); + + } + WSContentSend_P(PSTR("
")); + } + } else { + WSContentSend_P(PSTR("
")); + } + + + WSContentSend_P(HTTP_FORM_WIFI, SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), WIFI_HOSTNAME, WIFI_HOSTNAME, SettingsText(SET_HOSTNAME), SettingsText(SET_CORS)); + WSContentSend_P(HTTP_FORM_END); + } + + if (WifiIsInManagerMode()) { +#ifndef FIRMWARE_MINIMAL + WSContentSpaceButton(BUTTON_RESTORE); + WSContentButton(BUTTON_RESET_CONFIGURATION); +#endif + WSContentSpaceButton(BUTTON_RESTART); + } else { + WSContentSpaceButton(BUTTON_CONFIGURATION); + } + WSContentStop(); +} + +void WifiSaveSettings(void) +{ + char tmp[TOPSZ]; + + WebGetArg("h", tmp, sizeof(tmp)); + SettingsUpdateText(SET_HOSTNAME, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp); + if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) { + SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME); + } + WebGetArg("c", tmp, sizeof(tmp)); + SettingsUpdateText(SET_CORS, (!strlen(tmp)) ? CORS_DOMAIN : tmp); + WebGetArg("s1", tmp, sizeof(tmp)); + SettingsUpdateText(SET_STASSID1, (!strlen(tmp)) ? STA_SSID1 : tmp); + WebGetArg("s2", tmp, sizeof(tmp)); + SettingsUpdateText(SET_STASSID2, (!strlen(tmp)) ? STA_SSID2 : tmp); + WebGetArg("p1", tmp, sizeof(tmp)); + SettingsUpdateText(SET_STAPWD1, (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? SettingsText(SET_STAPWD1) : tmp); + WebGetArg("p2", tmp, sizeof(tmp)); + SettingsUpdateText(SET_STAPWD2, (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? SettingsText(SET_STAPWD2) : tmp); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CMND_HOSTNAME " %s, " D_CMND_SSID "1 %s, " D_CMND_SSID "2 %s, " D_CMND_CORS " %s"), + SettingsText(SET_HOSTNAME), SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), SettingsText(SET_CORS)); +} + + + +void HandleLoggingConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_LOGGING); + + if (WebServer->hasArg("save")) { + LoggingSaveSettings(); + HandleConfiguration(); + return; + } + + WSContentStart_P(S_CONFIGURE_LOGGING); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_LOG1); + char stemp1[45]; + char stemp2[32]; + uint8_t dlevel[4] = { LOG_LEVEL_INFO, LOG_LEVEL_INFO, LOG_LEVEL_NONE, LOG_LEVEL_NONE }; + for (uint32_t idx = 0; idx < 4; idx++) { + if ((2==idx) && !Settings.flag.mqtt_enabled) { continue; } + uint32_t llevel = (0==idx)?Settings.seriallog_level:(1==idx)?Settings.weblog_level:(2==idx)?Settings.mqttlog_level:Settings.syslog_level; + WSContentSend_P(PSTR("

%s (%s)

")); + } + WSContentSend_P(HTTP_FORM_LOG2, SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, Settings.tele_period); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void LoggingSaveSettings(void) +{ + char tmp[TOPSZ]; + + WebGetArg("l0", tmp, sizeof(tmp)); + SetSeriallog((!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp)); + WebGetArg("l1", tmp, sizeof(tmp)); + Settings.weblog_level = (!strlen(tmp)) ? WEB_LOG_LEVEL : atoi(tmp); + WebGetArg("l2", tmp, sizeof(tmp)); + Settings.mqttlog_level = (!strlen(tmp)) ? MQTT_LOG_LEVEL : atoi(tmp); + WebGetArg("l3", tmp, sizeof(tmp)); + SetSyslog((!strlen(tmp)) ? SYS_LOG_LEVEL : atoi(tmp)); + WebGetArg("lh", tmp, sizeof(tmp)); + SettingsUpdateText(SET_SYSLOG_HOST, (!strlen(tmp)) ? SYS_LOG_HOST : tmp); + WebGetArg("lp", tmp, sizeof(tmp)); + Settings.syslog_port = (!strlen(tmp)) ? SYS_LOG_PORT : atoi(tmp); + WebGetArg("lt", tmp, sizeof(tmp)); + Settings.tele_period = (!strlen(tmp)) ? TELE_PERIOD : atoi(tmp); + if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) { + Settings.tele_period = 10; + } + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_LOG D_CMND_SERIALLOG " %d, " D_CMND_WEBLOG " %d, " D_CMND_MQTTLOG " %d, " D_CMND_SYSLOG " %d, " D_CMND_LOGHOST " %s, " D_CMND_LOGPORT " %d, " D_CMND_TELEPERIOD " %d"), + Settings.seriallog_level, Settings.weblog_level, Settings.mqttlog_level, Settings.syslog_level, SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, Settings.tele_period); +} + + + +void HandleOtherConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_OTHER); + + if (WebServer->hasArg("save")) { + OtherSaveSettings(); + WebRestart(1); + return; + } + + WSContentStart_P(S_CONFIGURE_OTHER); + WSContentSendStyle(); + + TemplateJson(); + char stemp[strlen(mqtt_data) +1]; + strlcpy(stemp, mqtt_data, sizeof(stemp)); + WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "", (Settings.flag.mqtt_enabled) ? " checked" : ""); + + uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present; +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { maxfn = 1; } +#endif + for (uint32_t i = 0; i < maxfn; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i +1); + WSContentSend_P(PSTR("" D_FRIENDLY_NAME " %d (" FRIENDLY_NAME "%s)

"), + i +1, + (i) ? stemp : "", + i, + (i) ? stemp : "", + SettingsText(SET_FRIENDLYNAME1 + i)); + } + +#ifdef USE_EMULATION + WSContentSend_P(PSTR("

 " D_EMULATION " 

")); + for (uint32_t i = 0; i < EMUL_MAX; i++) { +#ifndef USE_EMULATION_WEMO + if (i == EMUL_WEMO) { i++; } +#endif +#ifndef USE_EMULATION_HUE + if (i == EMUL_HUE) { i++; } +#endif + if (i < EMUL_MAX) { + WSContentSend_P(PSTR("%s %s
"), + i, i, + (i == Settings.flag2.emulation) ? " checked" : "", + GetTextIndexed(stemp, sizeof(stemp), i, kEmulationOptions), + (i == EMUL_NONE) ? "" : (i == EMUL_WEMO) ? D_SINGLE_DEVICE : D_MULTI_DEVICE); + } + } + WSContentSend_P(PSTR("

")); +#endif + + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void OtherSaveSettings(void) +{ + char tmp[TOPSZ]; + char webindex[5]; + char friendlyname[TOPSZ]; + char message[LOGSZ]; + + WebGetArg("wp", tmp, sizeof(tmp)); + SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp); + Settings.flag.mqtt_enabled = WebServer->hasArg("b1"); +#ifdef USE_EMULATION + WebGetArg("b2", tmp, sizeof(tmp)); + Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp); +#endif + + 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); + for (uint32_t i = 0; i < MAX_FRIENDLYNAMES; i++) { + snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i); + WebGetArg(webindex, tmp, sizeof(tmp)); + snprintf_P(friendlyname, sizeof(friendlyname), PSTR(FRIENDLY_NAME"%d"), i +1); + SettingsUpdateText(SET_FRIENDLYNAME1 +i, (!strlen(tmp)) ? (i) ? friendlyname : FRIENDLY_NAME : tmp); + snprintf_P(message, sizeof(message), PSTR("%s%s %s"), message, (i) ? "," : "", SettingsText(SET_FRIENDLYNAME1 +i)); + } + AddLog_P(LOG_LEVEL_INFO, message); + + WebGetArg("t1", tmp, sizeof(tmp)); + if (strlen(tmp)) { + char svalue[128]; + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_TEMPLATE " %s"), tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + + if (WebServer->hasArg("t2")) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_MODULE " 0")); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } + + } +} + + + +void HandleBackupConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_BACKUP_CONFIGURATION)); + + if (!SettingsBufferAlloc()) { return; } + + WiFiClient myClient = WebServer->client(); + WebServer->setContentLength(sizeof(Settings)); + + char attachment[TOPSZ]; + + + + + char hostname[sizeof(my_hostname)]; + snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(hostname, my_hostname), my_version); + + WebServer->sendHeader(F("Content-Disposition"), attachment); + + WSSend(200, CT_STREAM, ""); + + uint32_t cfg_crc32 = Settings.cfg_crc32; + Settings.cfg_crc32 = GetSettingsCrc32(); + + memcpy(settings_buffer, &Settings, sizeof(Settings)); + if (Web.config_xor_on_set) { + for (uint32_t i = 2; i < sizeof(Settings); i++) { + settings_buffer[i] ^= (Web.config_xor_on_set +i); + } + } + +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + size_t written = myClient.write((const char*)settings_buffer, sizeof(Settings)); + if (written < sizeof(Settings)) { + myClient.write((const char*)settings_buffer +written, sizeof(Settings) -written); + } +#else + myClient.write((const char*)settings_buffer, sizeof(Settings)); +#endif + + SettingsBufferFree(); + + Settings.cfg_crc32 = cfg_crc32; +} + + + +void HandleResetConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess(!WifiIsInManagerMode())) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESET_CONFIGURATION); + + WSContentStart_P(S_RESET_CONFIGURATION, !WifiIsInManagerMode()); + WSContentSendStyle(); + WSContentSend_P(PSTR("
" D_CONFIGURATION_RESET "
")); + WSContentSend_P(HTTP_MSG_RSTRT); + WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); + + char command[CMDSZ]; + snprintf_P(command, sizeof(command), PSTR(D_CMND_RESET " 1")); + ExecuteWebCommand(command, SRC_WEBGUI); +} + +void HandleRestoreConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTORE_CONFIGURATION); + + WSContentStart_P(S_RESTORE_CONFIGURATION); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_RST); + WSContentSend_P(HTTP_FORM_RST_UPG, D_RESTORE); + if (WifiIsInManagerMode()) { + WSContentSpaceButton(BUTTON_MAIN); + } else { + WSContentSpaceButton(BUTTON_CONFIGURATION); + } + WSContentStop(); + + Web.upload_error = 0; + Web.upload_file_type = UPL_SETTINGS; +} + + + +void HandleInformation(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_INFORMATION); + + char stopic[TOPSZ]; + + int freeMem = ESP.getFreeHeap(); + + WSContentStart_P(S_INFORMATION); + + + + WSContentSend_P(HTTP_SCRIPT_INFO_BEGIN); + WSContentSend_P(PSTR("
")); + WSContentSend_P(PSTR(D_PROGRAM_VERSION "}2%s%s"), my_version, my_image); + WSContentSend_P(PSTR("}1" D_BUILD_DATE_AND_TIME "}2%s"), GetBuildDateAndTime().c_str()); + WSContentSend_P(PSTR("}1" D_CORE_AND_SDK_VERSION "}2" ARDUINO_ESP8266_RELEASE "/%s"), ESP.getSdkVersion()); + WSContentSend_P(PSTR("}1" D_UPTIME "}2%s"), GetUptime().c_str()); + WSContentSend_P(PSTR("}1" D_FLASH_WRITE_COUNT "}2%d at 0x%X"), Settings.save_flag, GetSettingsAddress()); + WSContentSend_P(PSTR("}1" D_BOOT_COUNT "}2%d"), Settings.bootcount); + WSContentSend_P(PSTR("}1" D_RESTART_REASON "}2%s"), GetResetReason().c_str()); + uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present; +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { maxfn = 1; } +#endif + for (uint32_t i = 0; i < maxfn; i++) { + WSContentSend_P(PSTR("}1" D_FRIENDLY_NAME " %d}2%s"), i +1, SettingsText(SET_FRIENDLYNAME1 +i)); + } + WSContentSend_P(PSTR("}1}2 ")); + int32_t rssi = WiFi.RSSI(); + WSContentSend_P(PSTR("}1" D_AP "%d " D_SSID " (" D_RSSI ")}2%s (%d%%, %d dBm)"), Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), WifiGetRssiAsQuality(rssi), rssi); + WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), my_hostname, (Wifi.mdns_begun) ? ".local" : ""); +#if LWIP_IPV6 + String ipv6_addr = WifiGetIPv6(); + if(ipv6_addr != ""){ + WSContentSend_P(PSTR("}1 IPv6 Address }2%s"), ipv6_addr.c_str()); + } +#endif + if (static_cast(WiFi.localIP()) != 0) { + WSContentSend_P(PSTR("}1" D_IP_ADDRESS "}2%s"), WiFi.localIP().toString().c_str()); + WSContentSend_P(PSTR("}1" D_GATEWAY "}2%s"), IPAddress(Settings.ip_address[1]).toString().c_str()); + WSContentSend_P(PSTR("}1" D_SUBNET_MASK "}2%s"), IPAddress(Settings.ip_address[2]).toString().c_str()); + WSContentSend_P(PSTR("}1" D_DNS_SERVER "}2%s"), IPAddress(Settings.ip_address[3]).toString().c_str()); + WSContentSend_P(PSTR("}1" D_MAC_ADDRESS "}2%s"), WiFi.macAddress().c_str()); + } + if (static_cast(WiFi.softAPIP()) != 0) { + WSContentSend_P(PSTR("}1" D_IP_ADDRESS "}2%s"), WiFi.softAPIP().toString().c_str()); + WSContentSend_P(PSTR("}1" D_GATEWAY "}2%s"), WiFi.softAPIP().toString().c_str()); + WSContentSend_P(PSTR("}1" D_MAC_ADDRESS "}2%s"), WiFi.softAPmacAddress().c_str()); + } + WSContentSend_P(PSTR("}1}2 ")); + if (Settings.flag.mqtt_enabled) { + WSContentSend_P(PSTR("}1" D_MQTT_HOST "}2%s"), SettingsText(SET_MQTT_HOST)); + WSContentSend_P(PSTR("}1" D_MQTT_PORT "}2%d"), Settings.mqtt_port); + WSContentSend_P(PSTR("}1" D_MQTT_USER "}2%s"), SettingsText(SET_MQTT_USER)); + WSContentSend_P(PSTR("}1" D_MQTT_CLIENT "}2%s"), mqtt_client); + WSContentSend_P(PSTR("}1" D_MQTT_TOPIC "}2%s"), SettingsText(SET_MQTT_TOPIC)); + + WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC "}2%s"), GetGroupTopic_P(stopic, "")); + WSContentSend_P(PSTR("}1" D_MQTT_FULL_TOPIC "}2%s"), GetTopic_P(stopic, CMND, mqtt_topic, "")); + WSContentSend_P(PSTR("}1" D_MQTT " " D_FALLBACK_TOPIC "}2%s"), GetFallbackTopic_P(stopic, "")); + } else { + WSContentSend_P(PSTR("}1" D_MQTT "}2" D_DISABLED)); + } + WSContentSend_P(PSTR("}1}2 ")); + +#ifdef USE_EMULATION + WSContentSend_P(PSTR("}1" D_EMULATION "}2%s"), GetTextIndexed(stopic, sizeof(stopic), Settings.flag2.emulation, kEmulationOptions)); +#else + WSContentSend_P(PSTR("}1" D_EMULATION "}2" D_DISABLED)); +#endif + +#ifdef USE_DISCOVERY + WSContentSend_P(PSTR("}1" D_MDNS_DISCOVERY "}2%s"), (Settings.flag3.mdns_enabled) ? D_ENABLED : D_DISABLED); + if (Settings.flag3.mdns_enabled) { +#ifdef WEBSERVER_ADVERTISE + WSContentSend_P(PSTR("}1" D_MDNS_ADVERTISE "}2" D_WEB_SERVER)); +#else + WSContentSend_P(PSTR("}1" D_MDNS_ADVERTISE "}2" D_DISABLED)); +#endif + } +#else + WSContentSend_P(PSTR("}1" D_MDNS_DISCOVERY "}2" D_DISABLED)); +#endif + + WSContentSend_P(PSTR("}1}2 ")); + WSContentSend_P(PSTR("}1" D_ESP_CHIP_ID "}2%d"), ESP.getChipId()); + WSContentSend_P(PSTR("}1" D_FLASH_CHIP_ID "}20x%06X"), ESP.getFlashChipId()); + WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%dkB"), ESP.getFlashChipRealSize() / 1024); + WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%dkB"), ESP.getFlashChipSize() / 1024); + WSContentSend_P(PSTR("}1" D_PROGRAM_SIZE "}2%dkB"), ESP.getSketchSize() / 1024); + WSContentSend_P(PSTR("}1" D_FREE_PROGRAM_SPACE "}2%dkB"), ESP.getFreeSketchSpace() / 1024); + WSContentSend_P(PSTR("}1" D_FREE_MEMORY "}2%dkB"), freeMem / 1024); + WSContentSend_P(PSTR("
")); + + WSContentSend_P(HTTP_SCRIPT_INFO_END); + WSContentSendStyle(); + + WSContentSend_P(PSTR("" + "
")); + + WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); +} +#endif + + + +void HandleUpgradeFirmware(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_FIRMWARE_UPGRADE); + + WSContentStart_P(S_FIRMWARE_UPGRADE); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_UPG, SettingsText(SET_OTAURL)); + WSContentSend_P(HTTP_FORM_RST_UPG, D_UPGRADE); + WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); + + Web.upload_error = 0; + Web.upload_file_type = UPL_TASMOTA; +} + +void HandleUpgradeFirmwareStart(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + char command[TOPSZ + 10]; + + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED)); + WifiConfigCounter(); + + char otaurl[TOPSZ]; + WebGetArg("o", otaurl, sizeof(otaurl)); + if (strlen(otaurl)) { + snprintf_P(command, sizeof(command), PSTR(D_CMND_OTAURL " %s"), otaurl); + ExecuteWebCommand(command, SRC_WEBGUI); + } + + WSContentStart_P(S_INFORMATION); + WSContentSend_P(HTTP_SCRIPT_RELOAD_OTA); + WSContentSendStyle(); + WSContentSend_P(PSTR("
" D_UPGRADE_STARTED " ...
")); + WSContentSend_P(HTTP_MSG_RSTRT); + WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); + + snprintf_P(command, sizeof(command), PSTR(D_CMND_UPGRADE " 1")); + ExecuteWebCommand(command, SRC_WEBGUI); +} + +void HandleUploadDone(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPLOAD_DONE)); + + char error[100]; + + WifiConfigCounter(); + restart_flag = 0; + MqttRetryCounter(0); + + WSContentStart_P(S_INFORMATION); + if (!Web.upload_error) { + WSContentSend_P(HTTP_SCRIPT_RELOAD_OTA); + } + WSContentSendStyle(); + WSContentSend_P(PSTR("
" D_UPLOAD " " D_FAILED "

"), WebColor(COL_TEXT_WARNING)); +#ifdef USE_RF_FLASH + if (Web.upload_error < 15) { +#else + if ((Web.upload_error < 10) || (14 == Web.upload_error)) { + if (14 == Web.upload_error) { Web.upload_error = 10; } +#endif + GetTextIndexed(error, sizeof(error), Web.upload_error -1, kUploadErrors); + } else { + snprintf_P(error, sizeof(error), PSTR(D_UPLOAD_ERROR_CODE " %d"), Web.upload_error); + } + WSContentSend_P(error); + DEBUG_CORE_LOG(PSTR("UPL: %s"), error); + stop_flash_rotate = Settings.flag.stop_flash_rotate; + } else { + WSContentSend_P(PSTR("%06x'>" D_SUCCESSFUL "
"), WebColor(COL_TEXT_SUCCESS)); + WSContentSend_P(HTTP_MSG_RSTRT); + ShowWebSource(SRC_WEBGUI); +#ifdef USE_TASMOTA_SLAVE + if (TasmotaSlave_GetFlagFlashing()) { + restart_flag = 0; + } else { + restart_flag = 2; + } +#else + restart_flag = 2; +#endif + } + SettingsBufferFree(); + WSContentSend_P(PSTR("

")); + WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); +#ifdef USE_TASMOTA_SLAVE + if (TasmotaSlave_GetFlagFlashing()) { + TasmotaSlave_Flash(); + } +#endif +} + +void HandleUploadLoop(void) +{ + + bool _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level); + + if (HTTP_USER == Web.state) { return; } + if (Web.upload_error) { + if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); } + return; + } + + HTTPUpload& upload = WebServer->upload(); + + if (UPLOAD_FILE_START == upload.status) { + restart_flag = 60; + if (0 == upload.filename.c_str()[0]) { + Web.upload_error = 1; + return; + } + SettingsSave(1); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_FILE " %s ..."), upload.filename.c_str()); + if (UPL_SETTINGS == Web.upload_file_type) { + if (!SettingsBufferAlloc()) { + Web.upload_error = 2; + return; + } + } else { + MqttRetryCounter(60); +#ifdef USE_EMULATION + UdpDisconnect(); +#endif +#ifdef USE_ARILUX_RF + AriluxRfDisable(); +#endif + if (Settings.flag.mqtt_enabled) { + MqttDisconnect(); + } + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if (!Update.begin(maxSketchSpace)) { + + + + + + + Web.upload_error = 2; + return; + } + } + Web.upload_progress_dot_count = 0; + } else if (!Web.upload_error && (UPLOAD_FILE_WRITE == upload.status)) { + if (0 == upload.totalSize) { + if (UPL_SETTINGS == Web.upload_file_type) { + Web.config_block_count = 0; + } + else { +#ifdef USE_RF_FLASH + if ((SONOFF_BRIDGE == my_module_type) && (upload.buf[0] == ':')) { + Update.end(); + Web.upload_file_type = UPL_EFM8BB1; + + Web.upload_error = SnfBrUpdateInit(); + if (Web.upload_error != 0) { return; } + } else +#endif +#ifdef USE_TASMOTA_SLAVE + if ((WEMOS == my_module_type) && (upload.buf[0] == ':')) { + Update.end(); + Web.upload_file_type = UPL_TASMOTASLAVE; + Web.upload_error = TasmotaSlave_UpdateInit(); + if (Web.upload_error != 0) { return; } + } else +#endif + { + if ((upload.buf[0] != 0xE9) && (upload.buf[0] != 0x1F)) { + Web.upload_error = 3; + return; + } + if (0xE9 == upload.buf[0]) { + uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4); + if (bin_flash_size > ESP.getFlashChipRealSize()) { + Web.upload_error = 4; + return; + } + + } + } + } + } + if (UPL_SETTINGS == Web.upload_file_type) { + if (!Web.upload_error) { + if (upload.currentSize > (sizeof(Settings) - (Web.config_block_count * HTTP_UPLOAD_BUFLEN))) { + Web.upload_error = 9; + return; + } + memcpy(settings_buffer + (Web.config_block_count * HTTP_UPLOAD_BUFLEN), upload.buf, upload.currentSize); + Web.config_block_count++; + } + } +#ifdef USE_RF_FLASH + else if (UPL_EFM8BB1 == Web.upload_file_type) { + if (efm8bb1_update != nullptr) { + ssize_t result = rf_glue_remnant_with_new_data_and_write(efm8bb1_update, upload.buf, upload.currentSize); + free(efm8bb1_update); + efm8bb1_update = nullptr; + if (result != 0) { + Web.upload_error = abs(result); + return; + } + } + ssize_t result = rf_search_and_write(upload.buf, upload.currentSize); + if (result < 0) { + Web.upload_error = abs(result); + return; + } else if (result > 0) { + if ((size_t)result > upload.currentSize) { + + Web.upload_error = 9; + return; + } + + size_t remnant_sz = upload.currentSize - result; + efm8bb1_update = (uint8_t *) malloc(remnant_sz + 1); + if (efm8bb1_update == nullptr) { + Web.upload_error = 2; + return; + } + memcpy(efm8bb1_update, upload.buf + result, remnant_sz); + + efm8bb1_update[remnant_sz] = '\0'; + } + } +#endif +#ifdef USE_TASMOTA_SLAVE + else if (UPL_TASMOTASLAVE == Web.upload_file_type) { + TasmotaSlave_WriteBuffer(upload.buf, upload.currentSize); + } +#endif + else { + if (!Web.upload_error && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) { + Web.upload_error = 5; + return; + } + if (_serialoutput) { + Serial.printf("."); + Web.upload_progress_dot_count++; + if (!(Web.upload_progress_dot_count % 80)) { Serial.println(); } + } + } + } else if(!Web.upload_error && (UPLOAD_FILE_END == upload.status)) { + if (_serialoutput && (Web.upload_progress_dot_count % 80)) { + Serial.println(); + } + if (UPL_SETTINGS == Web.upload_file_type) { + if (Web.config_xor_on_set) { + for (uint32_t i = 2; i < sizeof(Settings); i++) { + settings_buffer[i] ^= (Web.config_xor_on_set +i); + } + } + bool valid_settings = false; + unsigned long buffer_version = settings_buffer[11] << 24 | settings_buffer[10] << 16 | settings_buffer[9] << 8 | settings_buffer[8]; + if (buffer_version > 0x06000000) { + uint32_t buffer_size = settings_buffer[3] << 8 | settings_buffer[2]; + if (buffer_version > 0x0606000A) { + uint32_t buffer_crc32 = settings_buffer[4095] << 24 | settings_buffer[4094] << 16 | settings_buffer[4093] << 8 | settings_buffer[4092]; + valid_settings = (GetCfgCrc32(settings_buffer, buffer_size -4) == buffer_crc32); + } else { + uint16_t buffer_crc16 = settings_buffer[15] << 8 | settings_buffer[14]; + valid_settings = (GetCfgCrc16(settings_buffer, buffer_size) == buffer_crc16); + } + } else { + valid_settings = (settings_buffer[0] == CONFIG_FILE_SIGN); + } + if (valid_settings) { + SettingsDefaultSet2(); + memcpy((char*)&Settings +16, settings_buffer +16, sizeof(Settings) -16); + Settings.version = buffer_version; + SettingsBufferFree(); + } else { + Web.upload_error = 8; + return; + } + } +#ifdef USE_RF_FLASH + else if (UPL_EFM8BB1 == Web.upload_file_type) { + + Web.upload_file_type = UPL_TASMOTA; + } +#endif +#ifdef USE_TASMOTA_SLAVE + else if (UPL_TASMOTASLAVE == Web.upload_file_type) { + + TasmotaSlave_SetFlagFlashing(true); + Web.upload_file_type = UPL_TASMOTA; + } +#endif + else { + if (!Update.end(true)) { + if (_serialoutput) { Update.printError(Serial); } + Web.upload_error = 6; + return; + } + if (!VersionCompatible()) { + Web.upload_error = 14; + return; + } + } + if (!Web.upload_error) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes. " D_RESTARTING), upload.totalSize); + } + } else if (UPLOAD_FILE_ABORTED == upload.status) { + restart_flag = 0; + MqttRetryCounter(0); + Web.upload_error = 7; + if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); } + } + delay(0); +} + + + +void HandlePreflightRequest(void) +{ + HttpHeaderCors(); + WebServer->sendHeader(F("Access-Control-Allow-Methods"), F("GET, POST")); + WebServer->sendHeader(F("Access-Control-Allow-Headers"), F("authorization")); + WSSend(200, CT_HTML, ""); +} + + + +void HandleHttpCommand(void) +{ + if (!HttpCheckPriviledgedAccess(false)) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND)); + + bool valid = true; + if (strlen(SettingsText(SET_WEBPWD))) { + char tmp1[33]; + WebGetArg("user", tmp1, sizeof(tmp1)); + char tmp2[strlen(SettingsText(SET_WEBPWD)) +1]; + WebGetArg("password", tmp2, sizeof(tmp2)); + if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, SettingsText(SET_WEBPWD)))) { valid = false; } + } + + WSContentBegin(200, CT_JSON); + if (valid) { + uint32_t curridx = web_log_index; + String svalue = WebServer->arg("cmnd"); + if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) { + ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCOMMAND); + if (web_log_index != curridx) { + uint32_t counter = curridx; + WSContentSend_P(PSTR("{")); + bool cflg = false; + do { + char* tmp; + size_t len; + GetLog(counter, &tmp, &len); + if (len) { + + char* JSON = (char*)memchr(tmp, '{', len); + if (JSON) { + size_t JSONlen = len - (JSON - tmp); + if (JSONlen > sizeof(mqtt_data)) { JSONlen = sizeof(mqtt_data); } + char stemp[JSONlen]; + strlcpy(stemp, JSON +1, JSONlen -2); + WSContentSend_P(PSTR("%s%s"), (cflg) ? "," : "", stemp); + cflg = true; + } + } + counter++; + counter &= 0xFF; + if (!counter) counter++; + } while (counter != web_log_index); + WSContentSend_P(PSTR("}")); + } else { + WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_ENABLE_WEBLOG_FOR_RESPONSE "\"}")); + } + } else { + WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_ENTER_COMMAND " cmnd=\"}")); + } + } else { + WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_NEED_USER_AND_PASSWORD "\"}")); + } + WSContentEnd(); +} + + + +void HandleConsole(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + if (WebServer->hasArg("c2")) { + HandleConsoleRefresh(); + return; + } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONSOLE); + + WSContentStart_P(S_CONSOLE); + WSContentSend_P(HTTP_SCRIPT_CONSOL, Settings.web_refresh); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_CMND); + WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); +} + +void HandleConsoleRefresh(void) +{ + bool cflg = true; + uint32_t counter = 0; + + String svalue = WebServer->arg("c1"); + if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), svalue.c_str()); + ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCONSOLE); + } + + char stmp[8]; + WebGetArg("c2", stmp, sizeof(stmp)); + if (strlen(stmp)) { counter = atoi(stmp); } + + WSContentBegin(200, CT_PLAIN); + WSContentSend_P(PSTR("%d}1%d}1"), web_log_index, Web.reset_web_log_flag); + if (!Web.reset_web_log_flag) { + counter = 0; + Web.reset_web_log_flag = true; + } + if (counter != web_log_index) { + if (!counter) { + counter = web_log_index; + cflg = false; + } + do { + char* tmp; + size_t len; + GetLog(counter, &tmp, &len); + if (len) { + if (len > sizeof(mqtt_data) -2) { len = sizeof(mqtt_data); } + char stemp[len +1]; + strlcpy(stemp, tmp, len); + WSContentSend_P(PSTR("%s%s"), (cflg) ? "\n" : "", stemp); + cflg = true; + } + counter++; + counter &= 0xFF; + if (!counter) { counter++; } + } while (counter != web_log_index); + } + WSContentSend_P(PSTR("}1")); + WSContentEnd(); +} + + + +void HandleNotFound(void) +{ + + + if (CaptivePortal()) { return; } + +#ifdef USE_EMULATION +#ifdef USE_EMULATION_HUE + String path = WebServer->uri(); + if ((EMUL_HUE == Settings.flag2.emulation) && (path.startsWith("/api"))) { + HandleHueApi(&path); + } else +#endif +#endif + { + WSContentBegin(404, CT_PLAIN); + WSContentSend_P(PSTR(D_FILE_NOT_FOUND "\n\nURI: %s\nMethod: %s\nArguments: %d\n"), WebServer->uri().c_str(), (WebServer->method() == HTTP_GET) ? "GET" : "POST", WebServer->args()); + for (uint32_t i = 0; i < WebServer->args(); i++) { + WSContentSend_P(PSTR(" %s: %s\n"), WebServer->argName(i).c_str(), WebServer->arg(i).c_str()); + } + WSContentEnd(); + } +} + + +bool CaptivePortal(void) +{ + + if ((WifiIsInManagerMode()) && !ValidIpAddress(WebServer->hostHeader().c_str())) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_REDIRECTED)); + + WebServer->sendHeader(F("Location"), String("http://") + WebServer->client().localIP().toString(), true); + WSSend(302, CT_PLAIN, ""); + WebServer->client().stop(); + return true; + } + return false; +} + + + +String UrlEncode(const String& text) +{ + const char hex[] = "0123456789ABCDEF"; + + String encoded = ""; + int len = text.length(); + int i = 0; + while (i < len) { + char decodedChar = text.charAt(i++); +# 2674 "S:/Development/Tasmota/tasmota/xdrv_01_webserver.ino" + if ((' ' == decodedChar) || ('+' == decodedChar)) { + encoded += '%'; + encoded += hex[decodedChar >> 4]; + encoded += hex[decodedChar & 0xF]; + } else { + encoded += decodedChar; + } + + } + return encoded; +} + +int WebSend(char *buffer) +{ + + + + + + char *host; + char *user; + char *password; + char *command; + int status = 1; + + + host = strtok_r(buffer, "]", &command); + if (host && command) { + RemoveSpace(host); + host++; + host = strtok_r(host, ",", &user); + String url = F("http://"); + url += host; + + command = Trim(command); + if (command[0] != '/') { + url += F("/cm?"); + if (user) { + user = strtok_r(user, ":", &password); + if (user && password) { + char userpass[200]; + snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password); + url += userpass; + } + } + url += F("cmnd="); + } + url += command; + + DEBUG_CORE_LOG(PSTR("WEB: Uri |%s|"), url.c_str()); + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) + HTTPClient http; + if (http.begin(UrlEncode(url))) { +#else + WiFiClient http_client; + HTTPClient http; + if (http.begin(http_client, UrlEncode(url))) { +#endif + int http_code = http.GET(); + if (http_code > 0) { + if (http_code == HTTP_CODE_OK || http_code == HTTP_CODE_MOVED_PERMANENTLY) { +#ifdef USE_WEBSEND_RESPONSE + + const char* read = http.getString().c_str(); + uint32_t j = 0; + char text = '.'; + while (text != '\0') { + text = *read++; + if (text > 31) { + mqtt_data[j++] = text; + if (j == sizeof(mqtt_data) -2) { break; } + } + } + mqtt_data[j] = '\0'; + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WEBSEND)); +#ifdef USE_SCRIPT +extern uint8_t tasm_cmd_activ; + + tasm_cmd_activ=0; + XdrvRulesProcess(); +#endif +#endif + } + status = 0; + } else { + status = 2; + } + http.end(); + } else { + status = 3; + } + } + return status; +} + +bool JsonWebColor(const char* dataBuf) +{ + + + + + + char dataBufLc[strlen(dataBuf) +1]; + LowerCase(dataBufLc, dataBuf); + RemoveSpace(dataBufLc); + if (strlen(dataBufLc) < 9) { return false; } + + StaticJsonBuffer<450> jb; + JsonObject& obj = jb.parseObject(dataBufLc); + if (!obj.success()) { return false; } + + char parm_lc[10]; + if (obj[LowerCase(parm_lc, D_CMND_WEBCOLOR)].success()) { + for (uint32_t i = 0; i < COL_LAST; i++) { + const char* color = obj[parm_lc][i]; + if (color != nullptr) { + WebHexCode(i, color); + } + } + } + return true; +} + +const char kWebSendStatus[] PROGMEM = D_JSON_DONE "|" D_JSON_WRONG_PARAMETERS "|" D_JSON_CONNECT_FAILED "|" D_JSON_HOST_NOT_FOUND "|" D_JSON_MEMORY_ERROR; + +const char kWebCommands[] PROGMEM = "|" +#ifdef USE_EMULATION + D_CMND_EMULATION "|" +#endif +#ifdef USE_SENDMAIL + D_CMND_SENDMAIL "|" +#endif + D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|" + D_CMND_WEBSENSOR "|" D_CMND_WEBBUTTON "|" D_CMND_CORS; + +void (* const WebCommand[])(void) PROGMEM = { +#ifdef USE_EMULATION + &CmndEmulation, +#endif +#ifdef USE_SENDMAIL + &CmndSendmail, +#endif + &CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, + &CmndWebSensor, &CmndWebButton, &CmndCors }; + + + + + +#ifdef USE_EMULATION +void CmndEmulation(void) +{ +#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE) + if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) { +#else +#ifndef USE_EMULATION_WEMO + if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_HUE == XdrvMailbox.payload)) { +#endif +#ifndef USE_EMULATION_HUE + if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_WEMO == XdrvMailbox.payload)) { +#endif +#endif + Settings.flag2.emulation = XdrvMailbox.payload; + restart_flag = 2; + } + ResponseCmndNumber(Settings.flag2.emulation); +} +#endif + +#ifdef USE_SENDMAIL +void CmndSendmail(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t result = SendMail(XdrvMailbox.data); + char stemp1[20]; + ResponseCmndChar(GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus)); + } +} +#endif + + +void CmndWebServer(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { + Settings.webserver = XdrvMailbox.payload; + } + if (Settings.webserver) { + Response_P(PSTR("{\"" D_CMND_WEBSERVER "\":\"" D_JSON_ACTIVE_FOR " %s " D_JSON_ON_DEVICE " %s " D_JSON_WITH_IP_ADDRESS " %s\"}"), + (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str()); + } else { + ResponseCmndStateText(0); + } +} + +void CmndWebPassword(void) +{ + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_WEBPWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data); + ResponseCmndChar(SettingsText(SET_WEBPWD)); + } else { + Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command); + } +} + +void CmndWeblog(void) +{ + if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) { + Settings.weblog_level = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.weblog_level); +} + +void CmndWebRefresh(void) +{ + if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload <= 10000)) { + Settings.web_refresh = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.web_refresh); +} + +void CmndWebSend(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t result = WebSend(XdrvMailbox.data); + char stemp1[20]; + ResponseCmndChar(GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus)); + } +} + +void CmndWebColor(void) +{ + if (XdrvMailbox.data_len > 0) { + if (strstr(XdrvMailbox.data, "{") == nullptr) { + if ((XdrvMailbox.data_len > 3) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= COL_LAST)) { + WebHexCode(XdrvMailbox.index -1, XdrvMailbox.data); + } + else if (0 == XdrvMailbox.payload) { + SettingsDefaultWebColor(); + } + } + else { + JsonWebColor(XdrvMailbox.data); + } + } + Response_P(PSTR("{\"" D_CMND_WEBCOLOR "\":[")); + for (uint32_t i = 0; i < COL_LAST; i++) { + if (i) { ResponseAppend_P(PSTR(",")); } + ResponseAppend_P(PSTR("\"#%06x\""), WebColor(i)); + } + ResponseAppend_P(PSTR("]}")); +} + +void CmndWebSensor(void) +{ + if (XdrvMailbox.index < MAX_XSNS_DRIVERS) { + if (XdrvMailbox.payload >= 0) { + bitWrite(Settings.sensors[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1); + } + } + Response_P(PSTR("{\"" D_CMND_WEBSENSOR "\":")); + XsnsSensorState(); + ResponseJsonEnd(); +} + +void CmndWebButton(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_BUTTON_TEXT)) { + if (!XdrvMailbox.usridx) { + ResponseCmndAll(SET_BUTTON1, MAX_BUTTON_TEXT); + } else { + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_BUTTON1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data); + } + ResponseCmndIdxChar(SettingsText(SET_BUTTON1 + XdrvMailbox.index -1)); + } + } +} + +void CmndCors(void) +{ + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_CORS, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data); + } + ResponseCmndChar(SettingsText(SET_CORS)); +} + + + + + +bool Xdrv01(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_LOOP: + PollDnsWebserver(); +#ifdef USE_EMULATION +#ifdef USE_DEVICE_GROUPS + if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { PollUdp(); } +#else + if (Settings.flag2.emulation) { PollUdp(); } +#endif +#endif + break; + case FUNC_COMMAND: + result = DecodeCommand(kWebCommands, WebCommand); + break; + } + return result; +} +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_02_mqtt.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_02_mqtt.ino" +#define XDRV_02 2 + + + +#ifdef USE_MQTT_TLS + #include "WiFiClientSecureLightBearSSL.h" + BearSSL::WiFiClientSecure_light *tlsClient; +#else + WiFiClient EspClient; +#endif + +const char kMqttCommands[] PROGMEM = "|" +#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT) + D_CMND_MQTTFINGERPRINT "|" +#endif + D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + D_CMND_TLSKEY "|" +#endif + D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTCLIENT "|" + D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|" D_CMND_MQTTLOG "|" + D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|" D_CMND_SENSORRETAIN ; + +void (* const MqttCommand[])(void) PROGMEM = { +#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT) + &CmndMqttFingerprint, +#endif + &CmndMqttUser, &CmndMqttPassword, +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + &CmndTlsKey, +#endif + &CmndMqttHost, &CmndMqttPort, &CmndMqttRetry, &CmndStateText, &CmndMqttClient, + &CmndFullTopic, &CmndPrefix, &CmndGroupTopic, &CmndTopic, &CmndPublish, &CmndMqttlog, + &CmndButtonTopic, &CmndSwitchTopic, &CmndButtonRetain, &CmndSwitchRetain, &CmndPowerRetain, &CmndSensorRetain }; + +struct MQTT { + uint16_t connect_count = 0; + uint16_t retry_counter = 1; + uint8_t initial_connection_state = 2; + bool connected = false; + bool allowed = false; +} Mqtt; + +#ifdef USE_MQTT_TLS + +#ifdef USE_MQTT_AWS_IOT +#include + +const br_ec_private_key *AWS_IoT_Private_Key = nullptr; +const br_x509_certificate *AWS_IoT_Client_Certificate = nullptr; + +class tls_entry_t { +public: + uint32_t name; + uint16_t start; + uint16_t len; +}; + +const static uint32_t TLS_NAME_SKEY = 0x2079656B; +const static uint32_t TLS_NAME_CRT = 0x20747263; + +class tls_dir_t { +public: + tls_entry_t entry[4]; +}; + +tls_dir_t tls_dir; + +#endif + + + + +bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value) { + for (uint32_t i = 0; i<20; i++) { + if (finger[i] != value) { + return false; + } + } + return true; +} +#endif + +void MakeValidMqtt(uint32_t option, char* str) +{ + + + uint32_t i = 0; + while (str[i] > 0) { + + if ((str[i] == '+') || (str[i] == '#') || (str[i] == ' ')) { + if (option) { + uint32_t j = i; + while (str[j] > 0) { + str[j] = str[j +1]; + j++; + } + i--; + } else { + str[i] = '_'; + } + } + i++; + } +} + +#ifdef USE_DISCOVERY +#ifdef MQTT_HOST_DISCOVERY +void MqttDiscoverServer(void) +{ + if (!Wifi.mdns_begun) { return; } + + int n = MDNS.queryService("mqtt", "tcp"); + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS D_QUERY_DONE " %d"), n); + + if (n > 0) { + uint32_t i = 0; +#ifdef MDNS_HOSTNAME + for (i = n; i > 0; i--) { + if (!strcmp(MDNS.hostname(i).c_str(), MDNS_HOSTNAME)) { + break; + } + } +#endif + SettingsUpdateText(SET_MQTT_HOST, MDNS.IP(i).toString().c_str()); + Settings.mqtt_port = MDNS.port(i); + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS D_MQTT_SERVICE_FOUND " %s, " D_IP_ADDRESS " %s, " D_PORT " %d"), MDNS.hostname(i).c_str(), SettingsText(SET_MQTT_HOST), Settings.mqtt_port); + } +} +#endif +#endif +# 163 "S:/Development/Tasmota/tasmota/xdrv_02_mqtt.ino" +#include + + +#if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MIN_MESSZ + #error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 1200" +#endif + +#ifdef USE_MQTT_TLS +PubSubClient MqttClient; +#else +PubSubClient MqttClient(EspClient); +#endif + +void MqttInit(void) +{ +#ifdef USE_MQTT_TLS + tlsClient = new BearSSL::WiFiClientSecure_light(1024,1024); + +#ifdef USE_MQTT_AWS_IOT + loadTlsDir(); + tlsClient->setClientECCert(AWS_IoT_Client_Certificate, + AWS_IoT_Private_Key, + 0xFFFF , 0); +#endif + +#ifdef USE_MQTT_TLS_CA_CERT +#ifdef USE_MQTT_AWS_IOT + tlsClient->setTrustAnchor(&AmazonRootCA1_TA); +#else + tlsClient->setTrustAnchor(&LetsEncryptX3CrossSigned_TA); +#endif +#endif + + MqttClient.setClient(*tlsClient); +#endif +} + +bool MqttIsConnected(void) +{ + return MqttClient.connected(); +} + +void MqttDisconnect(void) +{ + MqttClient.disconnect(); +} + +void MqttSubscribeLib(const char *topic) +{ + MqttClient.subscribe(topic); + MqttClient.loop(); +} + +void MqttUnsubscribeLib(const char *topic) +{ + MqttClient.unsubscribe(topic); + MqttClient.loop(); +} + +bool MqttPublishLib(const char* topic, bool retained) +{ + + if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) { + char *str = strstr(topic, SettingsText(SET_MQTTPREFIX1)); + if (str == topic) { + mqtt_cmnd_blocked_reset = 4; + mqtt_cmnd_blocked++; + } + } + + bool result = MqttClient.publish(topic, mqtt_data, retained); + yield(); + return result; +} + +void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len) +{ +#ifdef USE_DEBUG_DRIVER + ShowFreeMem(PSTR("MqttDataHandler")); +#endif + + + if (data_len >= MQTT_MAX_PACKET_SIZE) { return; } + + + if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) { + char *str = strstr(mqtt_topic, SettingsText(SET_MQTTPREFIX1)); + if ((str == mqtt_topic) && mqtt_cmnd_blocked) { + mqtt_cmnd_blocked--; + return; + } + } + + + char topic[TOPSZ]; + strlcpy(topic, mqtt_topic, sizeof(topic)); + mqtt_data[data_len] = 0; + char data[data_len +1]; + memcpy(data, mqtt_data, sizeof(data)); + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_MQTT D_RECEIVED_TOPIC " \"%s\", " D_DATA_SIZE " %d, " D_DATA " \"%s\""), topic, data_len, data); + + + + XdrvMailbox.index = strlen(topic); + XdrvMailbox.data_len = data_len; + XdrvMailbox.topic = topic; + XdrvMailbox.data = (char*)data; + if (XdrvCall(FUNC_MQTT_DATA)) { return; } + + ShowSource(SRC_MQTT); + + CommandHandler(topic, data, data_len); +} + + + +void MqttRetryCounter(uint8_t value) +{ + Mqtt.retry_counter = value; +} + +void MqttSubscribe(const char *topic) +{ + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT D_SUBSCRIBE_TO " %s"), topic); + MqttSubscribeLib(topic); +} + +void MqttUnsubscribe(const char *topic) +{ + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT D_UNSUBSCRIBE_FROM " %s"), topic); + MqttUnsubscribeLib(topic); +} + +void MqttPublishLogging(const char *mxtime) +{ + char saved_mqtt_data[strlen(mqtt_data) +1]; + memcpy(saved_mqtt_data, mqtt_data, sizeof(saved_mqtt_data)); + + + Response_P(PSTR("%s%s"), mxtime, log_data); + char stopic[TOPSZ]; + GetTopic_P(stopic, STAT, mqtt_topic, PSTR("LOGGING")); + MqttPublishLib(stopic, false); + + memcpy(mqtt_data, saved_mqtt_data, sizeof(saved_mqtt_data)); +} + +void MqttPublish(const char* topic, bool retained) +{ +#ifdef USE_DEBUG_DRIVER + ShowFreeMem(PSTR("MqttPublish")); +#endif + +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) || defined(MQTT_NO_RETAIN) + + + + retained = false; +#endif + + char sretained[CMDSZ]; + sretained[0] = '\0'; + char slog_type[20]; + snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_RESULT)); + + if (Settings.flag.mqtt_enabled) { + if (MqttPublishLib(topic, retained)) { + snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_MQTT)); + if (retained) { + snprintf_P(sretained, sizeof(sretained), PSTR(" (" D_RETAINED ")")); + } + } + } + + snprintf_P(log_data, sizeof(log_data), PSTR("%s%s = %s"), slog_type, (Settings.flag.mqtt_enabled) ? topic : strrchr(topic,'/')+1, mqtt_data); + if (strlen(log_data) >= (sizeof(log_data) - strlen(sretained) -1)) { + log_data[sizeof(log_data) - strlen(sretained) -5] = '\0'; + snprintf_P(log_data, sizeof(log_data), PSTR("%s ..."), log_data); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s%s"), log_data, sretained); + AddLog(LOG_LEVEL_INFO); + + if (Settings.ledstate &0x04) { + blinks++; + } +} + +void MqttPublish(const char* topic) +{ + MqttPublish(topic, false); +} + +void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retained) +{ + + + + + + + + char romram[64]; + char stopic[TOPSZ]; + + snprintf_P(romram, sizeof(romram), ((prefix > 3) && !Settings.flag.mqtt_response) ? S_RSLT_RESULT : subtopic); + for (uint32_t i = 0; i < strlen(romram); i++) { + romram[i] = toupper(romram[i]); + } + prefix &= 3; + GetTopic_P(stopic, prefix, mqtt_topic, romram); + MqttPublish(stopic, retained); + +#ifdef USE_MQTT_AWS_IOT + if ((prefix > 0) && (Settings.flag4.awsiot_shadow)) { + + char *topic = SettingsText(SET_MQTT_TOPIC); + char topic2[strlen(topic)+1]; + strcpy(topic2, topic); + + char *s = topic2; + while (*s) { + if ('/' == *s) { + *s = '_'; + } + s++; + } + + snprintf_P(romram, sizeof(romram), PSTR("$aws/things/%s/shadow/update"), topic2); + + + char *mqtt_save = (char*) malloc(strlen(mqtt_data)+1); + if (!mqtt_save) { return; } + strcpy(mqtt_save, mqtt_data); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"state\":{\"reported\":%s}}"), mqtt_save); + free(mqtt_save); + + bool result = MqttClient.publish(romram, mqtt_data, false); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram); + yield(); + } +#endif +} + +void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic) +{ + MqttPublishPrefixTopic_P(prefix, subtopic, false); +} + +void MqttPublishTeleSensor(void) +{ + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); + XdrvRulesProcess(); +} + +void MqttPublishPowerState(uint32_t device) +{ + char stopic[TOPSZ]; + char scommand[33]; + + if ((device < 1) || (device > devices_present)) { device = 1; } + +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan() && (device > 1)) { + if (GetFanspeed() < MaxFanspeed()) { +#ifdef USE_DOMOTICZ + DomoticzUpdateFanState(); +#endif + snprintf_P(scommand, sizeof(scommand), PSTR(D_CMND_FANSPEED)); + GetTopic_P(stopic, STAT, mqtt_topic, (Settings.flag.mqtt_response) ? scommand : S_RSLT_RESULT); + Response_P(S_JSON_COMMAND_NVALUE, scommand, GetFanspeed()); + MqttPublish(stopic); + } + } else { +#endif + GetPowerDevice(scommand, device, sizeof(scommand), Settings.flag.device_index_enable); + GetTopic_P(stopic, STAT, mqtt_topic, (Settings.flag.mqtt_response) ? scommand : S_RSLT_RESULT); + Response_P(S_JSON_COMMAND_SVALUE, scommand, GetStateText(bitRead(power, device -1))); + MqttPublish(stopic); + + GetTopic_P(stopic, STAT, mqtt_topic, scommand); + Response_P(GetStateText(bitRead(power, device -1))); + MqttPublish(stopic, Settings.flag.mqtt_power_retain); +#ifdef USE_SONOFF_IFAN + } +#endif +} + +void MqttPublishAllPowerState(void) +{ + for (uint32_t i = 1; i <= devices_present; i++) { + MqttPublishPowerState(i); +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan()) { break; } +#endif + } +} + +void MqttPublishPowerBlinkState(uint32_t device) +{ + char scommand[33]; + + if ((device < 1) || (device > devices_present)) { + device = 1; + } + Response_P(PSTR("{\"%s\":\"" D_JSON_BLINK " %s\"}"), + GetPowerDevice(scommand, device, sizeof(scommand), Settings.flag.device_index_enable), GetStateText(bitRead(blink_mask, device -1))); + + MqttPublishPrefixTopic_P(RESULT_OR_STAT, S_RSLT_POWER); +} + + + +uint16_t MqttConnectCount(void) +{ + return Mqtt.connect_count; +} + +void MqttDisconnected(int state) +{ + Mqtt.connected = false; + Mqtt.retry_counter = Settings.mqtt_retry; + + MqttClient.disconnect(); + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), SettingsText(SET_MQTT_HOST), Settings.mqtt_port, state, Mqtt.retry_counter); + rules_flag.mqtt_disconnected = 1; +} + +void MqttConnected(void) +{ + char stopic[TOPSZ]; + + if (Mqtt.allowed) { + AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_CONNECTED)); + Mqtt.connected = true; + Mqtt.retry_counter = 0; + Mqtt.connect_count++; + + GetTopic_P(stopic, TELE, mqtt_topic, S_LWT); + Response_P(PSTR(D_ONLINE)); + MqttPublish(stopic, true); + + + mqtt_data[0] = '\0'; + MqttPublishPrefixTopic_P(CMND, S_RSLT_POWER); + + GetTopic_P(stopic, CMND, mqtt_topic, PSTR("#")); + MqttSubscribe(stopic); + if (strstr_P(SettingsText(SET_MQTT_FULLTOPIC), MQTT_TOKEN_TOPIC) != nullptr) { + GetGroupTopic_P(stopic, PSTR("#")); + MqttSubscribe(stopic); + GetFallbackTopic_P(stopic, PSTR("#")); + MqttSubscribe(stopic); + } + + XdrvCall(FUNC_MQTT_SUBSCRIBE); + } + + if (Mqtt.initial_connection_state) { + char stopic2[TOPSZ]; + Response_P(PSTR("{\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}"), + ModuleName().c_str(), my_version, my_image, GetFallbackTopic_P(stopic, ""), GetGroupTopic_P(stopic2, "")); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "1")); +#ifdef USE_WEBSERVER + if (Settings.webserver) { +#if LWIP_IPV6 + Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"IPv6Address\":\"%s\"}"), + (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str(),WifiGetIPv6().c_str()); +#else + Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\"}"), + (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str()); +#endif + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "2")); + } +#endif + Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":")); + if (CrashFlag()) { + CrashDump(); + } else { + ResponseAppend_P(PSTR("\"%s\""), GetResetReason().c_str()); + } + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "3")); + MqttPublishAllPowerState(); + if (Settings.tele_period) { + tele_period = Settings.tele_period -5; + } + rules_flag.system_boot = 1; + XdrvCall(FUNC_MQTT_INIT); + } + Mqtt.initial_connection_state = 0; + + global_state.mqtt_down = 0; + if (Settings.flag.mqtt_enabled) { + rules_flag.mqtt_connected = 1; + } +} + +void MqttReconnect(void) +{ + char stopic[TOPSZ]; + + Mqtt.allowed = Settings.flag.mqtt_enabled; + if (Mqtt.allowed) { +#ifdef USE_DISCOVERY +#ifdef MQTT_HOST_DISCOVERY + MqttDiscoverServer(); +#endif +#endif + if (!strlen(SettingsText(SET_MQTT_HOST)) || !Settings.mqtt_port) { + Mqtt.allowed = false; + } +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + + if (!AWS_IoT_Private_Key || !AWS_IoT_Client_Certificate) { + Mqtt.allowed = false; + } +#endif + } + if (!Mqtt.allowed) { + MqttConnected(); + return; + } + +#ifdef USE_EMULATION + UdpDisconnect(); +#endif + + AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_ATTEMPTING_CONNECTION)); + + Mqtt.connected = false; + Mqtt.retry_counter = Settings.mqtt_retry; + global_state.mqtt_down = 1; + + char *mqtt_user = nullptr; + char *mqtt_pwd = nullptr; + if (strlen(SettingsText(SET_MQTT_USER))) { + mqtt_user = SettingsText(SET_MQTT_USER); + } + if (strlen(SettingsText(SET_MQTT_PWD))) { + mqtt_pwd = SettingsText(SET_MQTT_PWD); + } + + GetTopic_P(stopic, TELE, mqtt_topic, S_LWT); + Response_P(S_OFFLINE); + + if (MqttClient.connected()) { MqttClient.disconnect(); } +#ifdef USE_MQTT_TLS + tlsClient->stop(); +#else + EspClient = WiFiClient(); + MqttClient.setClient(EspClient); +#endif + + if (2 == Mqtt.initial_connection_state) { + Mqtt.initial_connection_state = 1; + } + + MqttClient.setCallback(MqttDataHandler); +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + + tlsClient->setClientECCert(AWS_IoT_Client_Certificate, + AWS_IoT_Private_Key, + 0xFFFF , 0); +#endif + MqttClient.setServer(SettingsText(SET_MQTT_HOST), Settings.mqtt_port); + + uint32_t mqtt_connect_time = millis(); +#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT) + bool allow_all_fingerprints = false; + bool learn_fingerprint1 = is_fingerprint_mono_value(Settings.mqtt_fingerprint[0], 0x00); + bool learn_fingerprint2 = is_fingerprint_mono_value(Settings.mqtt_fingerprint[1], 0x00); + allow_all_fingerprints |= is_fingerprint_mono_value(Settings.mqtt_fingerprint[0], 0xff); + allow_all_fingerprints |= is_fingerprint_mono_value(Settings.mqtt_fingerprint[1], 0xff); + allow_all_fingerprints |= learn_fingerprint1; + allow_all_fingerprints |= learn_fingerprint2; + tlsClient->setPubKeyFingerprint(Settings.mqtt_fingerprint[0], Settings.mqtt_fingerprint[1], allow_all_fingerprints); +#endif +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), SettingsText(SET_MQTT_HOST)); + if (MqttClient.connect(mqtt_client, nullptr, nullptr, stopic, 1, false, mqtt_data, MQTT_CLEAN_SESSION)) { +#else + if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data, MQTT_CLEAN_SESSION)) { +#endif +#ifdef USE_MQTT_TLS + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connected in %d ms, max ThunkStack used %d"), + millis() - mqtt_connect_time, tlsClient->getMaxThunkStackUse()); + if (!tlsClient->getMFLNStatus()) { + AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("MFLN not supported by TLS server")); + } +#ifndef USE_MQTT_TLS_CA_CERT + + char buf_fingerprint[64]; + ToHex_P((unsigned char *)tlsClient->getRecvPubKeyFingerprint(), 20, buf_fingerprint, sizeof(buf_fingerprint), ' '); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Server fingerprint: %s"), buf_fingerprint); + + if (learn_fingerprint1 || learn_fingerprint2) { + + bool fingerprint_matched = false; + const uint8_t *recv_fingerprint = tlsClient->getRecvPubKeyFingerprint(); + if (0 == memcmp(recv_fingerprint, Settings.mqtt_fingerprint[0], 20)) { + fingerprint_matched = true; + } + if (0 == memcmp(recv_fingerprint, Settings.mqtt_fingerprint[1], 20)) { + fingerprint_matched = true; + } + if (!fingerprint_matched) { + + if (learn_fingerprint1) { + memcpy(Settings.mqtt_fingerprint[0], recv_fingerprint, 20); + } + if (learn_fingerprint2) { + memcpy(Settings.mqtt_fingerprint[1], recv_fingerprint, 20); + } + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Fingerprint learned: %s"), buf_fingerprint); + + SettingsSaveAll(); + } + } +#endif +#endif + MqttConnected(); + } else { +#ifdef USE_MQTT_TLS + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connection error: %d"), tlsClient->getLastError()); +#endif + MqttDisconnected(MqttClient.state()); + } +} + +void MqttCheck(void) +{ + if (Settings.flag.mqtt_enabled) { + if (!MqttIsConnected()) { + global_state.mqtt_down = 1; + if (!Mqtt.retry_counter) { + MqttReconnect(); + } else { + Mqtt.retry_counter--; + } + } else { + global_state.mqtt_down = 0; + } + } else { + global_state.mqtt_down = 0; + if (Mqtt.initial_connection_state) { + MqttReconnect(); + } + } +} + + + + + +#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT) +void CmndMqttFingerprint(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) { + char fingerprint[60]; + if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(fingerprint))) { + strlcpy(fingerprint, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? MQTT_FINGERPRINT1 : MQTT_FINGERPRINT2 : XdrvMailbox.data, sizeof(fingerprint)); + char *p = fingerprint; + for (uint32_t i = 0; i < 20; i++) { + Settings.mqtt_fingerprint[XdrvMailbox.index -1][i] = strtol(p, &p, 16); + } + restart_flag = 2; + } + ResponseCmndIdxChar(ToHex_P((unsigned char *)Settings.mqtt_fingerprint[XdrvMailbox.index -1], 20, fingerprint, sizeof(fingerprint), ' ')); + } +} +#endif + +void CmndMqttUser(void) +{ + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_MQTT_USER, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_USER : XdrvMailbox.data); + restart_flag = 2; + } + ResponseCmndChar(SettingsText(SET_MQTT_USER)); +} + +void CmndMqttPassword(void) +{ + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_MQTT_PWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_PASS : XdrvMailbox.data); + ResponseCmndChar(SettingsText(SET_MQTT_PWD)); + restart_flag = 2; + } else { + Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command); + } +} + +void CmndMqttlog(void) +{ + if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) { + Settings.mqttlog_level = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.mqttlog_level); +} + +void CmndMqttHost(void) +{ + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_MQTT_HOST, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data); + restart_flag = 2; + } + ResponseCmndChar(SettingsText(SET_MQTT_HOST)); +} + +void CmndMqttPort(void) +{ + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 65536)) { + Settings.mqtt_port = (1 == XdrvMailbox.payload) ? MQTT_PORT : XdrvMailbox.payload; + restart_flag = 2; + } + ResponseCmndNumber(Settings.mqtt_port); +} + +void CmndMqttRetry(void) +{ + if ((XdrvMailbox.payload >= MQTT_RETRY_SECS) && (XdrvMailbox.payload < 32001)) { + Settings.mqtt_retry = XdrvMailbox.payload; + Mqtt.retry_counter = Settings.mqtt_retry; + } + ResponseCmndNumber(Settings.mqtt_retry); +} + +void CmndStateText(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_STATE_TEXT)) { + if (!XdrvMailbox.usridx) { + ResponseCmndAll(SET_STATE_TXT1, MAX_STATE_TEXT); + } else { + if (XdrvMailbox.data_len > 0) { + for (uint32_t i = 0; i <= XdrvMailbox.data_len; i++) { + if (XdrvMailbox.data[i] == ' ') XdrvMailbox.data[i] = '_'; + } + SettingsUpdateText(SET_STATE_TXT1 + XdrvMailbox.index -1, XdrvMailbox.data); + } + ResponseCmndIdxChar(GetStateText(XdrvMailbox.index -1)); + } + } +} + +void CmndMqttClient(void) +{ + if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) { + SettingsUpdateText(SET_MQTT_CLIENT, (SC_DEFAULT == Shortcut()) ? MQTT_CLIENT_ID : XdrvMailbox.data); + restart_flag = 2; + } + ResponseCmndChar(SettingsText(SET_MQTT_CLIENT)); +} + +void CmndFullTopic(void) +{ + if (XdrvMailbox.data_len > 0) { + MakeValidMqtt(1, XdrvMailbox.data); + if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); } + char stemp1[TOPSZ]; + strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_FULLTOPIC : XdrvMailbox.data, sizeof(stemp1)); + if (strcmp(stemp1, SettingsText(SET_MQTT_FULLTOPIC))) { + Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); + MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); + SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp1); + restart_flag = 2; + } + } + ResponseCmndChar(SettingsText(SET_MQTT_FULLTOPIC)); +} + +void CmndPrefix(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_MQTT_PREFIXES)) { + if (!XdrvMailbox.usridx) { + ResponseCmndAll(SET_MQTTPREFIX1, MAX_MQTT_PREFIXES); + } else { + if (XdrvMailbox.data_len > 0) { + MakeValidMqtt(0, XdrvMailbox.data); + SettingsUpdateText(SET_MQTTPREFIX1 + XdrvMailbox.index -1, + (SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index) ? SUB_PREFIX : (2==XdrvMailbox.index) ? PUB_PREFIX : PUB_PREFIX2 : XdrvMailbox.data); + restart_flag = 2; + } + ResponseCmndIdxChar(SettingsText(SET_MQTTPREFIX1 + XdrvMailbox.index -1)); + } + } +} + +void CmndPublish(void) +{ + if (XdrvMailbox.data_len > 0) { + char *payload_part; + char *mqtt_part = strtok_r(XdrvMailbox.data, " ", &payload_part); + if (mqtt_part) { + char stemp1[TOPSZ]; + strlcpy(stemp1, mqtt_part, sizeof(stemp1)); + if ((payload_part != nullptr) && strlen(payload_part)) { + strlcpy(mqtt_data, payload_part, sizeof(mqtt_data)); + } else { + mqtt_data[0] = '\0'; + } + MqttPublish(stemp1, (XdrvMailbox.index == 2)); + + mqtt_data[0] = '\0'; + } + } +} + +void CmndGroupTopic(void) +{ +#ifdef USE_DEVICE_GROUPS + uint32_t settings_text_index = (XdrvMailbox.index <= 1 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + XdrvMailbox.index - 2); +#endif + if (XdrvMailbox.data_len > 0) { + MakeValidMqtt(0, XdrvMailbox.data); + if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); } +#ifdef USE_DEVICE_GROUPS + SettingsUpdateText(settings_text_index, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data); +#else + SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data); +#endif + restart_flag = 2; + } + ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC)); +} + +void CmndTopic(void) +{ + if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) { + MakeValidMqtt(0, XdrvMailbox.data); + if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); } + char stemp1[TOPSZ]; + strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_TOPIC : XdrvMailbox.data, sizeof(stemp1)); + if (strcmp(stemp1, SettingsText(SET_MQTT_TOPIC))) { + Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); + MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); + SettingsUpdateText(SET_MQTT_TOPIC, stemp1); + restart_flag = 2; + } + } + ResponseCmndChar(SettingsText(SET_MQTT_TOPIC)); +} + +void CmndButtonTopic(void) +{ + if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) { + MakeValidMqtt(0, XdrvMailbox.data); + if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); } + switch (Shortcut()) { + case SC_CLEAR: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, ""); break; + case SC_DEFAULT: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, mqtt_topic); break; + case SC_USER: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC); break; + default: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, XdrvMailbox.data); + } + } + ResponseCmndChar(SettingsText(SET_MQTT_BUTTON_TOPIC)); +} + +void CmndSwitchTopic(void) +{ + if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) { + MakeValidMqtt(0, XdrvMailbox.data); + if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); } + switch (Shortcut()) { + case SC_CLEAR: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, ""); break; + case SC_DEFAULT: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, mqtt_topic); break; + case SC_USER: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC); break; + default: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, XdrvMailbox.data); + } + } + ResponseCmndChar(SettingsText(SET_MQTT_SWITCH_TOPIC)); +} + +void CmndButtonRetain(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + if (!XdrvMailbox.payload) { + for (uint32_t i = 1; i <= MAX_KEYS; i++) { + SendKey(KEY_BUTTON, i, CLEAR_RETAIN); + } + } + Settings.flag.mqtt_button_retain = XdrvMailbox.payload; + } + ResponseCmndStateText(Settings.flag.mqtt_button_retain); +} + +void CmndSwitchRetain(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + if (!XdrvMailbox.payload) { + for (uint32_t i = 1; i <= MAX_SWITCHES; i++) { + SendKey(KEY_SWITCH, i, CLEAR_RETAIN); + } + } + Settings.flag.mqtt_switch_retain = XdrvMailbox.payload; + } + ResponseCmndStateText(Settings.flag.mqtt_switch_retain); +} + +void CmndPowerRetain(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + if (!XdrvMailbox.payload) { + char stemp1[TOPSZ]; + char scommand[CMDSZ]; + for (uint32_t i = 1; i <= devices_present; i++) { + GetTopic_P(stemp1, STAT, mqtt_topic, GetPowerDevice(scommand, i, sizeof(scommand), Settings.flag.device_index_enable)); + mqtt_data[0] = '\0'; + MqttPublish(stemp1, Settings.flag.mqtt_power_retain); + } + } + Settings.flag.mqtt_power_retain = XdrvMailbox.payload; + } + ResponseCmndStateText(Settings.flag.mqtt_power_retain); +} + +void CmndSensorRetain(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + if (!XdrvMailbox.payload) { + mqtt_data[0] = '\0'; + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_ENERGY), Settings.flag.mqtt_sensor_retain); + } + Settings.flag.mqtt_sensor_retain = XdrvMailbox.payload; + } + ResponseCmndStateText(Settings.flag.mqtt_sensor_retain); +} + + + + +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + + + +const static uint16_t tls_spi_start_sector = 0xFF; +const static uint8_t* tls_spi_start = (uint8_t*) 0x402FF000; +const static size_t tls_spi_len = 0x1000; +const static size_t tls_block_offset = 0x0400; +const static size_t tls_block_len = 0x0400; +const static size_t tls_obj_store_offset = tls_block_offset + sizeof(tls_dir_t); + + +inline void TlsEraseBuffer(uint8_t *buffer) { + memset(buffer + tls_block_offset, 0xFF, tls_block_len); +} + + + +static br_ec_private_key EC = { + 23, + nullptr, 0 +}; + +static br_x509_certificate CHAIN[] = { + { nullptr, 0 } +}; + + + +void loadTlsDir(void) { + memcpy_P(&tls_dir, tls_spi_start + tls_block_offset, sizeof(tls_dir)); + + + if ((TLS_NAME_SKEY == tls_dir.entry[0].name) && (tls_dir.entry[0].len > 0)) { + EC.x = (unsigned char *)(tls_spi_start + tls_obj_store_offset + tls_dir.entry[0].start); + EC.xlen = tls_dir.entry[0].len; + AWS_IoT_Private_Key = &EC; + } else { + AWS_IoT_Private_Key = nullptr; + } + if ((TLS_NAME_CRT == tls_dir.entry[1].name) && (tls_dir.entry[1].len > 0)) { + CHAIN[0].data = (unsigned char *) (tls_spi_start + tls_obj_store_offset + tls_dir.entry[1].start); + CHAIN[0].data_len = tls_dir.entry[1].len; + AWS_IoT_Client_Certificate = CHAIN; + } else { + AWS_IoT_Client_Certificate = nullptr; + } + +} + +const char ALLOCATE_ERROR[] PROGMEM = "TLSKey " D_JSON_ERROR ": cannot allocate buffer."; + +void CmndTlsKey(void) { +#ifdef DEBUG_DUMP_TLS + if (0 == XdrvMailbox.index){ + CmndTlsDump(); + } +#endif + if ((XdrvMailbox.index >= 1) && (XdrvMailbox.index <= 2)) { + tls_dir_t *tls_dir_write; + + if (XdrvMailbox.data_len > 0) { + + uint8_t *spi_buffer = (uint8_t*) malloc(tls_spi_len); + if (!spi_buffer) { + AddLog_P(LOG_LEVEL_ERROR, ALLOCATE_ERROR); + return; + } + memcpy_P(spi_buffer, tls_spi_start, tls_spi_len); + + + RemoveAllSpaces(XdrvMailbox.data); + + + uint32_t bin_len = decode_base64_length((unsigned char*)XdrvMailbox.data); + uint8_t *bin_buf = nullptr; + if (bin_len > 0) { + bin_buf = (uint8_t*) malloc(bin_len + 4); + if (!bin_buf) { + AddLog_P(LOG_LEVEL_ERROR, ALLOCATE_ERROR); + free(spi_buffer); + return; + } + } + + + if (bin_len > 0) { + decode_base64((unsigned char*)XdrvMailbox.data, bin_buf); + } + + + tls_dir_write = (tls_dir_t*) (spi_buffer + tls_block_offset); + + if (1 == XdrvMailbox.index) { + + + TlsEraseBuffer(spi_buffer); + if (bin_len > 0) { + if (bin_len != 32) { + + AddLog_P2(LOG_LEVEL_INFO, PSTR("TLSKey: Certificate must be 32 bytes: %d."), bin_len); + free(spi_buffer); + free(bin_buf); + return; + } + tls_entry_t *entry = &tls_dir_write->entry[0]; + entry->name = TLS_NAME_SKEY; + entry->start = 0; + entry->len = bin_len; + memcpy(spi_buffer + tls_obj_store_offset + entry->start, bin_buf, entry->len); + } else { + + } + } else if (2 == XdrvMailbox.index) { + + if (TLS_NAME_SKEY != tls_dir.entry[0].name) { + + AddLog_P(LOG_LEVEL_INFO, PSTR("TLSKey: cannot store Cert if no Key previously stored.")); + free(spi_buffer); + free(bin_buf); + return; + } + if (bin_len <= 256) { + + AddLog_P2(LOG_LEVEL_INFO, PSTR("TLSKey: Certificate length too short: %d."), bin_len); + free(spi_buffer); + free(bin_buf); + return; + } + tls_entry_t *entry = &tls_dir_write->entry[1]; + entry->name = TLS_NAME_CRT; + entry->start = (tls_dir_write->entry[0].start + tls_dir_write->entry[0].len + 3) & ~0x03; + entry->len = bin_len; + memcpy(spi_buffer + tls_obj_store_offset + entry->start, bin_buf, entry->len); + } + + if (ESP.flashEraseSector(tls_spi_start_sector)) { + ESP.flashWrite(tls_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + } + free(spi_buffer); + free(bin_buf); + } + + loadTlsDir(); + Response_P(PSTR("{\"%s1\":%d,\"%s2\":%d}"), + XdrvMailbox.command, AWS_IoT_Private_Key ? tls_dir.entry[0].len : -1, + XdrvMailbox.command, AWS_IoT_Client_Certificate ? tls_dir.entry[1].len : -1); + } +} + +#ifdef DEBUG_DUMP_TLS + +uint32_t bswap32(uint32_t x) { + return ((x << 24) & 0xff000000 ) | + ((x << 8) & 0x00ff0000 ) | + ((x >> 8) & 0x0000ff00 ) | + ((x >> 24) & 0x000000ff ); +} +void CmndTlsDump(void) { + uint32_t start = (uint32_t)tls_spi_start + tls_block_offset; + uint32_t end = start + tls_block_len -1; + for (uint32_t pos = start; pos < end; pos += 0x10) { + uint32_t* values = (uint32_t*)(pos); +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + Serial.printf("%08x: %08x %08x %08x %08x\n", pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3])); +#else + Serial.printf_P(PSTR("%08x: %08x %08x %08x %08x\n"), pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3])); +#endif + } +} +#endif +#endif + + + + + +#ifdef USE_WEBSERVER + +#define WEB_HANDLE_MQTT "mq" + +const char S_CONFIGURE_MQTT[] PROGMEM = D_CONFIGURE_MQTT; + +const char HTTP_BTN_MENU_MQTT[] PROGMEM = + "

"; + +const char HTTP_FORM_MQTT1[] PROGMEM = + "
 " D_MQTT_PARAMETERS " " + "
" + "

" D_HOST " (" MQTT_HOST ")

" + "

" D_PORT " (" STR(MQTT_PORT) ")

" + "

" D_CLIENT " (%s)

"; +const char HTTP_FORM_MQTT2[] PROGMEM = + "

" D_USER " (" MQTT_USER ")

" + "

" D_PASSWORD "

" + "

" D_TOPIC " = %%topic%% (%s)

" + "

" D_FULL_TOPIC " (%s)

"; + +void HandleMqttConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_MQTT); + + if (WebServer->hasArg("save")) { + MqttSaveSettings(); + WebRestart(1); + return; + } + + char str[TOPSZ]; + + WSContentStart_P(S_CONFIGURE_MQTT); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_MQTT1, + SettingsText(SET_MQTT_HOST), + Settings.mqtt_port, + Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, SettingsText(SET_MQTT_CLIENT)); + WSContentSend_P(HTTP_FORM_MQTT2, + (!strlen(SettingsText(SET_MQTT_USER))) ? "0" : SettingsText(SET_MQTT_USER), + Format(str, MQTT_TOPIC, sizeof(str)), MQTT_TOPIC, SettingsText(SET_MQTT_TOPIC), + MQTT_FULLTOPIC, MQTT_FULLTOPIC, SettingsText(SET_MQTT_FULLTOPIC)); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void MqttSaveSettings(void) +{ + char tmp[TOPSZ]; + char stemp[TOPSZ]; + char stemp2[TOPSZ]; + + WebGetArg("mt", tmp, sizeof(tmp)); + strlcpy(stemp, (!strlen(tmp)) ? MQTT_TOPIC : tmp, sizeof(stemp)); + MakeValidMqtt(0, stemp); + WebGetArg("mf", tmp, sizeof(tmp)); + strlcpy(stemp2, (!strlen(tmp)) ? MQTT_FULLTOPIC : tmp, sizeof(stemp2)); + MakeValidMqtt(1, stemp2); + if ((strcmp(stemp, SettingsText(SET_MQTT_TOPIC))) || (strcmp(stemp2, SettingsText(SET_MQTT_FULLTOPIC)))) { + Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); + MqttPublishPrefixTopic_P(TELE, S_LWT, true); + } + SettingsUpdateText(SET_MQTT_TOPIC, stemp); + SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp2); + WebGetArg("mh", tmp, sizeof(tmp)); + SettingsUpdateText(SET_MQTT_HOST, (!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp); + WebGetArg("ml", tmp, sizeof(tmp)); + Settings.mqtt_port = (!strlen(tmp)) ? MQTT_PORT : atoi(tmp); + WebGetArg("mc", tmp, sizeof(tmp)); + SettingsUpdateText(SET_MQTT_CLIENT, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp); +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"), + SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC)); +#else + WebGetArg("mu", tmp, sizeof(tmp)); + SettingsUpdateText(SET_MQTT_USER, (!strlen(tmp)) ? MQTT_USER : (!strcmp(tmp,"0")) ? "" : tmp); + WebGetArg("mp", tmp, sizeof(tmp)); + SettingsUpdateText(SET_MQTT_PWD, (!strlen(tmp)) ? "" : (!strcmp(tmp, D_ASTERISK_PWD)) ? SettingsText(SET_MQTT_PWD) : tmp); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_MQTTUSER " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"), + SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_USER), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC)); +#endif +} +#endif + + + + + +bool Xdrv02(uint8_t function) +{ + bool result = false; + + if (Settings.flag.mqtt_enabled) { + switch (function) { + case FUNC_PRE_INIT: + MqttInit(); + break; + case FUNC_EVERY_50_MSECOND: + MqttClient.loop(); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_MQTT); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/" WEB_HANDLE_MQTT, HandleMqttConfiguration); + break; +#endif + case FUNC_COMMAND: + result = DecodeCommand(kMqttCommands, MqttCommand); + break; + } + } + return result; +} +# 1 "S:/Development/Tasmota/tasmota/xdrv_03_energy.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_03_energy.ino" +#ifdef USE_ENERGY_SENSOR + + + + +#define XDRV_03 3 +#define XSNS_03 3 + + + + +#define ENERGY_NONE 0 +#define ENERGY_WATCHDOG 4 + +#include + +#define D_CMND_POWERCAL "PowerCal" +#define D_CMND_VOLTAGECAL "VoltageCal" +#define D_CMND_CURRENTCAL "CurrentCal" +#define D_CMND_TARIFF "Tariff" +#define D_CMND_MODULEADDRESS "ModuleAddress" + +enum EnergyCommands { + CMND_POWERCAL, CMND_VOLTAGECAL, CMND_CURRENTCAL, + CMND_POWERSET, CMND_VOLTAGESET, CMND_CURRENTSET, CMND_FREQUENCYSET, CMND_MODULEADDRESS }; + +const char kEnergyCommands[] PROGMEM = "|" + D_CMND_POWERCAL "|" D_CMND_VOLTAGECAL "|" D_CMND_CURRENTCAL "|" + D_CMND_POWERSET "|" D_CMND_VOLTAGESET "|" D_CMND_CURRENTSET "|" D_CMND_FREQUENCYSET "|" D_CMND_MODULEADDRESS "|" +#ifdef USE_ENERGY_MARGIN_DETECTION + D_CMND_POWERDELTA "|" D_CMND_POWERLOW "|" D_CMND_POWERHIGH "|" D_CMND_VOLTAGELOW "|" D_CMND_VOLTAGEHIGH "|" D_CMND_CURRENTLOW "|" D_CMND_CURRENTHIGH "|" +#ifdef USE_ENERGY_POWER_LIMIT + D_CMND_MAXENERGY "|" D_CMND_MAXENERGYSTART "|" + D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|" + D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW "|" +#endif +#endif + D_CMND_ENERGYRESET "|" D_CMND_TARIFF ; + +void (* const EnergyCommand[])(void) PROGMEM = { + &CmndPowerCal, &CmndVoltageCal, &CmndCurrentCal, + &CmndPowerSet, &CmndVoltageSet, &CmndCurrentSet, &CmndFrequencySet, &CmndModuleAddress, +#ifdef USE_ENERGY_MARGIN_DETECTION + &CmndPowerDelta, &CmndPowerLow, &CmndPowerHigh, &CmndVoltageLow, &CmndVoltageHigh, &CmndCurrentLow, &CmndCurrentHigh, +#ifdef USE_ENERGY_POWER_LIMIT + &CmndMaxEnergy, &CmndMaxEnergyStart, + &CmndMaxPower, &CmndMaxPowerHold, &CmndMaxPowerWindow, + &CmndSafePower, &CmndSafePowerHold, &CmndSafePowerWindow, +#endif +#endif + &CmndEnergyReset, &CmndTariff }; + +const char kEnergyPhases[] PROGMEM = "|%s / %s|%s / %s / %s||[%s,%s]|[%s,%s,%s]"; + +struct ENERGY { + float voltage[3] = { 0, 0, 0 }; + float current[3] = { 0, 0, 0 }; + float active_power[3] = { 0, 0, 0 }; + float apparent_power[3] = { NAN, NAN, NAN }; + float reactive_power[3] = { NAN, NAN, NAN }; + float power_factor[3] = { NAN, NAN, NAN }; + float frequency[3] = { NAN, NAN, NAN }; + + float start_energy = 0; + float daily = 0; + float total = 0; + float export_active = NAN; + + unsigned long kWhtoday_delta = 0; + unsigned long kWhtoday_offset = 0; + unsigned long kWhtoday; + unsigned long period = 0; + + uint8_t fifth_second = 0; + uint8_t command_code = 0; + uint8_t data_valid[3] = { 0, 0, 0 }; + + uint8_t phase_count = 1; + bool voltage_common = false; + + bool voltage_available = true; + bool current_available = true; + + bool type_dc = false; + bool power_on = true; + +#ifdef USE_ENERGY_MARGIN_DETECTION + uint16_t power_history[3] = { 0 }; + uint8_t power_steady_counter = 8; + bool power_delta = false; + bool min_power_flag = false; + bool max_power_flag = false; + bool min_voltage_flag = false; + bool max_voltage_flag = false; + bool min_current_flag = false; + bool max_current_flag = false; + +#ifdef USE_ENERGY_POWER_LIMIT + uint16_t mplh_counter = 0; + uint16_t mplw_counter = 0; + uint8_t mplr_counter = 0; + uint8_t max_energy_state = 0; +#endif +#endif +} Energy; + +Ticker ticker_energy; + + + +bool EnergyTariff1Active() +{ + uint8_t dst = 0; + if (IsDst() && (Settings.tariff[0][1] != Settings.tariff[1][1])) { + dst = 1; + } + if (Settings.tariff[0][dst] != Settings.tariff[1][dst]) { + if (Settings.flag3.energy_weekend && ((RtcTime.day_of_week == 1) || + (RtcTime.day_of_week == 7))) { + return true; + } + uint32_t minutes = MinutesPastMidnight(); + if (Settings.tariff[0][dst] > Settings.tariff[1][dst]) { + + return ((minutes >= Settings.tariff[0][dst]) || (minutes < Settings.tariff[1][dst])); + } else { + + return ((minutes >= Settings.tariff[0][dst]) && (minutes < Settings.tariff[1][dst])); + } + } else { + return false; + } +} + +void EnergyUpdateToday(void) +{ + if (Energy.kWhtoday_delta > 1000) { + unsigned long delta = Energy.kWhtoday_delta / 1000; + Energy.kWhtoday_delta -= (delta * 1000); + Energy.kWhtoday += delta; + } + + RtcSettings.energy_kWhtoday = Energy.kWhtoday_offset + Energy.kWhtoday; + Energy.daily = (float)(RtcSettings.energy_kWhtoday) / 100000; + Energy.total = (float)(RtcSettings.energy_kWhtotal + RtcSettings.energy_kWhtoday) / 100000; + + if (RtcTime.valid){ + + uint32_t energy_diff = (uint32_t)(Energy.total * 100000) - RtcSettings.energy_usage.last_usage_kWhtotal; + RtcSettings.energy_usage.last_usage_kWhtotal = (uint32_t)(Energy.total * 100000); + + uint32_t return_diff = 0; + if (!isnan(Energy.export_active)) { + return_diff = (uint32_t)(Energy.export_active * 100000) - RtcSettings.energy_usage.last_return_kWhtotal; + RtcSettings.energy_usage.last_return_kWhtotal = (uint32_t)(Energy.export_active * 100000); + } + + if (EnergyTariff1Active()) { + RtcSettings.energy_usage.usage1_kWhtotal += energy_diff; + RtcSettings.energy_usage.return1_kWhtotal += return_diff; + } else { + RtcSettings.energy_usage.usage2_kWhtotal += energy_diff; + RtcSettings.energy_usage.return2_kWhtotal += return_diff; + } + } +} + +void EnergyUpdateTotal(float value, bool kwh) +{ + + + + + uint32_t multiplier = (kwh) ? 100000 : 100; + + if (0 == Energy.start_energy || (value < Energy.start_energy)) { + Energy.start_energy = value; + } + else if (value != Energy.start_energy) { + Energy.kWhtoday = (unsigned long)((value - Energy.start_energy) * multiplier); + } + + if ((Energy.total < (value - 0.01)) && + Settings.flag3.hardware_energy_total) { + RtcSettings.energy_kWhtotal = (unsigned long)((value * multiplier) - Energy.kWhtoday_offset - Energy.kWhtoday); + Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal; + Energy.total = (float)(RtcSettings.energy_kWhtotal + Energy.kWhtoday_offset + Energy.kWhtoday) / 100000; + Settings.energy_kWhtotal_time = (!Energy.kWhtoday_offset) ? LocalTime() : Midnight(); + + } + EnergyUpdateToday(); +} + + + +void Energy200ms(void) +{ + Energy.power_on = (power != 0) | Settings.flag.no_power_on_check; + + Energy.fifth_second++; + if (5 == Energy.fifth_second) { + Energy.fifth_second = 0; + + XnrgCall(FUNC_ENERGY_EVERY_SECOND); + + if (RtcTime.valid) { + if (LocalTime() == Midnight()) { + Settings.energy_kWhyesterday = RtcSettings.energy_kWhtoday; + + RtcSettings.energy_kWhtotal += RtcSettings.energy_kWhtoday; + Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal; + Energy.kWhtoday = 0; + Energy.kWhtoday_offset = 0; + RtcSettings.energy_kWhtoday = 0; + Energy.start_energy = 0; + + Energy.kWhtoday_delta = 0; + Energy.period = Energy.kWhtoday; + EnergyUpdateToday(); +#if defined(USE_ENERGY_MARGIN_DETECTION) && defined(USE_ENERGY_POWER_LIMIT) + Energy.max_energy_state = 3; +#endif + } +#if defined(USE_ENERGY_MARGIN_DETECTION) && defined(USE_ENERGY_POWER_LIMIT) + if ((RtcTime.hour == Settings.energy_max_energy_start) && (3 == Energy.max_energy_state )) { + Energy.max_energy_state = 0; + } +#endif + + } + } + + XnrgCall(FUNC_EVERY_200_MSECOND); +} + +void EnergySaveState(void) +{ + Settings.energy_kWhdoy = (RtcTime.valid) ? RtcTime.day_of_year : 0; + + Settings.energy_kWhtoday = RtcSettings.energy_kWhtoday; + Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal; + + Settings.energy_usage = RtcSettings.energy_usage; +} + +#ifdef USE_ENERGY_MARGIN_DETECTION +bool EnergyMargin(bool type, uint16_t margin, uint16_t value, bool &flag, bool &save_flag) +{ + bool change; + + if (!margin) return false; + change = save_flag; + if (type) { + flag = (value > margin); + } else { + flag = (value < margin); + } + save_flag = flag; + return (change != save_flag); +} + +void EnergyMarginCheck(void) +{ + if (Energy.power_steady_counter) { + Energy.power_steady_counter--; + return; + } + + uint16_t energy_power_u = (uint16_t)(Energy.active_power[0]); + + if (Settings.energy_power_delta) { + uint16_t delta = abs(Energy.power_history[0] - energy_power_u); + if (delta > 0) { + if (Settings.energy_power_delta < 101) { + uint16_t min_power = (Energy.power_history[0] > energy_power_u) ? energy_power_u : Energy.power_history[0]; + if (0 == min_power) { min_power++; } + if (((delta * 100) / min_power) > Settings.energy_power_delta) { + Energy.power_delta = true; + } + } else { + if (delta > (Settings.energy_power_delta -100)) { + Energy.power_delta = true; + } + } + if (Energy.power_delta) { + Energy.power_history[1] = Energy.active_power[0]; + Energy.power_history[2] = Energy.active_power[0]; + } + } + } + Energy.power_history[0] = Energy.power_history[1]; + Energy.power_history[1] = Energy.power_history[2]; + Energy.power_history[2] = energy_power_u; + + if (Energy.power_on && (Settings.energy_min_power || Settings.energy_max_power || Settings.energy_min_voltage || Settings.energy_max_voltage || Settings.energy_min_current || Settings.energy_max_current)) { + uint16_t energy_voltage_u = (uint16_t)(Energy.voltage[0]); + uint16_t energy_current_u = (uint16_t)(Energy.current[0] * 1000); + + DEBUG_DRIVER_LOG(PSTR("NRG: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u); + + Response_P(PSTR("{")); + bool flag; + bool jsonflg = false; + if (EnergyMargin(false, Settings.energy_min_power, energy_power_u, flag, Energy.min_power_flag)) { + ResponseAppend_P(PSTR("%s\"" D_CMND_POWERLOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); + jsonflg = true; + } + if (EnergyMargin(true, Settings.energy_max_power, energy_power_u, flag, Energy.max_power_flag)) { + ResponseAppend_P(PSTR("%s\"" D_CMND_POWERHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); + jsonflg = true; + } + if (EnergyMargin(false, Settings.energy_min_voltage, energy_voltage_u, flag, Energy.min_voltage_flag)) { + ResponseAppend_P(PSTR("%s\"" D_CMND_VOLTAGELOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); + jsonflg = true; + } + if (EnergyMargin(true, Settings.energy_max_voltage, energy_voltage_u, flag, Energy.max_voltage_flag)) { + ResponseAppend_P(PSTR("%s\"" D_CMND_VOLTAGEHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); + jsonflg = true; + } + if (EnergyMargin(false, Settings.energy_min_current, energy_current_u, flag, Energy.min_current_flag)) { + ResponseAppend_P(PSTR("%s\"" D_CMND_CURRENTLOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); + jsonflg = true; + } + if (EnergyMargin(true, Settings.energy_max_current, energy_current_u, flag, Energy.max_current_flag)) { + ResponseAppend_P(PSTR("%s\"" D_CMND_CURRENTHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); + jsonflg = true; + } + if (jsonflg) { + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_MARGINS), MQTT_TELE_RETAIN); + EnergyMqttShow(); + } + } + +#ifdef USE_ENERGY_POWER_LIMIT + + if (Settings.energy_max_power_limit) { + if (Energy.active_power[0] > Settings.energy_max_power_limit) { + if (!Energy.mplh_counter) { + Energy.mplh_counter = Settings.energy_max_power_limit_hold; + } else { + Energy.mplh_counter--; + if (!Energy.mplh_counter) { + ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHED "\":%d}"), energy_power_u); + MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING); + EnergyMqttShow(); + SetAllPower(POWER_ALL_OFF, SRC_MAXPOWER); + if (!Energy.mplr_counter) { + Energy.mplr_counter = Settings.param[P_MAX_POWER_RETRY] +1; + } + Energy.mplw_counter = Settings.energy_max_power_limit_window; + } + } + } + else if (power && (energy_power_u <= Settings.energy_max_power_limit)) { + Energy.mplh_counter = 0; + Energy.mplr_counter = 0; + Energy.mplw_counter = 0; + } + if (!power) { + if (Energy.mplw_counter) { + Energy.mplw_counter--; + } else { + if (Energy.mplr_counter) { + Energy.mplr_counter--; + if (Energy.mplr_counter) { + ResponseTime_P(PSTR(",\"" D_JSON_POWERMONITOR "\":\"%s\"}"), GetStateText(1)); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_JSON_POWERMONITOR)); + RestorePower(true, SRC_MAXPOWER); + } else { + ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0)); + MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING); + EnergyMqttShow(); + SetAllPower(POWER_ALL_OFF, SRC_MAXPOWER); + } + } + } + } + } + + + if (Settings.energy_max_energy) { + uint16_t energy_daily_u = (uint16_t)(Energy.daily * 1000); + if (!Energy.max_energy_state && (RtcTime.hour == Settings.energy_max_energy_start)) { + Energy.max_energy_state = 1; + ResponseTime_P(PSTR(",\"" D_JSON_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1)); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_JSON_ENERGYMONITOR)); + RestorePower(true, SRC_MAXENERGY); + } + else if ((1 == Energy.max_energy_state ) && (energy_daily_u >= Settings.energy_max_energy)) { + Energy.max_energy_state = 2; + char stemp[FLOATSZ]; + dtostrfd(Energy.daily, 3, stemp); + ResponseTime_P(PSTR(",\"" D_JSON_MAXENERGYREACHED "\":%s}"), stemp); + MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING); + EnergyMqttShow(); + SetAllPower(POWER_ALL_OFF, SRC_MAXENERGY); + } + } +#endif + + if (Energy.power_delta) { EnergyMqttShow(); } +} + +void EnergyMqttShow(void) +{ + + int tele_period_save = tele_period; + tele_period = 2; + mqtt_data[0] = '\0'; + ResponseAppendTime(); + EnergyShow(true); + tele_period = tele_period_save; + ResponseJsonEnd(); + MqttPublishTeleSensor(); + Energy.power_delta = false; +} +#endif + +void EnergyEverySecond(void) +{ + + if (global_update) { + if (power && (global_temperature != 9999) && (global_temperature > Settings.param[P_OVER_TEMP])) { + SetAllPower(POWER_ALL_OFF, SRC_OVERTEMP); + } + } + + + uint32_t data_valid = Energy.phase_count; + for (uint32_t i = 0; i < Energy.phase_count; i++) { + if (Energy.data_valid[i] <= ENERGY_WATCHDOG) { + Energy.data_valid[i]++; + if (Energy.data_valid[i] > ENERGY_WATCHDOG) { + + Energy.voltage[i] = 0; + Energy.current[i] = 0; + Energy.active_power[i] = 0; + if (!isnan(Energy.apparent_power[i])) { Energy.apparent_power[i] = 0; } + if (!isnan(Energy.reactive_power[i])) { Energy.reactive_power[i] = 0; } + if (!isnan(Energy.frequency[i])) { Energy.frequency[i] = 0; } + if (!isnan(Energy.power_factor[i])) { Energy.power_factor[i] = 0; } + + data_valid--; + } + } + } + if (!data_valid) { + if (!isnan(Energy.export_active)) { Energy.export_active = 0; } + Energy.start_energy = 0; + + XnrgCall(FUNC_ENERGY_RESET); + } + +#ifdef USE_ENERGY_MARGIN_DETECTION + EnergyMarginCheck(); +#endif +} + + + + + +void EnergyCommandCalResponse(uint32_t nvalue) +{ + snprintf_P(XdrvMailbox.command, CMDSZ, PSTR("%sCal"), XdrvMailbox.command); + ResponseCmndNumber(nvalue); +} + +void CmndEnergyReset(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) { + char *p; + unsigned long lnum = strtoul(XdrvMailbox.data, &p, 10); + if (p != XdrvMailbox.data) { + switch (XdrvMailbox.index) { + case 1: + + Energy.kWhtoday_offset = lnum *100; + Energy.kWhtoday = 0; + Energy.kWhtoday_delta = 0; + Energy.start_energy = 0; + Energy.period = Energy.kWhtoday_offset; + Settings.energy_kWhtoday = Energy.kWhtoday_offset; + RtcSettings.energy_kWhtoday = Energy.kWhtoday_offset; + Energy.daily = (float)Energy.kWhtoday_offset / 100000; + if (!RtcSettings.energy_kWhtotal && !Energy.kWhtoday_offset) { + Settings.energy_kWhtotal_time = LocalTime(); + } + break; + case 2: + + Settings.energy_kWhyesterday = lnum *100; + break; + case 3: + + RtcSettings.energy_kWhtotal = lnum *100; + Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal; + + Settings.energy_kWhtotal_time = (!Energy.kWhtoday_offset) ? LocalTime() : Midnight(); + RtcSettings.energy_usage.last_usage_kWhtotal = (uint32_t)(Energy.total * 1000); + break; + } + } + } + else if ((XdrvMailbox.index > 3) && (XdrvMailbox.index <= 5)) { + uint32_t values[2] = { 0 }; + uint32_t position = ParseParameters(2, values); + values[0] *= 100; + values[1] *= 100; + + switch (XdrvMailbox.index) + { + case 4: + + if (position > 0) { + RtcSettings.energy_usage.usage1_kWhtotal = values[0]; + } + if (position > 1) { + RtcSettings.energy_usage.usage2_kWhtotal = values[1]; + } + Settings.energy_usage.usage1_kWhtotal = RtcSettings.energy_usage.usage1_kWhtotal; + Settings.energy_usage.usage2_kWhtotal = RtcSettings.energy_usage.usage2_kWhtotal; + break; + case 5: + + if (position > 0) { + RtcSettings.energy_usage.return1_kWhtotal = values[0]; + } + if (position > 1) { + RtcSettings.energy_usage.return2_kWhtotal = values[1]; + } + Settings.energy_usage.return1_kWhtotal = RtcSettings.energy_usage.return1_kWhtotal; + Settings.energy_usage.return2_kWhtotal = RtcSettings.energy_usage.return2_kWhtotal; + break; + } + } + + Energy.total = (float)(RtcSettings.energy_kWhtotal + Energy.kWhtoday_offset + Energy.kWhtoday) / 100000; + + char energy_total_chr[FLOATSZ]; + dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr); + char energy_daily_chr[FLOATSZ]; + dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr); + char energy_yesterday_chr[FLOATSZ]; + dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr); + + char energy_usage1_chr[FLOATSZ]; + dtostrfd((float)Settings.energy_usage.usage1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_usage1_chr); + char energy_usage2_chr[FLOATSZ]; + dtostrfd((float)Settings.energy_usage.usage2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_usage2_chr); + char energy_return1_chr[FLOATSZ]; + dtostrfd((float)Settings.energy_usage.return1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_return1_chr); + char energy_return2_chr[FLOATSZ]; + dtostrfd((float)Settings.energy_usage.return2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_return2_chr); + + Response_P(PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s,\"" D_JSON_USAGE "\":[%s,%s],\"" D_JSON_EXPORT "\":[%s,%s]}}"), + XdrvMailbox.command, energy_total_chr, energy_yesterday_chr, energy_daily_chr, energy_usage1_chr, energy_usage2_chr, energy_return1_chr, energy_return2_chr); +} + +void CmndTariff(void) +{ + + + + + + + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) { + uint32_t tariff = XdrvMailbox.index -1; + uint32_t time_type = 0; + char *p; + char *str = strtok_r(XdrvMailbox.data, ", ", &p); + while ((str != nullptr) && (time_type < 2)) { + char *q; + uint32_t value = strtol(str, &q, 10); + Settings.tariff[tariff][time_type] = value; + if (value < 24) { + Settings.tariff[tariff][time_type] *= 60; + char *minute = strtok_r(nullptr, ":", &q); + if (minute) { + value = strtol(minute, nullptr, 10); + if (value > 59) { + value = 59; + } + Settings.tariff[tariff][time_type] += value; + } + } + if (Settings.tariff[tariff][time_type] > 1439) { + Settings.tariff[tariff][time_type] = 1439; + } + str = strtok_r(nullptr, ", ", &p); + time_type++; + } + } + else if (XdrvMailbox.index == 9) { + Settings.flag3.energy_weekend = XdrvMailbox.payload & 1; + } + Response_P(PSTR("{\"%s\":{\"Off-Peak\":{\"STD\":\"%s\",\"DST\":\"%s\"},\"Standard\":{\"STD\":\"%s\",\"DST\":\"%s\"},\"Weekend\":\"%s\"}}"), + XdrvMailbox.command, + GetMinuteTime(Settings.tariff[0][0]).c_str(),GetMinuteTime(Settings.tariff[0][1]).c_str(), + GetMinuteTime(Settings.tariff[1][0]).c_str(),GetMinuteTime(Settings.tariff[1][1]).c_str(), + GetStateText(Settings.flag3.energy_weekend)); +} + +void CmndPowerCal(void) +{ + Energy.command_code = CMND_POWERCAL; + if (XnrgCall(FUNC_COMMAND)) { + if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) { + Settings.energy_power_calibration = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_power_calibration); + } +} + +void CmndVoltageCal(void) +{ + Energy.command_code = CMND_VOLTAGECAL; + if (XnrgCall(FUNC_COMMAND)) { + if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) { + Settings.energy_voltage_calibration = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_voltage_calibration); + } +} + +void CmndCurrentCal(void) +{ + Energy.command_code = CMND_CURRENTCAL; + if (XnrgCall(FUNC_COMMAND)) { + if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) { + Settings.energy_current_calibration = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_current_calibration); + } +} + +void CmndPowerSet(void) +{ + Energy.command_code = CMND_POWERSET; + if (XnrgCall(FUNC_COMMAND)) { + EnergyCommandCalResponse(Settings.energy_power_calibration); + } +} + +void CmndVoltageSet(void) +{ + Energy.command_code = CMND_VOLTAGESET; + if (XnrgCall(FUNC_COMMAND)) { + EnergyCommandCalResponse(Settings.energy_voltage_calibration); + } +} + +void CmndCurrentSet(void) +{ + Energy.command_code = CMND_CURRENTSET; + if (XnrgCall(FUNC_COMMAND)) { + EnergyCommandCalResponse(Settings.energy_current_calibration); + } +} + +void CmndFrequencySet(void) +{ + Energy.command_code = CMND_FREQUENCYSET; + if (XnrgCall(FUNC_COMMAND)) { + EnergyCommandCalResponse(Settings.energy_frequency_calibration); + } +} + +void CmndModuleAddress(void) +{ + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4) && (1 == Energy.phase_count)) { + Energy.command_code = CMND_MODULEADDRESS; + if (XnrgCall(FUNC_COMMAND)) { + ResponseCmndDone(); + } + } +} + +#ifdef USE_ENERGY_MARGIN_DETECTION +void CmndPowerDelta(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32000)) { + Settings.energy_power_delta = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_power_delta); +} + +void CmndPowerLow(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.energy_min_power = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_min_power); +} + +void CmndPowerHigh(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.energy_max_power = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_power); +} + +void CmndVoltageLow(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) { + Settings.energy_min_voltage = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_min_voltage); +} + +void CmndVoltageHigh(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) { + Settings.energy_max_voltage = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_voltage); +} + +void CmndCurrentLow(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) { + Settings.energy_min_current = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_min_current); +} + +void CmndCurrentHigh(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) { + Settings.energy_max_current = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_current); +} + +#ifdef USE_ENERGY_POWER_LIMIT +void CmndMaxPower(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.energy_max_power_limit = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_power_limit); +} + +void CmndMaxPowerHold(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.energy_max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_power_limit_hold); +} + +void CmndMaxPowerWindow(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.energy_max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_power_limit_window); +} + +void CmndSafePower(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.energy_max_power_safe_limit = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_power_safe_limit); +} + +void CmndSafePowerHold(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.energy_max_power_safe_limit_hold = (1 == XdrvMailbox.payload) ? SAFE_POWER_HOLD : XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_power_safe_limit_hold); +} + +void CmndSafePowerWindow(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 1440)) { + Settings.energy_max_power_safe_limit_window = (1 == XdrvMailbox.payload) ? SAFE_POWER_WINDOW : XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_power_safe_limit_window); +} + +void CmndMaxEnergy(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.energy_max_energy = XdrvMailbox.payload; + Energy.max_energy_state = 3; + } + ResponseCmndNumber(Settings.energy_max_energy); +} + +void CmndMaxEnergyStart(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) { + Settings.energy_max_energy_start = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.energy_max_energy_start); +} +#endif +#endif + +void EnergySnsInit(void) +{ + XnrgCall(FUNC_INIT); + + if (energy_flg) { + if (RtcSettingsValid()) { + Energy.kWhtoday_offset = RtcSettings.energy_kWhtoday; + } + else if (RtcTime.day_of_year == Settings.energy_kWhdoy) { + Energy.kWhtoday_offset = Settings.energy_kWhtoday; + } + else { + Energy.kWhtoday_offset = 0; + } + Energy.kWhtoday = 0; + Energy.kWhtoday_delta = 0; + Energy.period = Energy.kWhtoday_offset; + EnergyUpdateToday(); + ticker_energy.attach_ms(200, Energy200ms); + } +} + +#ifdef USE_WEBSERVER +const char HTTP_ENERGY_SNS1[] PROGMEM = + "{s}" D_POWERUSAGE_APPARENT "{m}%s " D_UNIT_VA "{e}" + "{s}" D_POWERUSAGE_REACTIVE "{m}%s " D_UNIT_VAR "{e}" + "{s}" D_POWER_FACTOR "{m}%s{e}"; + +const char HTTP_ENERGY_SNS2[] PROGMEM = + "{s}" D_ENERGY_TODAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}" + "{s}" D_ENERGY_YESTERDAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}" + "{s}" D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; + +const char HTTP_ENERGY_SNS3[] PROGMEM = + "{s}" D_EXPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; +#endif + +char* EnergyFormatIndex(char* result, char* input, bool json, uint32_t index, bool single = false) +{ + char layout[16]; + GetTextIndexed(layout, sizeof(layout), (index -1) + (3 * json), kEnergyPhases); + switch (index) { + case 2: + snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ); + break; + case 3: + snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ, input + FLOATSZ + FLOATSZ); + break; + default: + snprintf_P(result, FLOATSZ *3, input); + } + return result; +} + +char* EnergyFormat(char* result, char* input, bool json, bool single = false) +{ + uint8_t index = (single) ? 1 : Energy.phase_count; + return EnergyFormatIndex(result, input, json, index, single); +} + +void EnergyShow(bool json) +{ + for (uint32_t i = 0; i < Energy.phase_count; i++) { + if (Energy.voltage_common) { + Energy.voltage[i] = Energy.voltage[0]; + } + } + + float power_factor_knx = Energy.power_factor[0]; + + char apparent_power_chr[Energy.phase_count][FLOATSZ]; + char reactive_power_chr[Energy.phase_count][FLOATSZ]; + char power_factor_chr[Energy.phase_count][FLOATSZ]; + char frequency_chr[Energy.phase_count][FLOATSZ]; + if (!Energy.type_dc) { + if (Energy.current_available && Energy.voltage_available) { + for (uint32_t i = 0; i < Energy.phase_count; i++) { + float apparent_power = Energy.apparent_power[i]; + if (isnan(apparent_power)) { + apparent_power = Energy.voltage[i] * Energy.current[i]; + } + if (apparent_power < Energy.active_power[i]) { + Energy.active_power[i] = apparent_power; + } + + float power_factor = Energy.power_factor[i]; + if (isnan(power_factor)) { + power_factor = (Energy.active_power[i] && apparent_power) ? Energy.active_power[i] / apparent_power : 0; + if (power_factor > 1) { + power_factor = 1; + } + } + if (0 == i) { power_factor_knx = power_factor; } + + float reactive_power = Energy.reactive_power[i]; + if (isnan(reactive_power)) { + reactive_power = 0; + uint32_t difference = ((uint32_t)(apparent_power * 100) - (uint32_t)(Energy.active_power[i] * 100)) / 10; + if ((Energy.current[i] > 0.005) && ((difference > 15) || (difference > (uint32_t)(apparent_power * 100 / 1000)))) { + + + reactive_power = (float)(RoundSqrtInt((uint32_t)(apparent_power * apparent_power * 100) - (uint32_t)(Energy.active_power[i] * Energy.active_power[i] * 100))) / 10; + } + } + + dtostrfd(apparent_power, Settings.flag2.wattage_resolution, apparent_power_chr[i]); + dtostrfd(reactive_power, Settings.flag2.wattage_resolution, reactive_power_chr[i]); + dtostrfd(power_factor, 2, power_factor_chr[i]); + } + } + for (uint32_t i = 0; i < Energy.phase_count; i++) { + float frequency = Energy.frequency[i]; + if (isnan(Energy.frequency[i])) { + frequency = 0; + } + dtostrfd(frequency, Settings.flag2.frequency_resolution, frequency_chr[i]); + } + } + + char voltage_chr[Energy.phase_count][FLOATSZ]; + char current_chr[Energy.phase_count][FLOATSZ]; + char active_power_chr[Energy.phase_count][FLOATSZ]; + for (uint32_t i = 0; i < Energy.phase_count; i++) { + dtostrfd(Energy.voltage[i], Settings.flag2.voltage_resolution, voltage_chr[i]); + dtostrfd(Energy.current[i], Settings.flag2.current_resolution, current_chr[i]); + dtostrfd(Energy.active_power[i], Settings.flag2.wattage_resolution, active_power_chr[i]); + } + + char energy_daily_chr[FLOATSZ]; + dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr); + char energy_yesterday_chr[FLOATSZ]; + dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr); + + char energy_total_chr[3][FLOATSZ]; + dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr[0]); + char export_active_chr[3][FLOATSZ]; + dtostrfd(Energy.export_active, Settings.flag2.energy_resolution, export_active_chr[0]); + uint8_t energy_total_fields = 1; + + if (Settings.tariff[0][0] != Settings.tariff[1][0]) { + dtostrfd((float)RtcSettings.energy_usage.usage1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_total_chr[1]); + dtostrfd((float)RtcSettings.energy_usage.usage2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_total_chr[2]); + dtostrfd((float)RtcSettings.energy_usage.return1_kWhtotal / 100000, Settings.flag2.energy_resolution, export_active_chr[1]); + dtostrfd((float)RtcSettings.energy_usage.return2_kWhtotal / 100000, Settings.flag2.energy_resolution, export_active_chr[2]); + energy_total_fields = 3; + } + + char value_chr[FLOATSZ *3]; + char value2_chr[FLOATSZ *3]; + char value3_chr[FLOATSZ *3]; + + if (json) { + bool show_energy_period = (0 == tele_period); + + ResponseAppend_P(PSTR(",\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL_START_TIME "\":\"%s\",\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s"), + GetDateAndTime(DT_ENERGY).c_str(), + EnergyFormatIndex(value_chr, energy_total_chr[0], json, energy_total_fields), + energy_yesterday_chr, + energy_daily_chr); + + if (!isnan(Energy.export_active)) { + ResponseAppend_P(PSTR(",\"" D_JSON_EXPORT_ACTIVE "\":%s"), + EnergyFormatIndex(value_chr, export_active_chr[0], json, energy_total_fields)); + } + + if (show_energy_period) { + float energy = 0; + if (Energy.period) { + energy = (float)(RtcSettings.energy_kWhtoday - Energy.period) / 100; + } + Energy.period = RtcSettings.energy_kWhtoday; + char energy_period_chr[FLOATSZ]; + dtostrfd(energy, Settings.flag2.wattage_resolution, energy_period_chr); + ResponseAppend_P(PSTR(",\"" D_JSON_PERIOD "\":%s"), energy_period_chr); + } + ResponseAppend_P(PSTR(",\"" D_JSON_POWERUSAGE "\":%s"), + EnergyFormat(value_chr, active_power_chr[0], json)); + if (!Energy.type_dc) { + if (Energy.current_available && Energy.voltage_available) { + ResponseAppend_P(PSTR(",\"" D_JSON_APPARENT_POWERUSAGE "\":%s,\"" D_JSON_REACTIVE_POWERUSAGE "\":%s,\"" D_JSON_POWERFACTOR "\":%s"), + EnergyFormat(value_chr, apparent_power_chr[0], json), + EnergyFormat(value2_chr, reactive_power_chr[0], json), + EnergyFormat(value3_chr, power_factor_chr[0], json)); + } + if (!isnan(Energy.frequency[0])) { + ResponseAppend_P(PSTR(",\"" D_JSON_FREQUENCY "\":%s"), + EnergyFormat(value_chr, frequency_chr[0], json, Energy.voltage_common)); + } + } + if (Energy.voltage_available) { + ResponseAppend_P(PSTR(",\"" D_JSON_VOLTAGE "\":%s"), + EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common)); + } + if (Energy.current_available) { + ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"), + EnergyFormat(value_chr, current_chr[0], json)); + } + XnrgCall(FUNC_JSON_APPEND); + ResponseJsonEnd(); + +#ifdef USE_DOMOTICZ + if (show_energy_period) { + dtostrfd(Energy.total * 1000, 1, energy_total_chr[0]); + DomoticzSensorPowerEnergy((int)Energy.active_power[0], energy_total_chr[0]); + + dtostrfd((float)RtcSettings.energy_usage.usage1_kWhtotal / 100, 1, energy_total_chr[1]); + dtostrfd((float)RtcSettings.energy_usage.usage2_kWhtotal / 100, 1, energy_total_chr[2]); + dtostrfd((float)RtcSettings.energy_usage.return1_kWhtotal / 100, 1, export_active_chr[1]); + dtostrfd((float)RtcSettings.energy_usage.return2_kWhtotal / 100, 1, export_active_chr[2]); + DomoticzSensorP1SmartMeter(energy_total_chr[1], energy_total_chr[2], export_active_chr[1], export_active_chr[2], (int)Energy.active_power[0]); + + if (Energy.voltage_available) { + DomoticzSensor(DZ_VOLTAGE, voltage_chr[0]); + } + if (Energy.current_available) { + DomoticzSensor(DZ_CURRENT, current_chr[0]); + } + } +#endif +#ifdef USE_KNX + if (show_energy_period) { + if (Energy.voltage_available) { + KnxSensor(KNX_ENERGY_VOLTAGE, Energy.voltage[0]); + } + if (Energy.current_available) { + KnxSensor(KNX_ENERGY_CURRENT, Energy.current[0]); + } + KnxSensor(KNX_ENERGY_POWER, Energy.active_power[0]); + if (!Energy.type_dc) { + KnxSensor(KNX_ENERGY_POWERFACTOR, power_factor_knx); + } + KnxSensor(KNX_ENERGY_DAILY, Energy.daily); + KnxSensor(KNX_ENERGY_TOTAL, Energy.total); + KnxSensor(KNX_ENERGY_START, Energy.start_energy); + } +#endif +#ifdef USE_WEBSERVER + } else { + if (Energy.voltage_available) { + WSContentSend_PD(HTTP_SNS_VOLTAGE, EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common)); + } + if (Energy.current_available) { + WSContentSend_PD(HTTP_SNS_CURRENT, EnergyFormat(value_chr, current_chr[0], json)); + } + WSContentSend_PD(HTTP_SNS_POWER, EnergyFormat(value_chr, active_power_chr[0], json)); + if (!Energy.type_dc) { + if (Energy.current_available && Energy.voltage_available) { + WSContentSend_PD(HTTP_ENERGY_SNS1, EnergyFormat(value_chr, apparent_power_chr[0], json), + EnergyFormat(value2_chr, reactive_power_chr[0], json), + EnergyFormat(value3_chr, power_factor_chr[0], json)); + } + if (!isnan(Energy.frequency[0])) { + WSContentSend_PD(PSTR("{s}" D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}"), + EnergyFormat(value_chr, frequency_chr[0], json, Energy.voltage_common)); + } + } + WSContentSend_PD(HTTP_ENERGY_SNS2, energy_daily_chr, energy_yesterday_chr, energy_total_chr[0]); + if (!isnan(Energy.export_active)) { + WSContentSend_PD(HTTP_ENERGY_SNS3, export_active_chr[0]); + } + + XnrgCall(FUNC_WEB_SENSOR); +#endif + } +} + + + + + +bool Xdrv03(uint8_t function) +{ + bool result = false; + + if (FUNC_PRE_INIT == function) { + energy_flg = ENERGY_NONE; + XnrgCall(FUNC_PRE_INIT); + } + else if (energy_flg) { + switch (function) { + case FUNC_LOOP: + XnrgCall(FUNC_LOOP); + break; + case FUNC_EVERY_250_MSECOND: + XnrgCall(FUNC_EVERY_250_MSECOND); + break; + case FUNC_SERIAL: + result = XnrgCall(FUNC_SERIAL); + break; +#ifdef USE_ENERGY_MARGIN_DETECTION + case FUNC_SET_POWER: + Energy.power_steady_counter = 2; + break; +#endif + case FUNC_COMMAND: + result = DecodeCommand(kEnergyCommands, EnergyCommand); + break; + } + } + return result; +} + +bool Xsns03(uint8_t function) +{ + bool result = false; + + if (energy_flg) { + switch (function) { + case FUNC_EVERY_SECOND: + EnergyEverySecond(); + break; + case FUNC_JSON_APPEND: + EnergyShow(true); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + EnergyShow(false); + break; +#endif + case FUNC_SAVE_BEFORE_RESTART: + EnergySaveState(); + break; + case FUNC_INIT: + EnergySnsInit(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_04_light.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_04_light.ino" +#ifdef USE_LIGHT +# 124 "S:/Development/Tasmota/tasmota/xdrv_04_light.ino" +#define XDRV_04 4 + + +enum LightSchemes { LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MAX }; + +const uint8_t LIGHT_COLOR_SIZE = 25; + +const char kLightCommands[] PROGMEM = "|" + D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" D_CMND_DIMMER_RANGE "|" D_CMND_LEDTABLE "|" D_CMND_FADE "|" + D_CMND_RGBWWTABLE "|" D_CMND_SCHEME "|" D_CMND_SPEED "|" D_CMND_WAKEUP "|" D_CMND_WAKEUPDURATION "|" + D_CMND_WHITE "|" D_CMND_CHANNEL "|" D_CMND_HSBCOLOR "|UNDOCA" ; + +void (* const LightCommand[])(void) PROGMEM = { + &CmndColor, &CmndColorTemperature, &CmndDimmer, &CmndDimmerRange, &CmndLedTable, &CmndFade, + &CmndRgbwwTable, &CmndScheme, &CmndSpeed, &CmndWakeup, &CmndWakeupDuration, + &CmndWhite, &CmndChannel, &CmndHsbColor, &CmndUndocA }; + + +enum LightColorModes { + LCM_RGB = 1, LCM_CT = 2, LCM_BOTH = 3 }; + +struct LRgbColor { + uint8_t R, G, B; +}; +const uint8_t MAX_FIXED_COLOR = 12; +const LRgbColor kFixedColor[MAX_FIXED_COLOR] PROGMEM = + { 255,0,0, 0,255,0, 0,0,255, 228,32,0, 0,228,32, 0,32,228, 188,64,0, 0,160,96, 160,32,240, 255,255,0, 255,0,170, 255,255,255 }; + +struct LWColor { + uint8_t W; +}; +const uint8_t MAX_FIXED_WHITE = 4; +const LWColor kFixedWhite[MAX_FIXED_WHITE] PROGMEM = { 0, 255, 128, 32 }; + +struct LCwColor { + uint8_t C, W; +}; +const uint8_t MAX_FIXED_COLD_WARM = 4; +const LCwColor kFixedColdWarm[MAX_FIXED_COLD_WARM] PROGMEM = { 0,0, 255,0, 0,255, 128,128 }; + + +const uint16_t CT_MIN = 153; +const uint16_t CT_MAX = 500; + +const uint16_t CT_MIN_ALEXA = 200; +const uint16_t CT_MAX_ALEXA = 380; + + + + + + +typedef struct gamma_table_t { + uint16_t to_src; + uint16_t to_gamma; +} gamma_table_t; + +const gamma_table_t gamma_table[] = { + { 1, 1 }, + { 4, 1 }, + { 209, 13 }, + { 312, 41 }, + { 457, 106 }, + { 626, 261 }, + { 762, 450 }, + { 895, 703 }, + { 1023, 1023 }, + { 0xFFFF, 0xFFFF } +}; + + +const gamma_table_t gamma_table_fast[] = { + { 384, 192 }, + { 768, 576 }, + { 1023, 1023 }, + { 0xFFFF, 0xFFFF } +}; +# 248 "S:/Development/Tasmota/tasmota/xdrv_04_light.ino" +struct LIGHT { + uint32_t strip_timer_counter = 0; + power_t power = 0; + + uint16_t wakeup_counter = 0; + + uint8_t entry_color[LST_MAX]; + uint8_t current_color[LST_MAX]; + uint8_t new_color[LST_MAX]; + uint8_t last_color[LST_MAX]; + uint8_t color_remap[LST_MAX]; + + uint8_t wheel = 0; + uint8_t random = 0; + uint8_t subtype = 0; + uint8_t device = 0; + uint8_t old_power = 1; + uint8_t wakeup_active = 0; + uint8_t wakeup_dimmer = 0; + uint8_t fixed_color_index = 1; + uint8_t pwm_offset = 0; + uint8_t max_scheme = LS_MAX -1; + + bool update = true; + bool pwm_multi_channels = false; + + bool fade_initialized = false; + bool fade_running = false; + uint16_t fade_start_10[LST_MAX] = {0,0,0,0,0}; + uint16_t fade_cur_10[LST_MAX]; + uint16_t fade_end_10[LST_MAX]; + uint16_t fade_duration = 0; + uint32_t fade_start = 0; +} Light; + +power_t LightPower(void) +{ + return Light.power; +} + + +#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +power_t LightPowerIRAM(void) ICACHE_RAM_ATTR; +#endif + +power_t LightPowerIRAM(void) +{ + return Light.power; +} + +uint8_t LightDevice(void) +{ + return Light.device; +} + +static uint32_t min3(uint32_t a, uint32_t b, uint32_t c) { + return (a < b && a < c) ? a : (b < c) ? b : c; +} +# 344 "S:/Development/Tasmota/tasmota/xdrv_04_light.ino" +class LightStateClass { + private: + uint16_t _hue = 0; + uint8_t _sat = 255; + uint8_t _briRGB = 255; + + uint8_t _r = 255; + uint8_t _g = 255; + uint8_t _b = 255; + + uint8_t _subtype = 0; + uint16_t _ct = CT_MIN; + uint8_t _wc = 255; + uint8_t _ww = 0; + uint8_t _briCT = 255; + + uint8_t _color_mode = LCM_RGB; + + + + + + uint16_t _ct_min_range = CT_MIN; + uint16_t _ct_max_range = CT_MAX; + + public: + LightStateClass() { + + } + + void setSubType(uint8_t sub_type) { + _subtype = sub_type; + } +# 386 "S:/Development/Tasmota/tasmota/xdrv_04_light.ino" + uint8_t setColorMode(uint8_t cm) { + uint8_t prev_cm = _color_mode; + if (cm < LCM_RGB) { cm = LCM_RGB; } + if (cm > LCM_BOTH) { cm = LCM_BOTH; } + uint8_t maxbri = (_briRGB >= _briCT) ? _briRGB : _briCT; + + switch (_subtype) { + case LST_COLDWARM: + _color_mode = LCM_CT; + break; + + case LST_NONE: + case LST_SINGLE: + case LST_RGB: + default: + _color_mode = LCM_RGB; + break; + + case LST_RGBW: + case LST_RGBCW: + _color_mode = cm; + break; + } + if (LCM_RGB == _color_mode) { + _briCT = 0; + if (0 == _briRGB) { _briRGB = maxbri; } + } + if (LCM_CT == _color_mode) { + _briRGB = 0; + if (0 == _briCT) { _briCT = maxbri; } + } +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setColorMode prev_cm (%d) req_cm (%d) new_cm (%d)", prev_cm, cm, _color_mode); +#endif + return prev_cm; + } + + inline uint8_t getColorMode() { + return _color_mode; + } + + void addRGBMode() { + setColorMode(_color_mode | LCM_RGB); + } + void addCTMode() { + setColorMode(_color_mode | LCM_CT); + } + + + void getRGB(uint8_t *r, uint8_t *g, uint8_t *b) { + if (r) { *r = _r; } + if (g) { *g = _g; } + if (b) { *b = _b; } + } + + + + void getCW(uint8_t *rc, uint8_t *rw) { + if (rc) { *rc = _wc; } + if (rw) { *rw = _ww; } + } + + + void getActualRGBCW(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *c, uint8_t *w) { + bool rgb_channels_on = _color_mode & LCM_RGB; + bool ct_channels_on = _color_mode & LCM_CT; + + if (r) { *r = rgb_channels_on ? changeUIntScale(_r, 0, 255, 0, _briRGB) : 0; } + if (g) { *g = rgb_channels_on ? changeUIntScale(_g, 0, 255, 0, _briRGB) : 0; } + if (b) { *b = rgb_channels_on ? changeUIntScale(_b, 0, 255, 0, _briRGB) : 0; } + + if (c) { *c = ct_channels_on ? changeUIntScale(_wc, 0, 255, 0, _briCT) : 0; } + if (w) { *w = ct_channels_on ? changeUIntScale(_ww, 0, 255, 0, _briCT) : 0; } + } + + uint8_t getChannels(uint8_t *channels) { + getActualRGBCW(&channels[0], &channels[1], &channels[2], &channels[3], &channels[4]); + } + + void getChannelsRaw(uint8_t *channels) { + channels[0] = _r; + channels[1] = _g; + channels[2] = _b; + channels[3] = _wc; + channels[4] = _ww; + } + + void getHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) { + if (hue) { *hue = _hue; } + if (sat) { *sat = _sat; } + if (bri) { *bri = _briRGB; } + } + + + uint8_t getBri(void) { + + return (_briRGB >= _briCT) ? _briRGB : _briCT; + } + + + inline uint8_t getBriCT() { + return _briCT; + } + + static inline uint8_t DimmerToBri(uint8_t dimmer) { + return changeUIntScale(dimmer, 0, 100, 0, 255); + } + static uint8_t BriToDimmer(uint8_t bri) { + uint8_t dimmer = changeUIntScale(bri, 0, 255, 0, 100); + + if ((dimmer == 0) && (bri > 0)) { dimmer = 1; } + return dimmer; + } + + uint8_t getDimmer(uint32_t mode = 0) { + uint8_t bri; + switch (mode) { + case 1: + bri = getBriRGB(); + break; + case 2: + bri = getBriCT(); + break; + default: + bri = getBri(); + break; + } + return BriToDimmer(bri); + } + + inline uint16_t getCT() const { + return _ct; + } + + + uint16_t getCT10bits() const { + return changeUIntScale(_ct, _ct_min_range, _ct_max_range, 0, 1023); + } + + inline void setCTRange(uint16_t ct_min_range, uint16_t ct_max_range) { + _ct_min_range = ct_min_range; + _ct_max_range = ct_max_range; + } + + inline void getCTRange(uint16_t *ct_min_range, uint16_t *ct_max_range) const { + if (ct_min_range) { *ct_min_range = _ct_min_range; } + if (ct_max_range) { *ct_max_range = _ct_max_range; } + } + + + void getXY(float *x, float *y) { + RgbToXy(_r, _g, _b, x, y); + } + + + + void setBri(uint8_t bri) { + setBriRGB(_color_mode & LCM_RGB ? bri : 0); + setBriCT(_color_mode & LCM_CT ? bri : 0); +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setBri RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB); +#endif + } + + + uint8_t setBriRGB(uint8_t bri_rgb) { + uint8_t prev_bri = _briRGB; + _briRGB = bri_rgb; + if (bri_rgb > 0) { addRGBMode(); } + return prev_bri; + } + + + uint8_t setBriCT(uint8_t bri_ct) { + uint8_t prev_bri = _briCT; + _briCT = bri_ct; + if (bri_ct > 0) { addCTMode(); } + return prev_bri; + } + + inline uint8_t getBriRGB() { + return _briRGB; + } + + void setDimmer(uint8_t dimmer) { + setBri(DimmerToBri(dimmer)); + } + + void setCT(uint16_t ct) { + if (0 == ct) { + + setColorMode(LCM_RGB); + } else { + ct = (ct < CT_MIN ? CT_MIN : (ct > CT_MAX ? CT_MAX : ct)); + _ww = changeUIntScale(ct, _ct_min_range, _ct_max_range, 0, 255); + _wc = 255 - _ww; + _ct = ct; + addCTMode(); + } +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setCT RGB raw (%d %d %d) HS (%d %d) briRGB (%d) briCT (%d) CT (%d)", _r, _g, _b, _hue, _sat, _briRGB, _briCT, _ct); +#endif + } +# 604 "S:/Development/Tasmota/tasmota/xdrv_04_light.ino" + void setCW(uint8_t c, uint8_t w, bool free_range = false) { + uint16_t max = (w > c) ? w : c; + uint16_t sum = c + w; + + if (0 == max) { + _briCT = 0; + setColorMode(LCM_RGB); + } else { + if (!free_range) { + + _ww = changeUIntScale(w, 0, sum, 0, 255); + _wc = 255 - _ww; + } else { + _ww = changeUIntScale(w, 0, max, 0, 255); + _wc = changeUIntScale(c, 0, max, 0, 255); + } + _ct = changeUIntScale(w, 0, sum, _ct_min_range, _ct_max_range); + addCTMode(); + if (_color_mode & LCM_CT) { _briCT = free_range ? max : (sum > 255 ? 255 : sum); } + } +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setCW CW (%d %d) CT (%d) briCT (%d)", c, w, _ct, _briCT); +#endif + } + + + uint8_t setRGB(uint8_t r, uint8_t g, uint8_t b, bool keep_bri = false) { + uint16_t hue; + uint8_t sat; +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setRGB RGB input (%d %d %d)", r, g, b); +#endif + + uint32_t max = (r > g && r > b) ? r : (g > b) ? g : b; + + if (0 == max) { + r = g = b = 255; + setColorMode(LCM_CT); + } else { + if (255 > max) { + + r = changeUIntScale(r, 0, max, 0, 255); + g = changeUIntScale(g, 0, max, 0, 255); + b = changeUIntScale(b, 0, max, 0, 255); + } + addRGBMode(); + } + if (!keep_bri) { + _briRGB = (_color_mode & LCM_RGB) ? max : 0; + } + + RgbToHsb(r, g, b, &hue, &sat, nullptr); + _r = r; + _g = g; + _b = b; + _hue = hue; + _sat = sat; +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setRGB RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB); +#endif + return max; + } + + void setHS(uint16_t hue, uint8_t sat) { + uint8_t r, g, b; + HsToRgb(hue, sat, &r, &g, &b); + _r = r; + _g = g; + _b = b; + _hue = hue; + _sat = sat; + addRGBMode(); +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setHS HS (%d %d) rgb (%d %d %d)", hue, sat, r, g, b); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setHS RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB); +#endif + } + + + + void setChannelsRaw(uint8_t *channels) { + _r = channels[0]; + _g = channels[1]; + _b = channels[2]; + _wc = channels[3]; + _ww = channels[4]; +} + + + + + void setChannels(uint8_t *channels) { + setRGB(channels[0], channels[1], channels[2]); + setCW(channels[3], channels[4], true); +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setChannels (%d %d %d %d %d)", + channels[0], channels[1], channels[2], channels[3], channels[4]); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setChannels CT (%d) briRGB (%d) briCT (%d)", _ct, _briRGB, _briCT); +#endif + } + + + static void RgbToHsb(uint8_t r, uint8_t g, uint8_t b, uint16_t *r_hue, uint8_t *r_sat, uint8_t *r_bri); + static void HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b); + static void RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y); + static void XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb); + +}; +# 720 "S:/Development/Tasmota/tasmota/xdrv_04_light.ino" +void LightStateClass::RgbToHsb(uint8_t ir, uint8_t ig, uint8_t ib, uint16_t *r_hue, uint8_t *r_sat, uint8_t *r_bri) { + uint32_t r = ir; + uint32_t g = ig; + uint32_t b = ib; + uint32_t max = (r > g && r > b) ? r : (g > b) ? g : b; + uint32_t min = (r < g && r < b) ? r : (g < b) ? g : b; + uint32_t d = max - min; + + uint16_t hue = 0; + uint8_t sat = 0; + uint8_t bri = max; + + if (d != 0) { + sat = changeUIntScale(d, 0, max, 0, 255); + if (r == max) { + hue = (g > b) ? changeUIntScale(g-b,0,d,0,60) : 360 - changeUIntScale(b-g,0,d,0,60); + } else if (g == max) { + hue = (b > r) ? 120 + changeUIntScale(b-r,0,d,0,60) : 120 - changeUIntScale(r-b,0,d,0,60); + } else { + hue = (r > g) ? 240 + changeUIntScale(r-g,0,d,0,60) : 240 - changeUIntScale(g-r,0,d,0,60); + } + hue = hue % 360; + } + + if (r_hue) *r_hue = hue; + if (r_sat) *r_sat = sat; + if (r_bri) *r_bri = bri; + +} + +void LightStateClass::HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b) { + uint32_t r = 255; + uint32_t g = 255; + uint32_t b = 255; + + hue = hue % 360; + + if (sat > 0) { + uint32_t i = hue / 60; + uint32_t f = hue % 60; + uint32_t q = 255 - changeUIntScale(f, 0, 60, 0, sat); + uint32_t p = 255 - sat; + uint32_t t = 255 - changeUIntScale(60 - f, 0, 60, 0, sat); + + switch (i) { + case 0: + + g = t; + b = p; + break; + case 1: + r = q; + + b = p; + break; + case 2: + r = p; + + b = t; + break; + case 3: + r = p; + g = q; + + break; + case 4: + r = t; + g = p; + + break; + default: + + g = p; + b = q; + break; + } + } + if (r_r) *r_r = r; + if (r_g) *r_g = g; + if (r_b) *r_b = b; +} + +#define POW FastPrecisePowf + +void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y) { + float x = 0.31271f; + float y = 0.32902f; + + if (i_r + i_b + i_g > 0) { + float r = (float)i_r / 255.0f; + float g = (float)i_g / 255.0f; + float b = (float)i_b / 255.0f; + + + r = (r > 0.04045f) ? POW((r + 0.055f) / (1.0f + 0.055f), 2.4f) : (r / 12.92f); + g = (g > 0.04045f) ? POW((g + 0.055f) / (1.0f + 0.055f), 2.4f) : (g / 12.92f); + b = (b > 0.04045f) ? POW((b + 0.055f) / (1.0f + 0.055f), 2.4f) : (b / 12.92f); + + + + float X = r * 0.649926f + g * 0.103455f + b * 0.197109f; + float Y = r * 0.234327f + g * 0.743075f + b * 0.022598f; + float Z = r * 0.000000f + g * 0.053077f + b * 1.035763f; + + x = X / (X + Y + Z); + y = Y / (X + Y + Z); + + } + if (r_x) *r_x = x; + if (r_y) *r_y = y; +} + +void LightStateClass::XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb) +{ + x = (x > 0.99f ? 0.99f : (x < 0.01f ? 0.01f : x)); + y = (y > 0.99f ? 0.99f : (y < 0.01f ? 0.01f : y)); + float z = 1.0f - x - y; + + float X = x / y; + float Z = z / y; + + + + float r = X * 3.2406f - 1.5372f - Z * 0.4986f; + float g = -X * 0.9689f + 1.8758f + Z * 0.0415f; + float b = X * 0.0557f - 0.2040f + Z * 1.0570f; + float max = (r > g && r > b) ? r : (g > b) ? g : b; + r = r / max; + g = g / max; + b = b / max; + r = (r <= 0.0031308f) ? 12.92f * r : 1.055f * POW(r, (1.0f / 2.4f)) - 0.055f; + g = (g <= 0.0031308f) ? 12.92f * g : 1.055f * POW(g, (1.0f / 2.4f)) - 0.055f; + b = (b <= 0.0031308f) ? 12.92f * b : 1.055f * POW(b, (1.0f / 2.4f)) - 0.055f; + + + + + + int32_t ir = r * 255.0f + 0.5f; + int32_t ig = g * 255.0f + 0.5f; + int32_t ib = b * 255.0f + 0.5f; + if (rr) { *rr = (ir > 255 ? 255: (ir < 0 ? 0 : ir)); } + if (rg) { *rg = (ig > 255 ? 255: (ig < 0 ? 0 : ig)); } + if (rb) { *rb = (ib > 255 ? 255: (ib < 0 ? 0 : ib)); } +} + +class LightControllerClass { +private: + LightStateClass *_state; + + + bool _ct_rgb_linked = true; + bool _pwm_multi_channels = false; + +public: + LightControllerClass(LightStateClass& state) { + _state = &state; + } + + void setSubType(uint8_t sub_type) { + _state->setSubType(sub_type); + } + + inline bool setCTRGBLinked(bool ct_rgb_linked) { + bool prev = _ct_rgb_linked; + if (_pwm_multi_channels) { + _ct_rgb_linked = false; + } else { + _ct_rgb_linked = ct_rgb_linked; + } + return prev; + } + + void setAlexaCTRange(bool alexa_ct_range) { + + if (alexa_ct_range) { + _state->setCTRange(CT_MIN_ALEXA, CT_MAX_ALEXA); + } else { + _state->setCTRange(CT_MIN, CT_MAX); + } + } + + inline bool isCTRGBLinked() { + return _ct_rgb_linked; + } + + inline bool setPWMMultiChannel(bool pwm_multi_channels) { + bool prev = _pwm_multi_channels; + _pwm_multi_channels = pwm_multi_channels; + if (pwm_multi_channels) setCTRGBLinked(false); + return prev; + } + + inline bool isPWMMultiChannel(void) { + return _pwm_multi_channels; + } + +#ifdef DEBUG_LIGHT + void debugLogs() { + uint8_t r,g,b,c,w; + _state->getActualRGBCW(&r,&g,&b,&c,&w); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::debugLogs rgb (%d %d %d) cw (%d %d)", + r, g, b, c, w); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::debugLogs lightCurrent (%d %d %d %d %d)", + Light.current_color[0], Light.current_color[1], Light.current_color[2], + Light.current_color[3], Light.current_color[4]); + } +#endif + + void loadSettings() { +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::loadSettings Settings.light_color (%d %d %d %d %d - %d)", + Settings.light_color[0], Settings.light_color[1], Settings.light_color[2], + Settings.light_color[3], Settings.light_color[4], Settings.light_dimmer); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::loadSettings light_type/sub (%d %d)", + light_type, Light.subtype); +#endif + if (_pwm_multi_channels) { + _state->setChannelsRaw(Settings.light_color); + } else { + + _state->setCW(Settings.light_color[3], Settings.light_color[4], true); + _state->setRGB(Settings.light_color[0], Settings.light_color[1], Settings.light_color[2]); + + + + uint8_t bri = _state->DimmerToBri(Settings.light_dimmer); + if (Settings.light_color[0] + Settings.light_color[1] + Settings.light_color[2] > 0) { + + + if ( (DEFAULT_LIGHT_COMPONENT == Settings.light_color[0]) && + (DEFAULT_LIGHT_COMPONENT == Settings.light_color[1]) && + (DEFAULT_LIGHT_COMPONENT == Settings.light_color[2]) && + (DEFAULT_LIGHT_COMPONENT == Settings.light_color[3]) && + (DEFAULT_LIGHT_COMPONENT == Settings.light_color[4]) && + (DEFAULT_LIGHT_DIMMER == Settings.light_dimmer) ) { + _state->setColorMode(LCM_RGB); + } + _state->setBriRGB(bri); + } else { + _state->setBriCT(bri); + } + } + } + + void changeCTB(uint16_t new_ct, uint8_t briCT) { + + + + + + + if ((LST_COLDWARM != Light.subtype) && (LST_RGBW > Light.subtype)) { + return; + } + _state->setCT(new_ct); + _state->setBriCT(briCT); + if (_ct_rgb_linked) { _state->setColorMode(LCM_CT); } + saveSettings(); + calcLevels(); + + } + + void changeDimmer(uint8_t dimmer, uint32_t mode = 0) { + uint8_t bri = changeUIntScale(dimmer, 0, 100, 0, 255); + switch (mode) { + case 1: + changeBriRGB(bri); + if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); } + break; + case 2: + changeBriCT(bri); + if (_ct_rgb_linked) { _state->setColorMode(LCM_CT); } + break; + default: + changeBri(bri); + break; + } + } + + void changeBri(uint8_t bri) { + _state->setBri(bri); + saveSettings(); + calcLevels(); + } + + void changeBriRGB(uint8_t bri) { + _state->setBriRGB(bri); + saveSettings(); + calcLevels(); + } + + void changeBriCT(uint8_t bri) { + _state->setBriCT(bri); + saveSettings(); + calcLevels(); + } + + void changeRGB(uint8_t r, uint8_t g, uint8_t b, bool keep_bri = false) { + _state->setRGB(r, g, b, keep_bri); + if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); } + saveSettings(); + calcLevels(); + } + + + + void calcLevels(uint8_t *current_color = nullptr) { + uint8_t r,g,b,c,w,briRGB,briCT; + if (current_color == nullptr) { current_color = Light.current_color; } + + if (_pwm_multi_channels) { + _state->getChannelsRaw(current_color); + return; + } + + _state->getActualRGBCW(&r,&g,&b,&c,&w); + briRGB = _state->getBriRGB(); + briCT = _state->getBriCT(); + + current_color[0] = current_color[1] = current_color[2] = 0; + current_color[3] = current_color[4] = 0; + switch (Light.subtype) { + case LST_NONE: + current_color[0] = 255; + break; + case LST_SINGLE: + current_color[0] = briRGB; + break; + case LST_COLDWARM: + current_color[0] = c; + current_color[1] = w; + break; + case LST_RGBW: + case LST_RGBCW: + if (LST_RGBCW == Light.subtype) { + current_color[3] = c; + current_color[4] = w; + } else { + current_color[3] = briCT; + } + + case LST_RGB: + current_color[0] = r; + current_color[1] = g; + current_color[2] = b; + break; + } + } + + void changeHSB(uint16_t hue, uint8_t sat, uint8_t briRGB) { + _state->setHS(hue, sat); + _state->setBriRGB(briRGB); + if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); } + saveSettings(); + calcLevels(); + } + + + void saveSettings() { + if (Light.pwm_multi_channels) { + + _state->getChannelsRaw(Settings.light_color); + Settings.light_dimmer = 100; + } else { + uint8_t cm = _state->getColorMode(); + + memset(&Settings.light_color[0], 0, sizeof(Settings.light_color)); + if (LCM_RGB & cm) { + _state->getRGB(&Settings.light_color[0], &Settings.light_color[1], &Settings.light_color[2]); + Settings.light_dimmer = _state->BriToDimmer(_state->getBriRGB()); + + if (LCM_BOTH == cm) { + + _state->getActualRGBCW(nullptr, nullptr, nullptr, &Settings.light_color[3], &Settings.light_color[4]); + } + } else if (LCM_CT == cm) { + _state->getCW(&Settings.light_color[3], &Settings.light_color[4]); + Settings.light_dimmer = _state->BriToDimmer(_state->getBriCT()); + } + } +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::saveSettings Settings.light_color (%d %d %d %d %d - %d)", + Settings.light_color[0], Settings.light_color[1], Settings.light_color[2], + Settings.light_color[3], Settings.light_color[4], Settings.light_dimmer); +#endif + } + + + + + void changeChannels(uint8_t *channels) { + if (Light.pwm_multi_channels) { + _state->setChannelsRaw(channels); + } else if (LST_COLDWARM == Light.subtype) { + + uint8_t remapped_channels[5] = {0,0,0,channels[0],channels[1]}; + _state->setChannels(remapped_channels); + } else { + _state->setChannels(channels); + } + + saveSettings(); + calcLevels(); + } +}; + + + +LightStateClass light_state = LightStateClass(); +LightControllerClass light_controller = LightControllerClass(light_state); + + + + + +uint16_t change8to10(uint8_t v) { + return changeUIntScale(v, 0, 255, 0, 1023); +} + +uint8_t change10to8(uint16_t v) { + return (0 == v) ? 0 : changeUIntScale(v, 4, 1023, 1, 255); +} + + + + + +uint16_t ledGamma_internal(uint16_t v, const struct gamma_table_t *gt_ptr) { + uint16_t from_src = 0; + uint16_t from_gamma = 0; + + for (const gamma_table_t *gt = gt_ptr; ; gt++) { + uint16_t to_src = gt->to_src; + uint16_t to_gamma = gt->to_gamma; + if (v <= to_src) { + return changeUIntScale(v, from_src, to_src, from_gamma, to_gamma); + } + from_src = to_src; + from_gamma = to_gamma; + } +} + +uint16_t ledGammaReverse_internal(uint16_t vg, const struct gamma_table_t *gt_ptr) { + uint16_t from_src = 0; + uint16_t from_gamma = 0; + + for (const gamma_table_t *gt = gt_ptr; ; gt++) { + uint16_t to_src = gt->to_src; + uint16_t to_gamma = gt->to_gamma; + if (vg <= to_gamma) { + return changeUIntScale(vg, from_gamma, to_gamma, from_src, to_src); + } + from_src = to_src; + from_gamma = to_gamma; + } +} + + +uint16_t ledGamma10_10(uint16_t v) { + return ledGamma_internal(v, gamma_table); +} + +uint16_t ledGamma10(uint8_t v) { + return ledGamma10_10(change8to10(v)); +} + + +uint8_t ledGamma(uint8_t v) { + return change10to8(ledGamma10(v)); +} + + + +void LightPwmOffset(uint32_t offset) +{ + Light.pwm_offset = offset; +} + +bool LightModuleInit(void) +{ + light_type = LT_BASIC; + + if (Settings.flag.pwm_control) { + for (uint32_t i = 0; i < MAX_PWMS; i++) { + if (pin[GPIO_PWM1 +i] < 99) { light_type++; } + } + } + + light_flg = 0; + if (XlgtCall(FUNC_MODULE_INIT)) { + + } + else if (SONOFF_BN == my_module_type) { + light_type = LT_PWM1; + } + else if (SONOFF_LED == my_module_type) { + if (!my_module.io[4]) { + pinMode(4, OUTPUT); + digitalWrite(4, LOW); + } + if (!my_module.io[5]) { + pinMode(5, OUTPUT); + digitalWrite(5, LOW); + } + if (!my_module.io[14]) { + pinMode(14, OUTPUT); + digitalWrite(14, LOW); + } + light_type = LT_PWM2; + } + + if (light_type > LT_BASIC) { + devices_present++; + } + + + if (Settings.flag3.pwm_multi_channels) { + uint32_t pwm_channels = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7); + if (0 == pwm_channels) { pwm_channels = 1; } + devices_present += pwm_channels - 1; + } else if ((Settings.param[P_RGB_REMAP] & 128) && (LST_RGBW <= (light_type & 7))) { + + devices_present++; + } + + return (light_type > LT_BASIC); +} + +void LightInit(void) +{ + Light.device = devices_present; + Light.subtype = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7); + Light.pwm_multi_channels = Settings.flag3.pwm_multi_channels; + + if (LST_RGBW <= Light.subtype) { + + + bool ct_rgb_linked = !(Settings.param[P_RGB_REMAP] & 128); + light_controller.setCTRGBLinked(ct_rgb_linked); + } + + if ((LST_SINGLE <= Light.subtype) && Light.pwm_multi_channels) { + + light_controller.setPWMMultiChannel(true); + Light.device = devices_present - Light.subtype + 1; + } else if (!light_controller.isCTRGBLinked()) { + + Light.device--; + } +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightInit Light.pwm_multi_channels=%d Light.subtype=%d Light.device=%d devices_present=%d", + Light.pwm_multi_channels, Light.subtype, Light.device, devices_present); +#endif + + light_controller.setSubType(Light.subtype); + light_controller.loadSettings(); + light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range); + + if (LST_SINGLE == Light.subtype) { + Settings.light_color[0] = 255; + } + if (light_type < LT_PWM6) { + for (uint32_t i = 0; i < light_type; i++) { + Settings.pwm_value[i] = 0; + if (pin[GPIO_PWM1 +i] < 99) { + pinMode(pin[GPIO_PWM1 +i], OUTPUT); + } + } + if (pin[GPIO_ARIRFRCV] < 99) { + if (pin[GPIO_ARIRFSEL] < 99) { + pinMode(pin[GPIO_ARIRFSEL], OUTPUT); + digitalWrite(pin[GPIO_ARIRFSEL], 1); + } + } + } + + uint32_t max_scheme = Light.max_scheme; + if (Light.subtype < LST_RGB) { + max_scheme = LS_POWER; + } + if ((LS_WAKEUP == Settings.light_scheme) || (Settings.light_scheme > max_scheme)) { + Settings.light_scheme = LS_POWER; + } + Light.power = 0; + Light.update = true; + Light.wakeup_active = 0; + + LightUpdateColorMapping(); +} + +void LightUpdateColorMapping(void) +{ + uint8_t param = Settings.param[P_RGB_REMAP] & 127; + if (param > 119){ param = 0; } + + uint8_t tmp[] = {0,1,2,3,4}; + Light.color_remap[0] = tmp[param / 24]; + for (uint32_t i = param / 24; i<4; ++i){ + tmp[i] = tmp[i+1]; + } + param = param % 24; + Light.color_remap[1] = tmp[(param / 6)]; + for (uint32_t i = param / 6; i<3; ++i){ + tmp[i] = tmp[i+1]; + } + param = param % 6; + Light.color_remap[2] = tmp[(param / 2)]; + for (uint32_t i = param / 2; i<2; ++i){ + tmp[i] = tmp[i+1]; + } + param = param % 2; + Light.color_remap[3] = tmp[param]; + Light.color_remap[4] = tmp[1-param]; + + Light.update = true; + +} + +uint8_t LightGetDimmer(uint8_t dimmer) { + return light_state.getDimmer(dimmer); +} + +void LightSetDimmer(uint8_t dimmer) { + light_controller.changeDimmer(dimmer); +} + +void LightGetHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) { + light_state.getHSB(hue, sat, bri); +} + +void LightHsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b) { + light_state.HsToRgb(hue, sat, r_r, r_g, r_b); +} + + +uint8_t LightGetBri(uint8_t device) { + uint8_t bri = 254; + if (Light.pwm_multi_channels) { + if ((device >= Light.device) && (device < Light.device + LST_MAX) && (device <= devices_present)) { + bri = Light.current_color[device - Light.device]; + } + } else if (light_controller.isCTRGBLinked()) { + if (device == Light.device) { + bri = light_state.getBri(); + } + } else { + if (device == Light.device) { + bri = light_state.getBriRGB(); + } else if (device == Light.device + 1) { + bri = light_state.getBriCT(); + } + } + return bri; +} + + +void LightSetBri(uint8_t device, uint8_t bri) { + if (Light.pwm_multi_channels) { + if ((device >= Light.device) && (device < Light.device + LST_MAX) && (device <= devices_present)) { + Light.current_color[device - Light.device] = bri; + light_controller.changeChannels(Light.current_color); + } + } else if (light_controller.isCTRGBLinked()) { + if (device == Light.device) { + light_controller.changeBri(bri); + } + } else { + if (device == Light.device) { + light_controller.changeBriRGB(bri); + } else if (device == Light.device + 1) { + light_controller.changeBriCT(bri); + } + } +} + +void LightSetColorTemp(uint16_t ct) +{ + + + + + + + if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) { + return; + } + light_controller.changeCTB(ct, light_state.getBriCT()); +} + +uint16_t LightGetColorTemp(void) +{ + + if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) { + return 0; + } + return (light_state.getColorMode() & LCM_CT) ? light_state.getCT() : 0; +} + +void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value) +{ + + + + if (Settings.flag.light_signal) { + uint16_t signal = changeUIntScale(value, lo, hi, 0, 255); + + light_controller.changeRGB(signal, 255 - signal, 0, true); + Settings.light_scheme = 0; + if (0 == light_state.getBri()) { + light_controller.changeBri(50); + } + } +} + + +char* LightGetColor(char* scolor, boolean force_hex = false) +{ + light_controller.calcLevels(); + scolor[0] = '\0'; + for (uint32_t i = 0; i < Light.subtype; i++) { + if (!force_hex && Settings.flag.decimal_text) { + snprintf_P(scolor, LIGHT_COLOR_SIZE, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Light.current_color[i]); + } else { + snprintf_P(scolor, LIGHT_COLOR_SIZE, PSTR("%s%02X"), scolor, Light.current_color[i]); + } + } + return scolor; +} + +void LightPowerOn(void) +{ + if (light_state.getBri() && !(Light.power)) { + ExecuteCommandPower(Light.device, POWER_ON, SRC_LIGHT); + } +} + +void LightState(uint8_t append) +{ + char scolor[LIGHT_COLOR_SIZE]; + char scommand[33]; + bool unlinked = !light_controller.isCTRGBLinked() && (Light.subtype >= LST_RGBW); + + if (append) { + ResponseAppend_P(PSTR(",")); + } else { + Response_P(PSTR("{")); + } + if (!Light.pwm_multi_channels) { + if (unlinked) { + + ResponseAppend_P(PSTR("\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "%d\":%d" + ",\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "%d\":%d"), + Light.device, GetStateText(Light.power & 1), Light.device, light_state.getDimmer(1), + Light.device + 1, GetStateText(Light.power & 2 ? 1 : 0), Light.device + 1, light_state.getDimmer(2)); + } else { + GetPowerDevice(scommand, Light.device, sizeof(scommand), Settings.flag.device_index_enable); + ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_DIMMER "\":%d"), scommand, GetStateText(Light.power & 1), + light_state.getDimmer()); + } + + + if (Light.subtype > LST_SINGLE) { + ResponseAppend_P(PSTR(",\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor)); + if (LST_RGB <= Light.subtype) { + uint16_t hue; + uint8_t sat, bri; + light_state.getHSB(&hue, &sat, &bri); + sat = changeUIntScale(sat, 0, 255, 0, 100); + bri = changeUIntScale(bri, 0, 255, 0, 100); + + ResponseAppend_P(PSTR(",\"" D_CMND_HSBCOLOR "\":\"%d,%d,%d\""), hue,sat,bri); + } + + if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) { + ResponseAppend_P(PSTR(",\"" D_CMND_WHITE "\":%d"), light_state.getDimmer(2)); + } + + if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) { + ResponseAppend_P(PSTR(",\"" D_CMND_COLORTEMPERATURE "\":%d"), light_state.getCT()); + } + + ResponseAppend_P(PSTR(",\"" D_CMND_CHANNEL "\":[" )); + for (uint32_t i = 0; i < Light.subtype; i++) { + uint8_t channel_raw = Light.current_color[i]; + uint8_t channel = changeUIntScale(channel_raw,0,255,0,100); + + if ((0 == channel) && (channel_raw > 0)) { channel = 1; } + ResponseAppend_P(PSTR("%s%d" ), (i > 0 ? "," : ""), channel); + } + ResponseAppend_P(PSTR("]")); + } + + if (append) { + if (Light.subtype >= LST_RGB) { + ResponseAppend_P(PSTR(",\"" D_CMND_SCHEME "\":%d"), Settings.light_scheme); + } + if (Light.max_scheme > LS_MAX) { + ResponseAppend_P(PSTR(",\"" D_CMND_WIDTH "\":%d"), Settings.light_width); + } + ResponseAppend_P(PSTR(",\"" D_CMND_FADE "\":\"%s\",\"" D_CMND_SPEED "\":%d,\"" D_CMND_LEDTABLE "\":\"%s\""), + GetStateText(Settings.light_fade), Settings.light_speed, GetStateText(Settings.light_correction)); + } + } else { + for (uint32_t i = 0; i < Light.subtype; i++) { + GetPowerDevice(scommand, Light.device + i, sizeof(scommand), 1); + uint32_t light_power_masked = Light.power & (1 << i); + light_power_masked = light_power_masked ? 1 : 0; + ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_CHANNEL "%d\":%d,"), scommand, GetStateText(light_power_masked), Light.device + i, + changeUIntScale(Light.current_color[i], 0, 255, 0, 100)); + } + ResponseAppend_P(PSTR("\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor)); + } + + if (!append) { + ResponseJsonEnd(); + } +} + +void LightPreparePower(power_t channels = 0xFFFFFFFF) { +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower power=%d Light.power=%d", power, Light.power); +#endif + + if (Light.pwm_multi_channels) { + for (uint32_t i = 0; i < Light.subtype; i++) { + if (bitRead(channels, i)) { + + if ((Light.current_color[i]) && (!bitRead(Light.power, i))) { + if (!Settings.flag.not_power_linked) { + ExecuteCommandPower(Light.device + i, POWER_ON_NO_STATE, SRC_LIGHT); + } + } else { + + if ((0 == Light.current_color[i]) && bitRead(Light.power, i)) { + ExecuteCommandPower(Light.device + i, POWER_OFF_NO_STATE, SRC_LIGHT); + } + } + #ifdef USE_DOMOTICZ + DomoticzUpdatePowerState(Light.device + i); + #endif + } + } + } else { + if (light_controller.isCTRGBLinked()) { + if (light_state.getBri() && !(Light.power)) { + if (!Settings.flag.not_power_linked) { + ExecuteCommandPower(Light.device, POWER_ON_NO_STATE, SRC_LIGHT); + } + } else if (!light_state.getBri() && Light.power) { + ExecuteCommandPower(Light.device, POWER_OFF_NO_STATE, SRC_LIGHT); + } + } else { + + if (channels & 1) { + if (light_state.getBriRGB() && !(Light.power & 1)) { + if (!Settings.flag.not_power_linked) { + ExecuteCommandPower(Light.device, POWER_ON_NO_STATE, SRC_LIGHT); + } + } else if (!light_state.getBriRGB() && (Light.power & 1)) { + ExecuteCommandPower(Light.device, POWER_OFF_NO_STATE, SRC_LIGHT); + } + } + + if (channels & 2) { + if (light_state.getBriCT() && !(Light.power & 2)) { + if (!Settings.flag.not_power_linked) { + ExecuteCommandPower(Light.device + 1, POWER_ON_NO_STATE, SRC_LIGHT); + } + } else if (!light_state.getBriCT() && (Light.power & 2)) { + ExecuteCommandPower(Light.device + 1, POWER_OFF_NO_STATE, SRC_LIGHT); + } + } + } +#ifdef USE_DOMOTICZ + DomoticzUpdatePowerState(Light.device); +#endif + } + + if (Settings.flag3.hass_tele_on_power) { + MqttPublishTeleState(); + } + +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower End power=%d Light.power=%d", power, Light.power); +#endif + Light.power = power >> (Light.device - 1); + LightState(0); +} + +void LightCycleColor(int8_t direction) +{ + if (Light.strip_timer_counter % (Settings.light_speed * 2)) { + return; + } + + if (0 == direction) { + if (Light.random == Light.wheel) { + Light.random = random(255); + + uint8_t my_dir = (Light.random < Light.wheel -128) ? 1 : + (Light.random < Light.wheel ) ? 0 : + (Light.random > Light.wheel +128) ? 0 : 1; + Light.random = (Light.random & 0xFE) | my_dir; + + + } + + direction = (Light.random &0x01) ? 1 : -1; + } + Light.wheel += direction; + uint16_t hue = changeUIntScale(Light.wheel, 0, 255, 0, 359); + + + + uint8_t sat; + light_state.getHSB(nullptr, &sat, nullptr); + light_state.setHS(hue, sat); + light_controller.calcLevels(Light.new_color); +} + +void LightSetPower(void) +{ + + Light.old_power = Light.power; + + uint32_t mask = 1; + if (Light.pwm_multi_channels) { + mask = (1 << Light.subtype) - 1; + } else if (!light_controller.isCTRGBLinked()) { + mask = 3; + } + uint32_t shift = Light.device - 1; + + + + + + Light.power = (XdrvMailbox.index & (mask << shift)) >> shift; + if (Light.wakeup_active) { + Light.wakeup_active--; + } +#ifdef DEBUG_LIGHT + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightSetPower XdrvMailbox.index=%d Light.old_power=%d Light.power=%d mask=%d shift=%d", + XdrvMailbox.index, Light.old_power, Light.power, mask, shift); +#endif + if (Light.power != Light.old_power) { + Light.update = true; + } + LightAnimate(); +} + + + + +void LightAnimate(void) +{ + uint16_t light_still_on = 0; + bool power_off = false; + + + light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range); + Light.strip_timer_counter++; + + + + if (Light.power || Light.fade_running) { + if (Settings.sleep > PWM_MAX_SLEEP) { + sleep = PWM_MAX_SLEEP; + } else { + sleep = Settings.sleep; + } + } else { + sleep = Settings.sleep; + } + + if (!Light.power) { + Light.strip_timer_counter = 0; + if (Settings.light_scheme >= LS_MAX) { + power_off = true; + } + } else { + switch (Settings.light_scheme) { + case LS_POWER: + light_controller.calcLevels(Light.new_color); + break; + case LS_WAKEUP: + if (2 == Light.wakeup_active) { + Light.wakeup_active = 1; + for (uint32_t i = 0; i < Light.subtype; i++) { + Light.new_color[i] = 0; + } + Light.wakeup_counter = 0; + Light.wakeup_dimmer = 0; + } + Light.wakeup_counter++; + if (Light.wakeup_counter > ((Settings.light_wakeup * STATES) / Settings.light_dimmer)) { + Light.wakeup_counter = 0; + Light.wakeup_dimmer++; + if (Light.wakeup_dimmer <= Settings.light_dimmer) { + light_state.setDimmer(Light.wakeup_dimmer); + light_controller.calcLevels(); + for (uint32_t i = 0; i < Light.subtype; i++) { + Light.new_color[i] = Light.current_color[i]; + } + } else { + + + + + Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\"")); + LightState(1); + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WAKEUP)); + XdrvRulesProcess(); + + Light.wakeup_active = 0; + Settings.light_scheme = LS_POWER; + } + } + break; + case LS_CYCLEUP: + LightCycleColor(1); + break; + case LS_CYCLEDN: + LightCycleColor(-1); + break; + case LS_RANDOM: + LightCycleColor(0); + break; + default: + XlgtCall(FUNC_SET_SCHEME); + } + } + + if ((Settings.light_scheme < LS_MAX) || power_off) { + + + LightApplyPower(Light.new_color, Light.power); + + + + + + + + if (memcmp(Light.last_color, Light.new_color, Light.subtype)) { + Light.update = true; + } + if (Light.update) { +#ifdef USE_DEVICE_GROUPS + if (Light.power) LightSendDeviceGroupStatus(); +#endif + + uint16_t cur_col_10[LST_MAX]; + Light.update = false; + + + for (uint32_t i = 0; i < LST_MAX; i++) { + Light.last_color[i] = Light.new_color[i]; + + cur_col_10[i] = change8to10(Light.new_color[i]); + } + + if (Light.pwm_multi_channels) { + calcGammaMultiChannels(cur_col_10); + } else { + calcGammaBulbs(cur_col_10); + + + + if ((LST_RGBW <= Light.subtype) && (0 == Settings.rgbwwTable[4]) && (0 == cur_col_10[3]+cur_col_10[4])) { + uint32_t min_rgb_10 = min3(cur_col_10[0], cur_col_10[1], cur_col_10[2]); + for (uint32_t i=0; i<3; i++) { + + uint32_t adjust10 = change8to10(Settings.rgbwwTable[i]); + cur_col_10[i] = changeUIntScale(cur_col_10[i] - min_rgb_10, 0, 1023, 0, adjust10); + } + + + uint32_t adjust_w_10 = changeUIntScale(Settings.rgbwwTable[3], 0, 255, 0, 1023); + uint32_t white_10 = changeUIntScale(min_rgb_10, 0, 1023, 0, adjust_w_10); + if (LST_RGBW == Light.subtype) { + + cur_col_10[3] = white_10; + } else { + + uint32_t ct = light_state.getCT10bits(); + cur_col_10[4] = changeUIntScale(ct, 0, 1023, 0, white_10); + cur_col_10[3] = white_10 - cur_col_10[4]; + } + } + } + + + if (0 != Settings.rgbwwTable[4]) { + for (uint32_t i = 0; i 0) ? changeUIntScale(cur_col_10[i], 1, 1023, 1, Settings.pwm_range) : 0; + } + + + uint16_t orig_col_10bits[LST_MAX]; + memcpy(orig_col_10bits, cur_col_10, sizeof(orig_col_10bits)); + for (uint32_t i = 0; i < LST_MAX; i++) { + cur_col_10[i] = orig_col_10bits[Light.color_remap[i]]; + } + + if (!Settings.light_fade || power_off || (!Light.fade_initialized)) { + + memcpy(Light.fade_start_10, cur_col_10, sizeof(Light.fade_start_10)); + + LightSetOutputs(cur_col_10); + Light.fade_initialized = true; + } else { + if (Light.fade_running) { + + memcpy(Light.fade_start_10, Light.fade_cur_10, sizeof(Light.fade_start_10)); + } + memcpy(Light.fade_end_10, cur_col_10, sizeof(Light.fade_start_10)); + Light.fade_running = true; + Light.fade_duration = 0; + Light.fade_start = 0; + + } + } + if (Light.fade_running) { + if (LightApplyFade()) { + + + + LightSetOutputs(Light.fade_cur_10); + } + } + } +} + +bool isChannelGammaCorrected(uint32_t channel) { + if (!Settings.light_correction) { return false; } + if (channel >= Light.subtype) { return false; } + + if (PHILIPS == my_module_type) { + if ((LST_COLDWARM == Light.subtype) && (1 == channel)) { return false; } + if ((LST_RGBCW == Light.subtype) && (4 == channel)) { return false; } + } + return true; +} + + +uint16_t fadeGamma(uint32_t channel, uint16_t v) { + if (isChannelGammaCorrected(channel)) { + return ledGamma_internal(v, gamma_table_fast); + } else { + return v; + } +} +uint16_t fadeGammaReverse(uint32_t channel, uint16_t vg) { + if (isChannelGammaCorrected(channel)) { + return ledGammaReverse_internal(vg, gamma_table_fast); + } else { + return vg; + } +} + +bool LightApplyFade(void) { + static uint32_t last_millis = 0; + uint32_t now = millis(); + + if ((now - last_millis) <= 5) { + return false; + } + last_millis = now; + + + if (0 == Light.fade_duration) { + Light.fade_start = now; + + uint32_t distance = 0; + for (uint32_t i = 0; i < Light.subtype; i++) { + int32_t channel_distance = fadeGammaReverse(i, Light.fade_end_10[i]) - fadeGammaReverse(i, Light.fade_start_10[i]); + if (channel_distance < 0) { channel_distance = - channel_distance; } + if (channel_distance > distance) { distance = channel_distance; } + } + if (distance > 0) { + + + + Light.fade_duration = (distance * Settings.light_speed * 500) / 1023; + if (Settings.save_data) { + + uint32_t delay_seconds = 1 + (Light.fade_duration + 999) / 1000; + + if (save_data_counter < delay_seconds) { + save_data_counter = delay_seconds; + } + } + } else { + + Light.fade_running = false; + } + } + + uint16_t fade_current = now - Light.fade_start; + if (fade_current <= Light.fade_duration) { + + for (uint32_t i = 0; i < Light.subtype; i++) { + Light.fade_cur_10[i] = fadeGamma(i, + changeUIntScale(fadeGammaReverse(i, fade_current), + 0, Light.fade_duration, + fadeGammaReverse(i, Light.fade_start_10[i]), + fadeGammaReverse(i, Light.fade_end_10[i]))); + + + + } + } else { + + + Light.fade_running = false; + Light.fade_start = 0; + Light.fade_duration = 0; + + memcpy(Light.fade_cur_10, Light.fade_end_10, sizeof(Light.fade_end_10)); + + memcpy(Light.fade_start_10, Light.fade_end_10, sizeof(Light.fade_start_10)); + } + return true; +} + + + +void LightApplyPower(uint8_t new_color[LST_MAX], power_t power) { + + if (Light.pwm_multi_channels) { + + for (uint32_t i = 0; i < LST_MAX; i++) { + if (0 == bitRead(power,i)) { + new_color[i] = 0; + } + } + + + + + + } else { + if (!light_controller.isCTRGBLinked()) { + + if (0 == (power & 1)) { + new_color[0] = new_color[1] = new_color[2] = 0; + } + if (0 == (power & 2)) { + new_color[3] = new_color[4] = 0; + } + } else if (!power) { + for (uint32_t i = 0; i < LST_MAX; i++) { + new_color[i] = 0; + } + } + } +} + +void LightSetOutputs(const uint16_t *cur_col_10) { + + if (light_type < LT_PWM6) { + for (uint32_t i = 0; i < (Light.subtype - Light.pwm_offset); i++) { + if (pin[GPIO_PWM1 +i] < 99) { + + analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - cur_col_10[(i + Light.pwm_offset)] : cur_col_10[(i + Light.pwm_offset)]); + } + } + } + + + + + uint8_t cur_col[LST_MAX]; + for (uint32_t i = 0; i < LST_MAX; i++) { + cur_col[i] = change10to8(cur_col_10[i]); + } + + + uint8_t scale_col[3]; + uint32_t max = (cur_col[0] > cur_col[1] && cur_col[0] > cur_col[2]) ? cur_col[0] : (cur_col[1] > cur_col[2]) ? cur_col[1] : cur_col[2]; + for (uint32_t i = 0; i < 3; i++) { + scale_col[i] = (0 == max) ? 255 : (255 > max) ? changeUIntScale(cur_col[i], 0, max, 0, 255) : cur_col[i]; + } + + char *tmp_data = XdrvMailbox.data; + char *tmp_topic = XdrvMailbox.topic; + XdrvMailbox.data = (char*)cur_col; + XdrvMailbox.topic = (char*)scale_col; + if (XlgtCall(FUNC_SET_CHANNELS)) { } + else if (XdrvCall(FUNC_SET_CHANNELS)) { } + XdrvMailbox.data = tmp_data; + XdrvMailbox.topic = tmp_topic; +} + + +void calcGammaMultiChannels(uint16_t cur_col_10[5]) { + + if (Settings.light_correction) { + for (uint32_t i = 0; i < LST_MAX; i++) { + cur_col_10[i] = ledGamma10_10(cur_col_10[i]); + } + } +} + +void calcGammaBulbs(uint16_t cur_col_10[5]) { + + + + if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) { + + uint32_t cw1 = Light.subtype - 1; + uint32_t cw0 = Light.subtype - 2; + uint16_t white_bri10 = cur_col_10[cw0] + cur_col_10[cw1]; + uint16_t white_bri10_1023 = (white_bri10 > 1023) ? 1023 : white_bri10; + + if (PHILIPS == my_module_type) { + + cur_col_10[cw1] = light_state.getCT10bits(); + + if (Settings.light_correction) { + cur_col_10[cw0] = ledGamma10_10(white_bri10_1023); + } else { + cur_col_10[cw0] = white_bri10_1023; + } + } else if (Settings.light_correction) { + + if (white_bri10 <= 1031) { + + uint16_t white_bri_gamma10 = ledGamma10_10(white_bri10_1023); + + cur_col_10[cw0] = changeUIntScale(cur_col_10[cw0], 0, white_bri10_1023, 0, white_bri_gamma10); + cur_col_10[cw1] = changeUIntScale(cur_col_10[cw1], 0, white_bri10_1023, 0, white_bri_gamma10); + } else { + cur_col_10[cw0] = ledGamma10_10(cur_col_10[cw0]); + cur_col_10[cw1] = ledGamma10_10(cur_col_10[cw1]); + } + } + } + + if (Settings.light_correction) { + + if (LST_RGB <= Light.subtype) { + for (uint32_t i = 0; i < 3; i++) { + cur_col_10[i] = ledGamma10_10(cur_col_10[i]); + } + } + + if ((LST_SINGLE == Light.subtype) || (LST_RGBW == Light.subtype)) { + cur_col_10[Light.subtype - 1] = ledGamma10_10(cur_col_10[Light.subtype - 1]); + } + } +} + +#ifdef USE_DEVICE_GROUPS +void LightSendDeviceGroupStatus() +{ + uint8_t channels[LST_MAX]; + light_state.getChannels(channels); + SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme, DGR_ITEM_LIGHT_CHANNELS, channels, + DGR_ITEM_LIGHT_BRI, (power ? light_state.getBri() : 0)); +} + +void LightHandleDeviceGroupRequest() +{ + static bool send_state = false; + uint32_t value = XdrvMailbox.payload; + switch (XdrvMailbox.command_code) { + case DGR_ITEM_EOL: + LightAnimate(); + if (send_state && !(XdrvMailbox.index & DGR_FLAG_MORE_TO_COME)) { + light_controller.saveSettings(); + if (Settings.flag3.hass_tele_on_power) { + MqttPublishTeleState(); + } + send_state = false; + } + break; + case DGR_ITEM_LIGHT_BRI: + if (light_state.getBri() != value) { + light_controller.changeBri(value); + send_state = true; + } + break; + case DGR_ITEM_LIGHT_SCHEME: + if (Settings.light_scheme != value) { + Settings.light_scheme = value; + send_state = true; + } + break; + case DGR_ITEM_LIGHT_CHANNELS: + light_controller.changeChannels((uint8_t *)XdrvMailbox.data); + send_state = true; + break; + case DGR_ITEM_LIGHT_FIXED_COLOR: + { + power_t save_power = Light.power; + if (value) { + bool save_decimal_text = Settings.flag.decimal_text; + char str[16]; + XdrvMailbox.index = 2; + XdrvMailbox.data_len = sprintf_P(str, PSTR("%u"), value); + XdrvMailbox.data = str; + CmndSupportColor(); + Settings.flag.decimal_text = save_decimal_text; + } + else { + Light.fixed_color_index = 0; + XdrvMailbox.index = 1; + XdrvMailbox.payload = light_state.BriToDimmer(light_state.getBri()); + CmndWhite(); + } + if (Light.power != save_power) { + XdrvMailbox.index = save_power; + LightSetPower(); + } + send_state = true; + } + break; + case DGR_ITEM_LIGHT_SPEED: + if (Settings.light_speed != value && value > 0 && value <= 40) { + Settings.light_speed = value; + send_state = true; + } + break; + case DGR_ITEM_STATUS: + SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade, + DGR_ITEM_LIGHT_SPEED, Settings.light_speed); + LightSendDeviceGroupStatus(); + break; + } +} +#endif + + + + + +bool LightColorEntry(char *buffer, uint32_t buffer_length) +{ + char scolor[10]; + char *p; + char *str; + uint32_t entry_type = 0; + uint8_t value = Light.fixed_color_index; + + if (buffer[0] == '#') { + buffer++; + buffer_length--; + } + + if (Light.subtype >= LST_RGB) { + char option = (1 == buffer_length) ? buffer[0] : '\0'; + if (('+' == option) && (Light.fixed_color_index < MAX_FIXED_COLOR)) { + value++; + } + else if (('-' == option) && (Light.fixed_color_index > 1)) { + value--; + } else { + value = atoi(buffer); + } + } + + memset(&Light.entry_color, 0x00, sizeof(Light.entry_color)); + + while ((buffer_length > 0) && ('=' == buffer[buffer_length - 1])) { + buffer_length--; + memcpy(&Light.entry_color, &Light.current_color, sizeof(Light.entry_color)); + } + if (strstr(buffer, ",") != nullptr) { + int8_t i = 0; + for (str = strtok_r(buffer, ",", &p); str && i < 6; str = strtok_r(nullptr, ",", &p)) { + if (i < LST_MAX) { + Light.entry_color[i++] = atoi(str); + } + } + entry_type = 2; + } + else if (((2 * Light.subtype) == buffer_length) || (buffer_length > 3)) { + for (uint32_t i = 0; i < tmin((uint)(buffer_length / 2), sizeof(Light.entry_color)); i++) { + strlcpy(scolor, buffer + (i *2), 3); + Light.entry_color[i] = (uint8_t)strtol(scolor, &p, 16); + } + entry_type = 1; + } + else if ((Light.subtype >= LST_RGB) && (value > 0) && (value <= MAX_FIXED_COLOR)) { + Light.fixed_color_index = value; + memcpy_P(&Light.entry_color, &kFixedColor[value -1], 3); + entry_type = 1; + } + else if ((value > 199) && (value <= 199 + MAX_FIXED_COLD_WARM)) { + if (LST_RGBW == Light.subtype) { + memcpy_P(&Light.entry_color[3], &kFixedWhite[value -200], 1); + entry_type = 1; + } + else if (LST_COLDWARM == Light.subtype) { + memcpy_P(&Light.entry_color, &kFixedColdWarm[value -200], 2); + entry_type = 1; + } + else if (LST_RGBCW == Light.subtype) { + memcpy_P(&Light.entry_color[3], &kFixedColdWarm[value -200], 2); + entry_type = 1; + } + } + if (entry_type) { + Settings.flag.decimal_text = entry_type -1; + } + return (entry_type); +} + + + +void CmndSupportColor(void) +{ + bool valid_entry = false; + bool coldim = false; + + if (XdrvMailbox.data_len > 0) { + valid_entry = LightColorEntry(XdrvMailbox.data, XdrvMailbox.data_len); + if (valid_entry) { + if (XdrvMailbox.index <= 2) { + uint32_t old_bri = light_state.getBri(); + + light_controller.changeChannels(Light.entry_color); + if (2 == XdrvMailbox.index) { + + light_controller.changeBri(old_bri); + } + + Settings.light_scheme = 0; + coldim = true; + } else { + for (uint32_t i = 0; i < LST_RGB; i++) { + Settings.ws_color[XdrvMailbox.index -3][i] = Light.entry_color[i]; + } + } + } + } + char scolor[LIGHT_COLOR_SIZE]; + if (!valid_entry && (XdrvMailbox.index <= 2)) { + ResponseCmndChar(LightGetColor(scolor)); + } + if (XdrvMailbox.index >= 3) { + scolor[0] = '\0'; + for (uint32_t i = 0; i < LST_RGB; i++) { + if (Settings.flag.decimal_text) { + snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.ws_color[XdrvMailbox.index -3][i]); + } else { + snprintf_P(scolor, sizeof(scolor), PSTR("%s%02X"), scolor, Settings.ws_color[XdrvMailbox.index -3][i]); + } + } + ResponseCmndIdxChar(scolor); + } + if (coldim) { + LightPreparePower(); + } +} + +void CmndColor(void) +{ + + + + + + + + if ((Light.subtype > LST_SINGLE) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) { + CmndSupportColor(); + } +} + +void CmndWhite(void) +{ + + + if (Light.pwm_multi_channels) { return; } + if ( ((Light.subtype >= LST_RGBW) || (LST_COLDWARM == Light.subtype)) && (XdrvMailbox.index == 1)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { + light_controller.changeDimmer(XdrvMailbox.payload, 2); + LightPreparePower(2); + } else { + ResponseCmndNumber(light_state.getDimmer(2)); + } + } +} + +void CmndChannel(void) +{ + + + + + if ((XdrvMailbox.index >= Light.device) && (XdrvMailbox.index < Light.device + Light.subtype )) { + uint32_t light_index = XdrvMailbox.index - Light.device; + power_t coldim = 0; + + + if (1 == XdrvMailbox.data_len) { + uint8_t channel = changeUIntScale(Light.current_color[light_index],0,255,0,100); + if ('+' == XdrvMailbox.data[0]) { + XdrvMailbox.payload = (channel > 89) ? 100 : channel + 10; + } else if ('-' == XdrvMailbox.data[0]) { + XdrvMailbox.payload = (channel < 11) ? 1 : channel - 10; + } + } + + + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { + Light.current_color[light_index] = changeUIntScale(XdrvMailbox.payload,0,100,0,255); + if (Light.pwm_multi_channels) { + coldim = 1 << light_index; + } else { + if (light_controller.isCTRGBLinked()) { + + if ((light_index < 3) && (light_controller.isCTRGBLinked())) { + Light.current_color[3] = Light.current_color[4] = 0; + } else { + Light.current_color[0] = Light.current_color[1] = Light.current_color[2] = 0; + } + coldim = 1; + } else { + if (light_index < 3) { coldim = 1; } + else { coldim = 2; } + } + } + light_controller.changeChannels(Light.current_color); + } + ResponseCmndIdxNumber(changeUIntScale(Light.current_color[light_index],0,255,0,100)); + if (coldim) { + LightPreparePower(coldim); + } + } +} + +void CmndHsbColor(void) +{ + + + + + + + + if (Light.subtype >= LST_RGB) { + if (XdrvMailbox.data_len > 0) { + uint16_t c_hue; + uint8_t c_sat; + light_state.getHSB(&c_hue, &c_sat, nullptr); + uint32_t HSB[3]; + HSB[0] = c_hue; + HSB[1] = c_sat; + HSB[2] = light_state.getBriRGB(); + if ((2 == XdrvMailbox.index) || (3 == XdrvMailbox.index)) { + if ((uint32_t)XdrvMailbox.payload > 100) { XdrvMailbox.payload = 100; } + HSB[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 255); + } else { + uint32_t paramcount = ParseParameters(3, HSB); + if (HSB[0] > 360) { HSB[0] = 360; } + for (uint32_t i = 1; i < paramcount; i++) { + if (HSB[i] > 100) { HSB[i] == 100; } + HSB[i] = changeUIntScale(HSB[i], 0, 100, 0, 255); + } + } + light_controller.changeHSB(HSB[0], HSB[1], HSB[2]); + LightPreparePower(1); + } else { + LightState(0); + } + } +} + +void CmndScheme(void) +{ + + + + + + if (Light.subtype >= LST_RGB) { + uint32_t max_scheme = Light.max_scheme; + + if (1 == XdrvMailbox.data_len) { + if (('+' == XdrvMailbox.data[0]) && (Settings.light_scheme < max_scheme)) { + XdrvMailbox.payload = Settings.light_scheme + ((0 == Settings.light_scheme) ? 2 : 1); + } + else if (('-' == XdrvMailbox.data[0]) && (Settings.light_scheme > 0)) { + XdrvMailbox.payload = Settings.light_scheme - ((2 == Settings.light_scheme) ? 2 : 1); + } + } + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= max_scheme)) { + uint32_t parm[2]; + if (ParseParameters(2, parm) > 1) { + Light.wheel = parm[1]; + } + Settings.light_scheme = XdrvMailbox.payload; +#ifdef USE_DEVICE_GROUPS + SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme); +#endif + if (LS_WAKEUP == Settings.light_scheme) { + Light.wakeup_active = 3; + } + LightPowerOn(); + Light.strip_timer_counter = 0; + + if (Settings.flag3.hass_tele_on_power) { + MqttPublishTeleState(); + } + } + ResponseCmndNumber(Settings.light_scheme); + } +} + +void CmndWakeup(void) +{ + + + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { + light_controller.changeDimmer(XdrvMailbox.payload); + } + Light.wakeup_active = 3; + Settings.light_scheme = LS_WAKEUP; + LightPowerOn(); + ResponseCmndChar(D_JSON_STARTED); +} + +void CmndColorTemperature(void) +{ + + + + + if (Light.pwm_multi_channels) { return; } + if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) { + uint32_t ct = light_state.getCT(); + if (1 == XdrvMailbox.data_len) { + if ('+' == XdrvMailbox.data[0]) { + XdrvMailbox.payload = (ct > (CT_MAX-34)) ? CT_MAX : ct + 34; + } + else if ('-' == XdrvMailbox.data[0]) { + XdrvMailbox.payload = (ct < (CT_MIN+34)) ? CT_MIN : ct - 34; + } + } + if ((XdrvMailbox.payload >= CT_MIN) && (XdrvMailbox.payload <= CT_MAX)) { + light_controller.changeCTB(XdrvMailbox.payload, light_state.getBriCT()); + LightPreparePower(2); + } else { + ResponseCmndNumber(ct); + } + } +} + +void CmndDimmer(void) +{ + + + + + + + uint32_t dimmer; + if (XdrvMailbox.index > 2) { XdrvMailbox.index = 1; } + + if ((light_controller.isCTRGBLinked()) || (0 == XdrvMailbox.index)) { + dimmer = light_state.getDimmer(); + } else { + dimmer = light_state.getDimmer(XdrvMailbox.index); + } + + 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)) { + if (light_controller.isCTRGBLinked()) { + + light_controller.changeDimmer(XdrvMailbox.payload); + LightPreparePower(); + } else { + if (0 != XdrvMailbox.index) { + light_controller.changeDimmer(XdrvMailbox.payload, XdrvMailbox.index); + LightPreparePower(1 << (XdrvMailbox.index - 1)); + } else { + + light_controller.changeDimmer(XdrvMailbox.payload, 1); + light_controller.changeDimmer(XdrvMailbox.payload, 2); + LightPreparePower(); + } + } + Light.update = true; + } else { + ResponseCmndNumber(dimmer); + } +} + +void CmndDimmerRange(void) +{ + + + if (XdrvMailbox.data_len > 0) { + uint32_t parm[2]; + parm[0] = Settings.dimmer_hw_min; + parm[1] = Settings.dimmer_hw_max; + ParseParameters(2, parm); + if (parm[0] < parm[1]) { + Settings.dimmer_hw_min = parm[0]; + Settings.dimmer_hw_max = parm[1]; + } else { + Settings.dimmer_hw_min = parm[1]; + Settings.dimmer_hw_max = parm[0]; + } + restart_flag = 2; + } + Response_P(PSTR("{\"" D_CMND_DIMMER_RANGE "\":{\"Min\":%d,\"Max\":%d}}"), Settings.dimmer_hw_min, Settings.dimmer_hw_max); +} + +void CmndLedTable(void) +{ + + + + + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { + switch (XdrvMailbox.payload) { + case 0: + case 1: + Settings.light_correction = XdrvMailbox.payload; + break; + case 2: + Settings.light_correction ^= 1; + break; + } + Light.update = true; + } + ResponseCmndStateText(Settings.light_correction); +} + +void CmndRgbwwTable(void) +{ + + + if ((XdrvMailbox.data_len > 0)) { + uint32_t parm[LST_RGBCW -1]; + uint32_t parmcount = ParseParameters(LST_RGBCW, parm); + for (uint32_t i = 0; i < parmcount; i++) { + Settings.rgbwwTable[i] = parm[i]; + } + Light.update = true; + } + char scolor[LIGHT_COLOR_SIZE]; + scolor[0] = '\0'; + for (uint32_t i = 0; i < LST_RGBCW; i++) { + snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]); + } + ResponseCmndChar(scolor); +} + +void CmndFade(void) +{ + + + + + switch (XdrvMailbox.payload) { + case 0: + case 1: + Settings.light_fade = XdrvMailbox.payload; + break; + case 2: + Settings.light_fade ^= 1; + break; + } +#ifdef USE_DEVICE_GROUPS + if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 2) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade); +#endif + if (!Settings.light_fade) { Light.fade_running = false; } + ResponseCmndStateText(Settings.light_fade); +} + +void CmndSpeed(void) +{ + + + + + if (1 == XdrvMailbox.data_len) { + if (('+' == XdrvMailbox.data[0]) && (Settings.light_speed > 1)) { + XdrvMailbox.payload = Settings.light_speed - 1; + } + else if (('-' == XdrvMailbox.data[0]) && (Settings.light_speed < 40)) { + XdrvMailbox.payload = Settings.light_speed + 1; + } + } + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 40)) { + Settings.light_speed = XdrvMailbox.payload; +#ifdef USE_DEVICE_GROUPS + SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SPEED, Settings.light_speed); +#endif + } + ResponseCmndNumber(Settings.light_speed); +} + +void CmndWakeupDuration(void) +{ + + + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3001)) { + Settings.light_wakeup = XdrvMailbox.payload; + Light.wakeup_active = 0; + } + ResponseCmndNumber(Settings.light_wakeup); +} + +void CmndUndocA(void) +{ + + char scolor[LIGHT_COLOR_SIZE]; + LightGetColor(scolor, true); + scolor[6] = '\0'; + Response_P(PSTR("%s,%d,%d,%d,%d,%d"), scolor, Settings.light_fade, Settings.light_correction, Settings.light_scheme, Settings.light_speed, Settings.light_width); + MqttPublishPrefixTopic_P(STAT, XdrvMailbox.topic); + mqtt_data[0] = '\0'; +} + + + + + +bool Xdrv04(uint8_t function) +{ + bool result = false; + + if (FUNC_MODULE_INIT == function) { + return LightModuleInit(); + } + else if (light_type) { + switch (function) { + case FUNC_SERIAL: + result = XlgtCall(FUNC_SERIAL); + break; + case FUNC_LOOP: + if (Light.fade_running) { + if (LightApplyFade()) { + LightSetOutputs(Light.fade_cur_10); + } + } + break; + case FUNC_EVERY_50_MSECOND: + LightAnimate(); + break; +#ifdef USE_DEVICE_GROUPS + case FUNC_DEVICE_GROUP_REQUEST: + LightHandleDeviceGroupRequest(); + break; +#endif + case FUNC_SET_POWER: + LightSetPower(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kLightCommands, LightCommand); + if (!result) { + result = XlgtCall(FUNC_COMMAND); + } + break; + case FUNC_PRE_INIT: + LightInit(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_05_irremote.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_05_irremote.ino" +#if defined(USE_IR_REMOTE) && !defined(USE_IR_REMOTE_FULL) + + + + +#define XDRV_05 5 + +#include + +enum IrErrors { IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND }; + +const char kIrRemoteCommands[] PROGMEM = "|" D_CMND_IRSEND ; + + +void (* const IrRemoteCommand[])(void) PROGMEM = { + &CmndIrSend }; + + +static const uint8_t MAX_STANDARD_IR = NEC; +const char kIrRemoteProtocols[] PROGMEM = "UNKNOWN|RC5|RC6|NEC"; + + + + + +#include + +IRsend *irsend = nullptr; +bool irsend_active = false; + +void IrSendInit(void) +{ + irsend = new IRsend(pin[GPIO_IRSEND]); + irsend->begin(); +} + +#ifdef USE_IR_RECEIVE + + + + +const bool IR_RCV_SAVE_BUFFER = false; +const uint32_t IR_TIME_AVOID_DUPLICATE = 500; + +#include + +IRrecv *irrecv = nullptr; + +unsigned long ir_lasttime = 0; + +void IrReceiveUpdateThreshold(void) +{ + if (irrecv != nullptr) { + if (Settings.param[P_IR_UNKNOW_THRESHOLD] < 6) { Settings.param[P_IR_UNKNOW_THRESHOLD] = 6; } + irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]); + } +} + +void IrReceiveInit(void) +{ + + irrecv = new IRrecv(pin[GPIO_IRRECV], IR_RCV_BUFFER_SIZE, IR_RCV_TIMEOUT, IR_RCV_SAVE_BUFFER); + irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]); + irrecv->enableIRIn(); + + +} + +void IrReceiveCheck(void) +{ + char sirtype[8]; + int8_t iridx = 0; + + decode_results results; + + if (irrecv->decode(&results)) { + char hvalue[65]; + + iridx = results.decode_type; + if ((iridx < 0) || (iridx > MAX_STANDARD_IR)) { iridx = 0; } + + if (iridx) { + if (results.bits > 64) { + + uint32_t digits2 = results.bits / 8; + if (results.bits % 8) { digits2++; } + ToHex_P((unsigned char*)results.state, digits2, hvalue, sizeof(hvalue)); + } else { + Uint64toHex(results.value, hvalue, results.bits); + } + } else { + Uint64toHex(results.value, hvalue, 32); + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_IRR "Echo %d, RawLen %d, Overflow %d, Bits %d, Value 0x%s, Decode %d"), + irsend_active, results.rawlen, results.overflow, results.bits, hvalue, results.decode_type); + + unsigned long now = millis(); + + if (!irsend_active && (now - ir_lasttime > IR_TIME_AVOID_DUPLICATE)) { + ir_lasttime = now; + + char svalue[64]; + if (Settings.flag.ir_receive_decimal) { + ulltoa(results.value, svalue, 10); + } else { + snprintf_P(svalue, sizeof(svalue), PSTR("\"0x%s\""), hvalue); + } + ResponseTime_P(PSTR(",\"" D_JSON_IRRECEIVED "\":{\"" D_JSON_IR_PROTOCOL "\":\"%s\",\"" D_JSON_IR_BITS "\":%d"), + GetTextIndexed(sirtype, sizeof(sirtype), iridx, kIrRemoteProtocols), results.bits); + if (iridx) { + ResponseAppend_P(PSTR(",\"" D_JSON_IR_DATA "\":%s"), svalue); + } else { + ResponseAppend_P(PSTR(",\"" D_JSON_IR_HASH "\":%s"), svalue); + } + + if (Settings.flag3.receive_raw) { + ResponseAppend_P(PSTR(",\"" D_JSON_IR_RAWDATA "\":[")); + uint16_t i; + for (i = 1; i < results.rawlen; i++) { + if (i > 1) { ResponseAppend_P(PSTR(",")); } + uint32_t usecs; + for (usecs = results.rawbuf[i] * kRawTick; usecs > UINT16_MAX; usecs -= UINT16_MAX) { + ResponseAppend_P(PSTR("%d,0,"), UINT16_MAX); + } + ResponseAppend_P(PSTR("%d"), usecs); + if (strlen(mqtt_data) > sizeof(mqtt_data) - 40) { break; } + } + uint16_t extended_length = results.rawlen - 1; + for (uint32_t j = 0; j < results.rawlen - 1; j++) { + uint32_t usecs = results.rawbuf[j] * kRawTick; + + extended_length += (usecs / (UINT16_MAX + 1)) * 2; + } + ResponseAppend_P(PSTR("],\"" D_JSON_IR_RAWDATA "Info\":[%d,%d,%d]"), extended_length, i -1, results.overflow); + } + + ResponseJsonEndEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_IRRECEIVED)); + + XdrvRulesProcess(); +#ifdef USE_DOMOTICZ + if (iridx) { + unsigned long value = results.value | (iridx << 28); + DomoticzSensor(DZ_COUNT, value); + } +#endif + } + + irrecv->resume(); + } +} +#endif + + + + + +uint32_t IrRemoteCmndIrSendJson(void) +{ + + + + + char dataBufUc[XdrvMailbox.data_len + 1]; + UpperCase(dataBufUc, XdrvMailbox.data); + RemoveSpace(dataBufUc); + if (strlen(dataBufUc) < 8) { + return IE_INVALID_JSON; + } + + StaticJsonBuffer<140> jsonBuf; + JsonObject &root = jsonBuf.parseObject(dataBufUc); + if (!root.success()) { + return IE_INVALID_JSON; + } + + + + char parm_uc[10]; + const char *protocol = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_PROTOCOL))]; + uint16_t bits = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_BITS))]; + uint64_t data = strtoull(root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATA))], nullptr, 0); + uint16_t repeat = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_REPEAT))]; + + if (XdrvMailbox.index > repeat + 1) { + repeat = XdrvMailbox.index - 1; + } + if (!(protocol && bits)) { + return IE_SYNTAX_IRSEND; + } + + char protocol_text[20]; + int protocol_code = GetCommandCode(protocol_text, sizeof(protocol_text), protocol, kIrRemoteProtocols); + + char dvalue[64]; + char hvalue[20]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol_text %s, protocol %s, bits %d, data %s (0x%s), repeat %d, protocol_code %d"), + protocol_text, protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat, protocol_code); + + irsend_active = true; + switch (protocol_code) { +#ifdef USE_IR_SEND_RC5 + case RC5: + irsend->sendRC5(data, bits, repeat); break; +#endif +#ifdef USE_IR_SEND_RC6 + case RC6: + irsend->sendRC6(data, bits, repeat); break; +#endif +#ifdef USE_IR_SEND_NEC + case NEC: + irsend->sendNEC(data, (bits > NEC_BITS) ? NEC_BITS : bits, repeat); break; +#endif + default: + irsend_active = false; + ResponseCmndChar(D_JSON_PROTOCOL_NOT_SUPPORTED); + } + + return IE_NO_ERROR; +} + +void CmndIrSend(void) +{ + uint8_t error = IE_SYNTAX_IRSEND; + + if (XdrvMailbox.data_len) { + + if (strstr(XdrvMailbox.data, "{") == nullptr) { + error = IE_INVALID_JSON; + } else { + error = IrRemoteCmndIrSendJson(); + } + } + IrRemoteCmndResponse(error); +} + +void IrRemoteCmndResponse(uint32_t error) +{ + switch (error) { + case IE_INVALID_RAWDATA: + ResponseCmndChar(D_JSON_INVALID_RAWDATA); + break; + case IE_INVALID_JSON: + ResponseCmndChar(D_JSON_INVALID_JSON); + break; + case IE_SYNTAX_IRSEND: + Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_NO " " D_JSON_IR_PROTOCOL ", " D_JSON_IR_BITS " " D_JSON_OR " " D_JSON_IR_DATA "\"}")); + break; + default: + ResponseCmndDone(); + } +} + + + + + +bool Xdrv05(uint8_t function) +{ + bool result = false; + + if ((pin[GPIO_IRSEND] < 99) || (pin[GPIO_IRRECV] < 99)) { + switch (function) { + case FUNC_PRE_INIT: + if (pin[GPIO_IRSEND] < 99) { + IrSendInit(); + } +#ifdef USE_IR_RECEIVE + if (pin[GPIO_IRRECV] < 99) { + IrReceiveInit(); + } +#endif + break; + case FUNC_EVERY_50_MSECOND: +#ifdef USE_IR_RECEIVE + if (pin[GPIO_IRRECV] < 99) { + IrReceiveCheck(); + } +#endif + irsend_active = false; + break; + case FUNC_COMMAND: + if (pin[GPIO_IRSEND] < 99) { + result = DecodeCommand(kIrRemoteCommands, IrRemoteCommand); + } + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_05_irremote_full.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_05_irremote_full.ino" +#ifdef USE_IR_REMOTE_FULL + + + + +#define XDRV_05 5 + +#include +#include +#include +#include +#include + +enum IrErrors { IE_RESPONSE_PROVIDED, IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND, IE_SYNTAX_IRHVAC, + IE_UNSUPPORTED_HVAC, IE_UNSUPPORTED_PROTOCOL }; + +const char kIrRemoteCommands[] PROGMEM = "|" + D_CMND_IRHVAC "|" D_CMND_IRSEND ; + +void (* const IrRemoteCommand[])(void) PROGMEM = { + &CmndIrHvac, &CmndIrSend }; + + + + + +IRsend *irsend = nullptr; +bool irsend_active = false; + +void IrSendInit(void) +{ + irsend = new IRsend(pin[GPIO_IRSEND]); + irsend->begin(); +} + + + +uint8_t reverseBitsInByte(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} + + +uint64_t reverseBitsInBytes64(uint64_t b) { + union { + uint8_t b[8]; + uint64_t i; + } a; + a.i = b; + for (uint32_t i=0; i<8; i++) { + a.b[i] = reverseBitsInByte(a.b[i]); + } + return a.i; +} + + + + + +const bool IR_FULL_RCV_SAVE_BUFFER = false; +const uint32_t IR_TIME_AVOID_DUPLICATE = 500; + + + + +const uint16_t IR_FULL_BUFFER_SIZE = 1024; + + + +const uint8_t IR__FULL_RCV_TIMEOUT = 50; + +IRrecv *irrecv = nullptr; + +unsigned long ir_lasttime = 0; + +void IrReceiveUpdateThreshold(void) +{ + if (irrecv != nullptr) { + if (Settings.param[P_IR_UNKNOW_THRESHOLD] < 6) { Settings.param[P_IR_UNKNOW_THRESHOLD] = 6; } + irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]); + } +} + +void IrReceiveInit(void) +{ + + irrecv = new IRrecv(pin[GPIO_IRRECV], IR_FULL_BUFFER_SIZE, IR__FULL_RCV_TIMEOUT, IR_FULL_RCV_SAVE_BUFFER); + irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]); + irrecv->enableIRIn(); +} + +String sendACJsonState(const stdAc::state_t &state) { + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json[D_JSON_IRHVAC_VENDOR] = typeToString(state.protocol); + json[D_JSON_IRHVAC_MODEL] = state.model; + json[D_JSON_IRHVAC_POWER] = IRac::boolToString(state.power); + json[D_JSON_IRHVAC_MODE] = IRac::opmodeToString(state.mode); + + if (state.mode == stdAc::opmode_t::kOff || !state.power) { + json[D_JSON_IRHVAC_MODE] = IRac::opmodeToString(stdAc::opmode_t::kOff); + json[D_JSON_IRHVAC_POWER] = IRac::boolToString(false); + } + json[D_JSON_IRHVAC_CELSIUS] = IRac::boolToString(state.celsius); + if (floorf(state.degrees) == state.degrees) { + json[D_JSON_IRHVAC_TEMP] = floorf(state.degrees); + } else { + json[D_JSON_IRHVAC_TEMP] = RawJson(String(state.degrees, 1)); + } + json[D_JSON_IRHVAC_FANSPEED] = IRac::fanspeedToString(state.fanspeed); + json[D_JSON_IRHVAC_SWINGV] = IRac::swingvToString(state.swingv); + json[D_JSON_IRHVAC_SWINGH] = IRac::swinghToString(state.swingh); + json[D_JSON_IRHVAC_QUIET] = IRac::boolToString(state.quiet); + json[D_JSON_IRHVAC_TURBO] = IRac::boolToString(state.turbo); + json[D_JSON_IRHVAC_ECONO] = IRac::boolToString(state.econo); + json[D_JSON_IRHVAC_LIGHT] = IRac::boolToString(state.light); + json[D_JSON_IRHVAC_FILTER] = IRac::boolToString(state.filter); + json[D_JSON_IRHVAC_CLEAN] = IRac::boolToString(state.clean); + json[D_JSON_IRHVAC_BEEP] = IRac::boolToString(state.beep); + json[D_JSON_IRHVAC_SLEEP] = state.sleep; + + String payload = ""; + payload.reserve(200); + json.printTo(payload); + return payload; +} + +String sendIRJsonState(const struct decode_results &results) { + String json("{"); + json += "\"" D_JSON_IR_PROTOCOL "\":\""; + json += typeToString(results.decode_type); + json += "\",\"" D_JSON_IR_BITS "\":"; + json += results.bits; + + if (hasACState(results.decode_type)) { + json += ",\"" D_JSON_IR_DATA "\":\"0x"; + json += resultToHexidecimal(&results); + json += "\""; + } else { + if (UNKNOWN != results.decode_type) { + json += ",\"" D_JSON_IR_DATA "\":"; + } else { + json += ",\"" D_JSON_IR_HASH "\":"; + } + if (Settings.flag.ir_receive_decimal) { + char svalue[32]; + ulltoa(results.value, svalue, 10); + json += svalue; + } else { + char hvalue[64]; + if (UNKNOWN != results.decode_type) { + Uint64toHex(results.value, hvalue, results.bits); + json += "\"0x"; + json += hvalue; + json += "\",\"" D_JSON_IR_DATALSB "\":\"0x"; + Uint64toHex(reverseBitsInBytes64(results.value), hvalue, results.bits); + json += hvalue; + json += "\""; + } else { + Uint64toHex(results.value, hvalue, 32); + json += "\"0x"; + json += hvalue; + json += "\""; + } + } + } + json += ",\"" D_JSON_IR_REPEAT "\":"; + json += results.repeat; + + stdAc::state_t ac_result; + if (IRAcUtils::decodeToState(&results, &ac_result, nullptr)) { + + json += ",\"" D_CMND_IRHVAC "\":"; + json += sendACJsonState(ac_result); + } + + return json; +} + +void IrReceiveCheck(void) +{ + decode_results results; + + if (irrecv->decode(&results)) { + uint32_t now = millis(); + + + if (!irsend_active && (now - ir_lasttime > IR_TIME_AVOID_DUPLICATE)) { + ir_lasttime = now; + Response_P(PSTR("{\"" D_JSON_IRRECEIVED "\":%s"), sendIRJsonState(results).c_str()); + + if (Settings.flag3.receive_raw) { + ResponseAppend_P(PSTR(",\"" D_JSON_IR_RAWDATA "\":[")); + uint16_t i; + for (i = 1; i < results.rawlen; i++) { + if (i > 1) { ResponseAppend_P(PSTR(",")); } + uint32_t usecs; + for (usecs = results.rawbuf[i] * kRawTick; usecs > UINT16_MAX; usecs -= UINT16_MAX) { + ResponseAppend_P(PSTR("%d,0,"), UINT16_MAX); + } + ResponseAppend_P(PSTR("%d"), usecs); + if (strlen(mqtt_data) > sizeof(mqtt_data) - 40) { break; } + } + uint16_t extended_length = results.rawlen - 1; + for (uint32_t j = 0; j < results.rawlen - 1; j++) { + uint32_t usecs = results.rawbuf[j] * kRawTick; + + extended_length += (usecs / (UINT16_MAX + 1)) * 2; + } + ResponseAppend_P(PSTR("],\"" D_JSON_IR_RAWDATA "Info\":[%d,%d,%d]"), extended_length, i -1, results.overflow); + } + + ResponseJsonEndEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_IRRECEIVED)); + + XdrvRulesProcess(); + } + + irrecv->resume(); + } +} + + + + + + + +String listSupportedProtocols(bool hvac) { + String l(""); + bool first = true; + for (uint32_t i = UNUSED + 1; i <= kLastDecodeType; i++) { + bool found = false; + if (hvac) { + found = IRac::isProtocolSupported((decode_type_t)i); + } else { + found = (IRsend::defaultBits((decode_type_t)i) > 0) && (!IRac::isProtocolSupported((decode_type_t)i)); + } + if (found) { + if (first) { + first = false; + } else { + l += "|"; + } + l += typeToString((decode_type_t)i); + } + } + return l; +} + + +const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto, + stdAc::fanspeed_t::kMin, stdAc::fanspeed_t::kLow,stdAc::fanspeed_t::kMedium, + stdAc::fanspeed_t::kHigh, stdAc::fanspeed_t::kMax }; + +uint32_t IrRemoteCmndIrHvacJson(void) +{ + stdAc::state_t state, prev; + char parm_uc[12]; + + + char dataBufUc[XdrvMailbox.data_len + 1]; + UpperCase(dataBufUc, XdrvMailbox.data); + RemoveSpace(dataBufUc); + if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; } + + DynamicJsonBuffer jsonBuf; + JsonObject &json = jsonBuf.parseObject(dataBufUc); + if (!json.success()) { return IE_INVALID_JSON; } + + + state.protocol = decode_type_t::UNKNOWN; + state.model = 1; + state.mode = stdAc::opmode_t::kAuto; + state.power = false; + state.celsius = true; + state.degrees = 21.0f; + state.fanspeed = stdAc::fanspeed_t::kMedium; + state.swingv = stdAc::swingv_t::kOff; + state.swingh = stdAc::swingh_t::kOff; + state.light = false; + state.beep = false; + state.econo = false; + state.filter = false; + state.turbo = false; + state.quiet = false; + state.sleep = -1; + state.clean = false; + state.clock = -1; + + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR)); + if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL)); + if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } + if (decode_type_t::UNKNOWN == state.protocol) { return IE_UNSUPPORTED_HVAC; } + if (!IRac::isProtocolSupported(state.protocol)) { return IE_UNSUPPORTED_HVAC; } + + + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FANSPEED)); + if (json.containsKey(parm_uc)) { + uint32_t fan_speed = json[parm_uc]; + if ((fan_speed >= 1) && (fan_speed <= 5)) { + state.fanspeed = (stdAc::fanspeed_t) pgm_read_byte(&IrHvacFanSpeed[fan_speed]); + } else { + state.fanspeed = IRac::strToFanspeed(json[parm_uc]); + } + } + + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODEL)); + if (json.containsKey(parm_uc)) { state.model = IRac::strToModel(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODE)); + if (json.containsKey(parm_uc)) { state.mode = IRac::strToOpmode(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGV)); + if (json.containsKey(parm_uc)) { state.swingv = IRac::strToSwingV(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGH)); + if (json.containsKey(parm_uc)) { state.swingh = IRac::strToSwingH(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TEMP)); + if (json.containsKey(parm_uc)) { state.degrees = json[parm_uc]; } + + + + + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_POWER)); + if (json.containsKey(parm_uc)) { state.power = IRac::strToBool(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CELSIUS)); + if (json.containsKey(parm_uc)) { state.celsius = IRac::strToBool(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_LIGHT)); + if (json.containsKey(parm_uc)) { state.light = IRac::strToBool(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_BEEP)); + if (json.containsKey(parm_uc)) { state.beep = IRac::strToBool(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_ECONO)); + if (json.containsKey(parm_uc)) { state.econo = IRac::strToBool(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FILTER)); + if (json.containsKey(parm_uc)) { state.filter = IRac::strToBool(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TURBO)); + if (json.containsKey(parm_uc)) { state.turbo = IRac::strToBool(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_QUIET)); + if (json.containsKey(parm_uc)) { state.quiet = IRac::strToBool(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CLEAN)); + if (json.containsKey(parm_uc)) { state.clean = IRac::strToBool(json[parm_uc]); } + + + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SLEEP)); + if (json[parm_uc]) { state.sleep = json[parm_uc]; } + + + IRac ac(pin[GPIO_IRSEND]); + bool success = ac.sendAc(state, &prev); + if (!success) { return IE_SYNTAX_IRHVAC; } + + Response_P(PSTR("{\"" D_CMND_IRHVAC "\":%s}"), sendACJsonState(state).c_str()); + return IE_RESPONSE_PROVIDED; +} + +void CmndIrHvac(void) +{ + uint8_t error = IE_SYNTAX_IRHVAC; + + if (XdrvMailbox.data_len) { + error = IrRemoteCmndIrHvacJson(); + } + if (error != IE_RESPONSE_PROVIDED) { IrRemoteCmndResponse(error); } +} + + + + + +uint32_t IrRemoteCmndIrSendJson(void) +{ + char parm_uc[12]; + + + + char dataBufUc[XdrvMailbox.data_len + 1]; + UpperCase(dataBufUc, XdrvMailbox.data); + RemoveSpace(dataBufUc); + if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; } + + DynamicJsonBuffer jsonBuf; + JsonObject &json = jsonBuf.parseObject(dataBufUc); + if (!json.success()) { return IE_INVALID_JSON; } + + + + decode_type_t protocol = decode_type_t::UNKNOWN; + uint16_t bits = 0; + uint64_t data; + uint8_t repeat = 0; + + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR)); + if (json.containsKey(parm_uc)) { protocol = strToDecodeType(json[parm_uc]); } + UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL)); + if (json.containsKey(parm_uc)) { protocol = strToDecodeType(json[parm_uc]); } + if (decode_type_t::UNKNOWN == protocol) { return IE_UNSUPPORTED_PROTOCOL; } + + UpperCase_P(parm_uc, PSTR(D_JSON_IR_BITS)); + if (json.containsKey(parm_uc)) { bits = json[parm_uc]; } + UpperCase_P(parm_uc, PSTR(D_JSON_IR_REPEAT)); + if (json.containsKey(parm_uc)) { repeat = json[parm_uc]; } + UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATALSB)); + if (json.containsKey(parm_uc)) { data = reverseBitsInBytes64(strtoull(json[parm_uc], nullptr, 0)); } + UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATA)); + if (json.containsKey(parm_uc)) { data = strtoull(json[parm_uc], nullptr, 0); } + if (0 == bits) { return IE_SYNTAX_IRSEND; } + + + if (XdrvMailbox.index > repeat + 1) { repeat = XdrvMailbox.index - 1; } + + char dvalue[32]; + char hvalue[32]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol %d, bits %d, data 0x%s (%s), repeat %d"), + protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat); + + irsend_active = true; + bool success = irsend->send(protocol, data, bits, repeat); + + if (!success) { + irsend_active = false; + ResponseCmndChar(D_JSON_PROTOCOL_NOT_SUPPORTED); + } + return IE_NO_ERROR; +} + +uint32_t IrRemoteCmndIrSendRaw(void) +{ + + + + + + + + char *p; + char *str = strtok_r(XdrvMailbox.data, ", ", &p); + if (p == nullptr) { + return IE_INVALID_RAWDATA; + } + + + uint16_t repeat = XdrvMailbox.index > 0 ? XdrvMailbox.index - 1 : 0; + + uint16_t freq = atoi(str); + if (!freq && (*str != '0')) { + uint16_t count = 0; + char *q = p; + for (; *q; count += (*q++ == ',')); + if (count < 2) { + return IE_INVALID_RAWDATA; + } + + uint16_t parm[count]; + for (uint32_t i = 0; i < count; i++) { + parm[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0); + if (!parm[i]) { + if (!i) { + parm[0] = 38000; + } else { + return IE_INVALID_RAWDATA; + } + } + } + + uint16_t i = 0; + if (count < 4) { + + uint16_t mark = parm[1] *2; + if (3 == count) { + if (parm[2] < parm[1]) { + + mark = parm[1] * parm[2]; + } else { + + mark = parm[2]; + } + } + uint16_t raw_array[strlen(p)]; + for (; *p; *p++) { + if (*p == '0') { + raw_array[i++] = parm[1]; + } + else if (*p == '1') { + raw_array[i++] = mark; + } + } + irsend_active = true; + for (uint32_t r = 0; r <= repeat; r++) { + irsend->sendRaw(raw_array, i, parm[0]); + if (r < repeat) { + irsend->space(40000); + } + } + } + else if (6 == count) { + + uint16_t raw_array[strlen(p)*2+3]; + raw_array[i++] = parm[1]; + raw_array[i++] = parm[2]; + uint32_t inter_message_32 = (parm[1] + parm[2]) * 3; + uint16_t inter_message = (inter_message_32 > 65000) ? 65000 : inter_message_32; + for (; *p; *p++) { + if (*p == '0') { + raw_array[i++] = parm[3]; + raw_array[i++] = parm[4]; + } + else if (*p == '1') { + raw_array[i++] = parm[3]; + raw_array[i++] = parm[5]; + } + } + raw_array[i++] = parm[3]; + irsend_active = true; + for (uint32_t r = 0; r <= repeat; r++) { + irsend->sendRaw(raw_array, i, parm[0]); + if (r < repeat) { + irsend->space(inter_message); + } + } + } + else { + return IE_INVALID_RAWDATA; + } + } else { + if (!freq) { freq = 38000; } + uint16_t count = 0; + char *q = p; + for (; *q; count += (*q++ == ',')); + if (0 == count) { + return IE_INVALID_RAWDATA; + } + + + count++; + if (count < 200) { + uint16_t raw_array[count]; + for (uint32_t i = 0; i < count; i++) { + raw_array[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0); + } + + + + irsend_active = true; + for (uint32_t r = 0; r <= repeat; r++) { + irsend->sendRaw(raw_array, count, freq); + } + } else { + uint16_t *raw_array = reinterpret_cast(malloc(count * sizeof(uint16_t))); + if (raw_array == nullptr) { + return IE_INVALID_RAWDATA; + } + + for (uint32_t i = 0; i < count; i++) { + raw_array[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0); + } + + + + irsend_active = true; + for (uint32_t r = 0; r <= repeat; r++) { + irsend->sendRaw(raw_array, count, freq); + } + free(raw_array); + } + } + + return IE_NO_ERROR; +} + +void CmndIrSend(void) +{ + uint8_t error = IE_SYNTAX_IRSEND; + + if (XdrvMailbox.data_len) { + if (strstr(XdrvMailbox.data, "{") == nullptr) { + error = IrRemoteCmndIrSendRaw(); + } else { + error = IrRemoteCmndIrSendJson(); + } + } + IrRemoteCmndResponse(error); +} + +void IrRemoteCmndResponse(uint32_t error) +{ + switch (error) { + case IE_INVALID_RAWDATA: + ResponseCmndChar(D_JSON_INVALID_RAWDATA); + break; + case IE_INVALID_JSON: + ResponseCmndChar(D_JSON_INVALID_JSON); + break; + case IE_SYNTAX_IRSEND: + Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_NO " " D_JSON_IR_BITS " " D_JSON_OR " " D_JSON_IR_DATA "\"}")); + break; + case IE_SYNTAX_IRHVAC: + Response_P(PSTR("{\"" D_CMND_IRHVAC "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_VENDOR ", " D_JSON_IRHVAC_MODE " " D_JSON_OR " " D_JSON_IRHVAC_FANSPEED "\"}")); + break; + case IE_UNSUPPORTED_HVAC: + Response_P(PSTR("{\"" D_CMND_IRHVAC "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_VENDOR " (%s)\"}"), listSupportedProtocols(true).c_str()); + break; + case IE_UNSUPPORTED_PROTOCOL: + Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_PROTOCOL " (%s)\"}"), listSupportedProtocols(false).c_str()); + break; + default: + ResponseCmndDone(); + } +} + + + + + +bool Xdrv05(uint8_t function) +{ + bool result = false; + + if ((pin[GPIO_IRSEND] < 99) || (pin[GPIO_IRRECV] < 99)) { + switch (function) { + case FUNC_PRE_INIT: + if (pin[GPIO_IRSEND] < 99) { + IrSendInit(); + } + if (pin[GPIO_IRRECV] < 99) { + IrReceiveInit(); + } + break; + case FUNC_EVERY_50_MSECOND: + if (pin[GPIO_IRRECV] < 99) { + IrReceiveCheck(); + } + irsend_active = false; + break; + case FUNC_COMMAND: + if (pin[GPIO_IRSEND] < 99) { + result = DecodeCommand(kIrRemoteCommands, IrRemoteCommand); + } + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_06_snfbridge.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_06_snfbridge.ino" +#ifdef USE_SONOFF_RF + + + + +#define XDRV_06 6 + +const uint32_t SFB_TIME_AVOID_DUPLICATE = 2000; + +enum SonoffBridgeCommands { + CMND_RFSYNC, CMND_RFLOW, CMND_RFHIGH, CMND_RFHOST, CMND_RFCODE }; + +const char kSonoffBridgeCommands[] PROGMEM = "|" + D_CMND_RFSYNC "|" D_CMND_RFLOW "|" D_CMND_RFHIGH "|" D_CMND_RFHOST "|" D_CMND_RFCODE "|" D_CMND_RFKEY "|" D_CMND_RFRAW; + +void (* const SonoffBridgeCommand[])(void) PROGMEM = { + &CmndRfBridge, &CmndRfBridge, &CmndRfBridge, &CmndRfBridge, &CmndRfBridge, &CmndRfKey, &CmndRfRaw }; + +struct SONOFFBRIDGE { + uint32_t last_received_id = 0; + uint32_t last_send_code = 0; + uint32_t last_time = 0; + uint32_t last_learn_time = 0; + uint8_t receive_flag = 0; + uint8_t receive_raw_flag = 0; + uint8_t learn_key = 1; + uint8_t learn_active = 0; + uint8_t expected_bytes = 0; +} SnfBridge; + +#ifdef USE_RF_FLASH + + + + + + + +#include "ihx.h" +#include "c2.h" + +const ssize_t RF_RECORD_NO_START_FOUND = -1; +const ssize_t RF_RECORD_NO_END_FOUND = -2; + +ssize_t rf_find_hex_record_start(uint8_t *buf, size_t size) +{ + for (size_t i = 0; i < size; i++) { + if (buf[i] == ':') { + return i; + } + } + return RF_RECORD_NO_START_FOUND; +} + +ssize_t rf_find_hex_record_end(uint8_t *buf, size_t size) +{ + for (size_t i = 0; i < size; i++) { + if (buf[i] == '\n') { + return i; + } + } + return RF_RECORD_NO_END_FOUND; +} + +ssize_t rf_glue_remnant_with_new_data_and_write(const uint8_t *remnant_data, uint8_t *new_data, size_t new_data_len) +{ + ssize_t record_start; + ssize_t record_end; + ssize_t glue_record_sz; + uint8_t *glue_buf; + ssize_t result; + + if (remnant_data[0] != ':') { return -8; } + + + record_end = rf_find_hex_record_end(new_data, new_data_len); + record_start = rf_find_hex_record_start(new_data, new_data_len); + + + + + if ((record_start != RF_RECORD_NO_START_FOUND) && (record_start < record_end)) { + return -8; + } + + glue_record_sz = strlen((const char *) remnant_data) + record_end; + + glue_buf = (uint8_t *) malloc(glue_record_sz); + if (glue_buf == nullptr) { return -2; } + + + memcpy(glue_buf, remnant_data, strlen((const char *) remnant_data)); + memcpy(glue_buf + strlen((const char *) remnant_data), new_data, record_end); + + result = rf_decode_and_write(glue_buf, glue_record_sz); + free(glue_buf); + return result; +} + +ssize_t rf_decode_and_write(uint8_t *record, size_t size) +{ + uint8_t err = ihx_decode(record, size); + if (err != IHX_SUCCESS) { return -13; } + + ihx_t *h = (ihx_t *) record; + if (h->record_type == IHX_RT_DATA) { + int retries = 5; + uint16_t address = h->address_high * 0x100 + h->address_low; + + do { + err = c2_programming_init(); + err = c2_block_write(address, h->data, h->len); + } while (err != C2_SUCCESS && retries--); + } else if (h->record_type == IHX_RT_END_OF_FILE) { + + err = c2_reset(); + } + + if (err != C2_SUCCESS) { return -12; } + + return 0; +} + +ssize_t rf_search_and_write(uint8_t *buf, size_t size) +{ + + ssize_t rec_end; + ssize_t rec_start; + ssize_t err; + + for (size_t i = 0; i < size; i++) { + + rec_start = rf_find_hex_record_start(buf + i, size - i); + if (rec_start == RF_RECORD_NO_START_FOUND) { + + return -8; + } + + + rec_start += i; + rec_end = rf_find_hex_record_end(buf + rec_start, size - rec_start); + if (rec_end == RF_RECORD_NO_END_FOUND) { + + return rec_start; + } + + + rec_end += rec_start; + + err = rf_decode_and_write(buf + rec_start, rec_end - rec_start); + if (err < 0) { return err; } + i = rec_end; + } + + return 0; +} + +uint8_t rf_erase_flash(void) +{ + uint8_t err; + + for (uint32_t i = 0; i < 4; i++) { + err = c2_programming_init(); + if (err != C2_SUCCESS) { + return 10; + } + err = c2_device_erase(); + if (err != C2_SUCCESS) { + if (i < 3) { + c2_reset(); + } else { + return 11; + } + } else { + break; + } + } + return 0; +} + +uint8_t SnfBrUpdateInit(void) +{ + pinMode(PIN_C2CK, OUTPUT); + pinMode(PIN_C2D, INPUT); + + return rf_erase_flash(); +} +#endif + + + +void SonoffBridgeReceivedRaw(void) +{ + + uint8_t buckets = 0; + + if (0xB1 == serial_in_buffer[1]) { buckets = serial_in_buffer[2] << 1; } + + ResponseTime_P(PSTR(",\"" D_CMND_RFRAW "\":{\"" D_JSON_DATA "\":\"")); + for (uint32_t i = 0; i < serial_in_byte_counter; i++) { + ResponseAppend_P(PSTR("%02X"), serial_in_buffer[i]); + if (0xB1 == serial_in_buffer[1]) { + if ((i > 3) && buckets) { buckets--; } + if ((i < 3) || (buckets % 2) || (i == serial_in_byte_counter -2)) { + ResponseAppend_P(PSTR(" ")); + } + } + } + ResponseAppend_P(PSTR("\"}}")); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_CMND_RFRAW)); + + XdrvRulesProcess(); +} + + + +void SonoffBridgeLearnFailed(void) +{ + SnfBridge.learn_active = 0; + Response_P(S_JSON_COMMAND_INDEX_SVALUE, D_CMND_RFKEY, SnfBridge.learn_key, D_JSON_LEARN_FAILED); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RFKEY)); +} + +void SonoffBridgeReceived(void) +{ + uint16_t sync_time = 0; + uint16_t low_time = 0; + uint16_t high_time = 0; + uint32_t received_id = 0; + char rfkey[8]; + char stemp[16]; + + AddLogSerial(LOG_LEVEL_DEBUG); + + if (0xA2 == serial_in_buffer[0]) { + SonoffBridgeLearnFailed(); + } + else if (0xA3 == serial_in_buffer[0]) { + SnfBridge.learn_active = 0; + low_time = serial_in_buffer[3] << 8 | serial_in_buffer[4]; + high_time = serial_in_buffer[5] << 8 | serial_in_buffer[6]; + if (low_time && high_time) { + for (uint32_t i = 0; i < 9; i++) { + Settings.rf_code[SnfBridge.learn_key][i] = serial_in_buffer[i +1]; + } + Response_P(S_JSON_COMMAND_INDEX_SVALUE, D_CMND_RFKEY, SnfBridge.learn_key, D_JSON_LEARNED); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RFKEY)); + } else { + SonoffBridgeLearnFailed(); + } + } + else if (0xA4 == serial_in_buffer[0]) { + if (SnfBridge.learn_active) { + SonoffBridgeLearnFailed(); + } else { + sync_time = serial_in_buffer[1] << 8 | serial_in_buffer[2]; + low_time = serial_in_buffer[3] << 8 | serial_in_buffer[4]; + high_time = serial_in_buffer[5] << 8 | serial_in_buffer[6]; + received_id = serial_in_buffer[7] << 16 | serial_in_buffer[8] << 8 | serial_in_buffer[9]; + + unsigned long now = millis(); + if (!((received_id == SnfBridge.last_received_id) && (now - SnfBridge.last_time < SFB_TIME_AVOID_DUPLICATE))) { + SnfBridge.last_received_id = received_id; + SnfBridge.last_time = now; + strncpy_P(rfkey, PSTR("\"" D_JSON_NONE "\""), sizeof(rfkey)); + for (uint32_t i = 1; i <= 16; i++) { + if (Settings.rf_code[i][0]) { + uint32_t send_id = Settings.rf_code[i][6] << 16 | Settings.rf_code[i][7] << 8 | Settings.rf_code[i][8]; + if (send_id == received_id) { + snprintf_P(rfkey, sizeof(rfkey), PSTR("%d"), i); + break; + } + } + } + if (Settings.flag.rf_receive_decimal) { + snprintf_P(stemp, sizeof(stemp), PSTR("%u"), received_id); + } else { + snprintf_P(stemp, sizeof(stemp), PSTR("\"%06X\""), received_id); + } + ResponseTime_P(PSTR(",\"" D_JSON_RFRECEIVED "\":{\"" D_JSON_SYNC "\":%d,\"" D_JSON_LOW "\":%d,\"" D_JSON_HIGH "\":%d,\"" D_JSON_DATA "\":%s,\"" D_CMND_RFKEY "\":%s}}"), + sync_time, low_time, high_time, stemp, rfkey); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_RFRECEIVED)); + XdrvRulesProcess(); + #ifdef USE_DOMOTICZ + DomoticzSensor(DZ_COUNT, received_id); + #endif + } + } + } +} + +bool SonoffBridgeSerialInput(void) +{ + + static int8_t receive_len = 0; + + if (SnfBridge.receive_flag) { + if (SnfBridge.receive_raw_flag) { + if (!serial_in_byte_counter) { + serial_in_buffer[serial_in_byte_counter++] = 0xAA; + } + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + if (serial_in_byte_counter == 3) { + if ((0xA6 == serial_in_buffer[1]) || (0xAB == serial_in_buffer[1])) { + receive_len = serial_in_buffer[2] + 4; + } + } + if ((!receive_len && (0x55 == serial_in_byte)) || (receive_len && (serial_in_byte_counter == receive_len))) { + SonoffBridgeReceivedRaw(); + SnfBridge.receive_flag = 0; + return 1; + } + } + else if (!((0 == serial_in_byte_counter) && (0 == serial_in_byte))) { + if (0 == serial_in_byte_counter) { + SnfBridge.expected_bytes = 2; + if (serial_in_byte >= 0xA3) { + SnfBridge.expected_bytes = 11; + } + if (serial_in_byte == 0xA6) { + SnfBridge.expected_bytes = 0; + serial_in_buffer[serial_in_byte_counter++] = 0xAA; + SnfBridge.receive_raw_flag = 1; + } + } + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + if ((SnfBridge.expected_bytes == serial_in_byte_counter) && (0x55 == serial_in_byte)) { + SonoffBridgeReceived(); + SnfBridge.receive_flag = 0; + return 1; + } + } + serial_in_byte = 0; + } + if (0xAA == serial_in_byte) { + serial_in_byte_counter = 0; + serial_in_byte = 0; + SnfBridge.receive_flag = 1; + receive_len = 0; + } + return 0; +} + +void SonoffBridgeSendCommand(uint8_t code) +{ + Serial.write(0xAA); + Serial.write(code); + Serial.write(0x55); +} + +void SonoffBridgeSendAck(void) +{ + Serial.write(0xAA); + Serial.write(0xA0); + Serial.write(0x55); +} + +void SonoffBridgeSendCode(uint32_t code) +{ + Serial.write(0xAA); + Serial.write(0xA5); + for (uint32_t i = 0; i < 6; i++) { + Serial.write(Settings.rf_code[0][i]); + } + Serial.write((code >> 16) & 0xff); + Serial.write((code >> 8) & 0xff); + Serial.write(code & 0xff); + Serial.write(0x55); + Serial.flush(); +} + +void SonoffBridgeSend(uint8_t idx, uint8_t key) +{ + uint8_t code; + + key--; + Serial.write(0xAA); + Serial.write(0xA5); + for (uint32_t i = 0; i < 8; i++) { + Serial.write(Settings.rf_code[idx][i]); + } + if (0 == idx) { + code = (0x10 << (key >> 2)) | (1 << (key & 3)); + } else { + code = Settings.rf_code[idx][8]; + } + Serial.write(code); + Serial.write(0x55); + Serial.flush(); +#ifdef USE_DOMOTICZ + + +#endif +} + +void SonoffBridgeLearn(uint8_t key) +{ + SnfBridge.learn_key = key; + SnfBridge.learn_active = 1; + SnfBridge.last_learn_time = millis(); + Serial.write(0xAA); + Serial.write(0xA1); + Serial.write(0x55); +} + + + + + +void CmndRfBridge(void) +{ + char *p; + char stemp [10]; + uint32_t code = 0; + uint8_t radix = 10; + + uint32_t set_index = XdrvMailbox.command_code *2; + + if (XdrvMailbox.data[0] == '#') { + XdrvMailbox.data++; + XdrvMailbox.data_len--; + radix = 16; + } + + if (XdrvMailbox.data_len) { + code = strtol(XdrvMailbox.data, &p, radix); + if (code) { + if (CMND_RFCODE == XdrvMailbox.command_code) { + SnfBridge.last_send_code = code; + SonoffBridgeSendCode(code); + } else { + if (1 == XdrvMailbox.payload) { + code = pgm_read_byte(kDefaultRfCode + set_index) << 8 | pgm_read_byte(kDefaultRfCode + set_index +1); + } + uint8_t msb = code >> 8; + uint8_t lsb = code & 0xFF; + if ((code > 0) && (code < 0x7FFF) && (msb != 0x55) && (lsb != 0x55)) { + Settings.rf_code[0][set_index] = msb; + Settings.rf_code[0][set_index +1] = lsb; + } + } + } + } + if (CMND_RFCODE == XdrvMailbox.command_code) { + code = SnfBridge.last_send_code; + } else { + code = Settings.rf_code[0][set_index] << 8 | Settings.rf_code[0][set_index +1]; + } + if (10 == radix) { + snprintf_P(stemp, sizeof(stemp), PSTR("%d"), code); + } else { + snprintf_P(stemp, sizeof(stemp), PSTR("\"#%06X\""), code); + } + Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, stemp); +} + +void CmndRfKey(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 16)) { + unsigned long now = millis(); + if ((!SnfBridge.learn_active) || (now - SnfBridge.last_learn_time > 60100)) { + SnfBridge.learn_active = 0; + if (2 == XdrvMailbox.payload) { + SonoffBridgeLearn(XdrvMailbox.index); + ResponseCmndIdxChar(D_JSON_START_LEARNING); + } + else if (3 == XdrvMailbox.payload) { + Settings.rf_code[XdrvMailbox.index][0] = 0; + ResponseCmndIdxChar(D_JSON_SET_TO_DEFAULT); + } + else if (4 == XdrvMailbox.payload) { + for (uint32_t i = 0; i < 6; i++) { + Settings.rf_code[XdrvMailbox.index][i] = Settings.rf_code[0][i]; + } + Settings.rf_code[XdrvMailbox.index][6] = (SnfBridge.last_send_code >> 16) & 0xff; + Settings.rf_code[XdrvMailbox.index][7] = (SnfBridge.last_send_code >> 8) & 0xff; + Settings.rf_code[XdrvMailbox.index][8] = SnfBridge.last_send_code & 0xff; + ResponseCmndIdxChar(D_JSON_SAVED); + } else if (5 == XdrvMailbox.payload) { + uint8_t key = XdrvMailbox.index; + uint8_t index = (0 == Settings.rf_code[key][0]) ? 0 : key; + uint16_t sync_time = (Settings.rf_code[index][0] << 8) | Settings.rf_code[index][1]; + uint16_t low_time = (Settings.rf_code[index][2] << 8) | Settings.rf_code[index][3]; + uint16_t high_time = (Settings.rf_code[index][4] << 8) | Settings.rf_code[index][5]; + uint32_t code = (Settings.rf_code[index][6] << 16) | (Settings.rf_code[index][7] << 8); + if (0 == index) { + key--; + code |= (uint8_t)((0x10 << (key >> 2)) | (1 << (key & 3))); + } else { + code |= Settings.rf_code[index][8]; + } + Response_P(PSTR("{\"%s%d\":{\"" D_JSON_SYNC "\":%d,\"" D_JSON_LOW "\":%d,\"" D_JSON_HIGH "\":%d,\"" D_JSON_DATA "\":\"%06X\"}}"), + XdrvMailbox.command, XdrvMailbox.index, sync_time, low_time, high_time, code); + } else { + if ((1 == XdrvMailbox.payload) || (0 == Settings.rf_code[XdrvMailbox.index][0])) { + SonoffBridgeSend(0, XdrvMailbox.index); + ResponseCmndIdxChar(D_JSON_DEFAULT_SENT); + } else { + SonoffBridgeSend(XdrvMailbox.index, 0); + ResponseCmndIdxChar(D_JSON_LEARNED_SENT); + } + } + } else { + Response_P(S_JSON_COMMAND_INDEX_SVALUE, XdrvMailbox.command, SnfBridge.learn_key, D_JSON_LEARNING_ACTIVE); + } + } +} + +void CmndRfRaw(void) +{ + if (XdrvMailbox.data_len) { + if (XdrvMailbox.data_len < 6) { + switch (XdrvMailbox.payload) { + case 0: + SonoffBridgeSendCommand(0xA7); + case 1: + SnfBridge.receive_raw_flag = XdrvMailbox.payload; + break; + case 166: + case 167: + case 169: + case 176: + case 177: + case 255: + SonoffBridgeSendCommand(XdrvMailbox.payload); + SnfBridge.receive_raw_flag = 1; + break; + case 192: + char beep[] = "AAC000C055\0"; + SerialSendRaw(beep); + break; + } + } else { + SerialSendRaw(RemoveSpace(XdrvMailbox.data)); + SnfBridge.receive_raw_flag = 1; + } + } + ResponseCmndStateText(SnfBridge.receive_raw_flag); +} + + + + + +bool Xdrv06(uint8_t function) +{ + bool result = false; + + if (SONOFF_BRIDGE == my_module_type) { + switch (function) { + case FUNC_SERIAL: + result = SonoffBridgeSerialInput(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kSonoffBridgeCommands, SonoffBridgeCommand); + break; + case FUNC_INIT: + SnfBridge.receive_raw_flag = 0; + SonoffBridgeSendCommand(0xA7); + break; + case FUNC_PRE_INIT: + SetSerial(19200, TS_SERIAL_8N1); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_07_domoticz.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_07_domoticz.ino" +#ifdef USE_DOMOTICZ + +#define XDRV_07 7 + +#define D_PRFX_DOMOTICZ "Domoticz" +#define D_CMND_IDX "Idx" +#define D_CMND_KEYIDX "KeyIdx" +#define D_CMND_SWITCHIDX "SwitchIdx" +#define D_CMND_SENSORIDX "SensorIdx" +#define D_CMND_UPDATETIMER "UpdateTimer" + +const char kDomoticzCommands[] PROGMEM = D_PRFX_DOMOTICZ "|" + D_CMND_IDX "|" D_CMND_KEYIDX "|" D_CMND_SWITCHIDX "|" D_CMND_SENSORIDX "|" D_CMND_UPDATETIMER ; + +void (* const DomoticzCommand[])(void) PROGMEM = { + &CmndDomoticzIdx, &CmndDomoticzKeyIdx, &CmndDomoticzSwitchIdx, &CmndDomoticzSensorIdx, &CmndDomoticzUpdateTimer }; + +const char DOMOTICZ_MESSAGE[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\",\"Battery\":%d,\"RSSI\":%d}"; + +#if MAX_DOMOTICZ_SNS_IDX < DZ_MAX_SENSORS + #error "Domoticz: Too many sensors or change settings.h layout" +#endif + +const char kDomoticzSensors[] PROGMEM = + D_DOMOTICZ_TEMP "|" D_DOMOTICZ_TEMP_HUM "|" D_DOMOTICZ_TEMP_HUM_BARO "|" D_DOMOTICZ_POWER_ENERGY "|" D_DOMOTICZ_ILLUMINANCE "|" + D_DOMOTICZ_COUNT "|" D_DOMOTICZ_VOLTAGE "|" D_DOMOTICZ_CURRENT "|" D_DOMOTICZ_AIRQUALITY "|" D_DOMOTICZ_P1_SMART_METER "|" D_DOMOTICZ_SHUTTER ; + +char domoticz_in_topic[] = DOMOTICZ_IN_TOPIC; + +int domoticz_update_timer = 0; +uint32_t domoticz_fan_debounce = 0; +bool domoticz_subscribe = false; +bool domoticz_update_flag = true; + +#ifdef USE_SHUTTER +bool domoticz_is_shutter = false; +#endif + +int DomoticzBatteryQuality(void) +{ + + + + + int quality = 100; + +#ifdef USE_ADC_VCC + uint16_t voltage = ESP.getVcc(); + if (voltage <= 2600) { + quality = 0; + } else if (voltage >= 4600) { + quality = 200; + } else { + quality = (voltage - 2600) / 10; + } +#endif + return quality; +} + +int DomoticzRssiQuality(void) +{ + + + return WifiGetRssiAsQuality(WiFi.RSSI()) / 10; +} + +#ifdef USE_SONOFF_IFAN +void MqttPublishDomoticzFanState(void) +{ + if (Settings.flag.mqtt_enabled && Settings.domoticz_relay_idx[1]) { + char svalue[8]; + + int fan_speed = GetFanspeed(); + snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fan_speed * 10); + Response_P(DOMOTICZ_MESSAGE, (int)Settings.domoticz_relay_idx[1], (0 == fan_speed) ? 0 : 2, svalue, DomoticzBatteryQuality(), DomoticzRssiQuality()); + MqttPublish(domoticz_in_topic); + + domoticz_fan_debounce = millis(); + } +} + +void DomoticzUpdateFanState(void) +{ + if (domoticz_update_flag) { + MqttPublishDomoticzFanState(); + } + domoticz_update_flag = true; +} +#endif + +void MqttPublishDomoticzPowerState(uint8_t device) +{ + if (Settings.flag.mqtt_enabled) { + if ((device < 1) || (device > devices_present)) { device = 1; } + if (Settings.domoticz_relay_idx[device -1]) { +#ifdef USE_SHUTTER + if (domoticz_is_shutter) { + + } else { +#endif +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan() && (device > 1)) { + + } else { +#endif + char svalue[8]; + + snprintf_P(svalue, sizeof(svalue), PSTR("%d"), Settings.light_dimmer); + Response_P(DOMOTICZ_MESSAGE, (int)Settings.domoticz_relay_idx[device -1], (power & (1 << (device -1))) ? 1 : 0, (light_type) ? svalue : "", DomoticzBatteryQuality(), DomoticzRssiQuality()); + MqttPublish(domoticz_in_topic); +#ifdef USE_SONOFF_IFAN + } +#endif +#ifdef USE_SHUTTER + } +#endif + } + } +} + +void DomoticzUpdatePowerState(uint8_t device) +{ + if (domoticz_update_flag) { + MqttPublishDomoticzPowerState(device); + } + domoticz_update_flag = true; +} + +void DomoticzMqttUpdate(void) +{ + if (domoticz_subscribe && (Settings.domoticz_update_timer || domoticz_update_timer)) { + domoticz_update_timer--; + if (domoticz_update_timer <= 0) { + domoticz_update_timer = Settings.domoticz_update_timer; + for (uint32_t i = 1; i <= devices_present; i++) { +#ifdef USE_SHUTTER + if (domoticz_is_shutter) + { + + break; + } +#endif +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan() && (i > 1)) { + MqttPublishDomoticzFanState(); + break; + } else { +#endif + MqttPublishDomoticzPowerState(i); +#ifdef USE_SONOFF_IFAN + } +#endif + } + } + } +} + +void DomoticzMqttSubscribe(void) +{ + uint8_t maxdev = (devices_present > MAX_DOMOTICZ_IDX) ? MAX_DOMOTICZ_IDX : devices_present; + for (uint32_t i = 0; i < maxdev; i++) { + if (Settings.domoticz_relay_idx[i]) { + domoticz_subscribe = true; + } + } + + if (domoticz_subscribe) { + char stopic[TOPSZ]; + snprintf_P(stopic, sizeof(stopic), PSTR(DOMOTICZ_OUT_TOPIC "/#")); + MqttSubscribe(stopic); + } +} +# 219 "S:/Development/Tasmota/tasmota/xdrv_07_domoticz.ino" +bool DomoticzMqttData(void) +{ + domoticz_update_flag = true; + + if (strncasecmp_P(XdrvMailbox.topic, PSTR(DOMOTICZ_OUT_TOPIC), strlen(DOMOTICZ_OUT_TOPIC)) != 0) { + return false; + } + + + if (XdrvMailbox.data_len < 20) { + return true; + } + StaticJsonBuffer<400> jsonBuf; + JsonObject& domoticz = jsonBuf.parseObject(XdrvMailbox.data); + if (!domoticz.success()) { + return true; + } + + + + uint32_t idx = domoticz["idx"]; + int16_t nvalue = -1; + if (domoticz.containsKey("nvalue")) { + nvalue = domoticz["nvalue"]; + } + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_DOMOTICZ "idx %d, nvalue %d"), idx, nvalue); + + bool found = false; + if ((idx > 0) && (nvalue >= 0) && (nvalue <= 15)) { + uint8_t maxdev = (devices_present > MAX_DOMOTICZ_IDX) ? MAX_DOMOTICZ_IDX : devices_present; + for (uint32_t i = 0; i < maxdev; i++) { + if (idx == Settings.domoticz_relay_idx[i]) { + bool iscolordimmer = strcmp_P(domoticz["dtype"],PSTR("Color Switch")) == 0; + bool isShutter = strcmp_P(domoticz["dtype"],PSTR("Light/Switch")) == 0 & strncmp_P(domoticz["switchType"],PSTR("Blinds"), 6) == 0; + + char stemp1[10]; + snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), i +1); +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan() && (1 == i)) { + uint8_t svalue = 0; + if (domoticz.containsKey("svalue1")) { + svalue = domoticz["svalue1"]; + } else { + return true; + } + svalue = (nvalue == 2) ? svalue / 10 : 0; + if (GetFanspeed() == svalue) { + return true; + } + if (TimePassedSince(domoticz_fan_debounce) < 1000) { + return true; + } + snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_FANSPEED)); + snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), svalue); + found = true; + } else +#endif +#ifdef USE_SHUTTER + if (isShutter) + { + if (domoticz.containsKey("nvalue")) { + nvalue = domoticz["nvalue"]; + } + + uint8_t position = 0; + if (domoticz.containsKey("svalue1")) { + position = domoticz["svalue1"]; + } + if (nvalue != 2) { + position = nvalue == 0 ? 0 : 100; + } + + snprintf_P(XdrvMailbox.topic, TOPSZ, PSTR("/" D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION)); + snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), position); + XdrvMailbox.data_len = position > 99 ? 3 : (position > 9 ? 2 : 1); + + found = true; + } else +#endif +#ifdef USE_LIGHT + if (iscolordimmer && 10 == nvalue) { + + JsonObject& color = domoticz["Color"]; + uint16_t level = nvalue = domoticz["svalue1"]; + uint16_t r = color["r"]; r = r * level / 100; + uint16_t g = color["g"]; g = g * level / 100; + uint16_t b = color["b"]; b = b * level / 100; + uint16_t cw = color["cw"]; cw = cw * level / 100; + uint16_t ww = color["ww"]; ww = ww * level / 100; + uint16_t m = 0; + uint16_t t = 0; + if (color.containsKey("m")) { + m = color["m"]; + t = color["t"]; + } + if (2 == m) { + snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_BACKLOG)); + snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR(D_CMND_COLORTEMPERATURE " %d;" D_CMND_DIMMER " %d"), changeUIntScale(t, 0, 255, CT_MIN, CT_MAX), level); + } else { + snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_COLOR)); + snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%02x%02x%02x%02x%02x"), r, g, b, cw, ww); + } + found = true; + } + else if ((!iscolordimmer && 2 == nvalue) || + (iscolordimmer && 15 == nvalue)) { + if (domoticz.containsKey("svalue1")) { + nvalue = domoticz["svalue1"]; + } else { + return true; + } + if (light_type && (Settings.light_dimmer == nvalue) && ((power >> i) &1)) { + return true; + } + snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_DIMMER)); + snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue); + found = true; + } else +#endif + if (1 == nvalue || 0 == nvalue) { + if (((power >> i) &1) == (power_t)nvalue) { + return true; + } + snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_POWER "%s"), (devices_present > 1) ? stemp1 : ""); + snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue); + found = true; + } + break; + } + } + } + if (!found) { return true; } + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_DOMOTICZ D_RECEIVED_TOPIC " %s, " D_DATA " %s"), XdrvMailbox.topic, XdrvMailbox.data); + + domoticz_update_flag = false; + return false; +} + + + +bool DomoticzSendKey(uint8_t key, uint8_t device, uint8_t state, uint8_t svalflg) +{ + bool result = false; + + if (device <= MAX_DOMOTICZ_IDX) { + if ((Settings.domoticz_key_idx[device -1] || Settings.domoticz_switch_idx[device -1]) && (svalflg)) { + Response_P(PSTR("{\"command\":\"switchlight\",\"idx\":%d,\"switchcmd\":\"%s\"}"), + (key) ? Settings.domoticz_switch_idx[device -1] : Settings.domoticz_key_idx[device -1], (state) ? (POWER_TOGGLE == state) ? "Toggle" : "On" : "Off"); + MqttPublish(domoticz_in_topic); + result = true; + } + } + return result; +} +# 393 "S:/Development/Tasmota/tasmota/xdrv_07_domoticz.ino" +uint8_t DomoticzHumidityState(char *hum) +{ + uint8_t h = atoi(hum); + return (!h) ? 0 : (h < 40) ? 2 : (h > 70) ? 3 : 1; +} + +void DomoticzSensor(uint8_t idx, char *data) +{ + if (Settings.domoticz_sensor_idx[idx]) { + char dmess[128]; + + memcpy(dmess, mqtt_data, sizeof(dmess)); + if (DZ_AIRQUALITY == idx) { + Response_P(PSTR("{\"idx\":%d,\"nvalue\":%s,\"Battery\":%d,\"RSSI\":%d}"), + Settings.domoticz_sensor_idx[idx], data, DomoticzBatteryQuality(), DomoticzRssiQuality()); + } else { + uint8_t nvalue = 0; +#ifdef USE_SHUTTER + if (DZ_SHUTTER == idx) { + uint8_t position = atoi(data); + nvalue = position < 2 ? 0 : (position == 100 ? 1 : 2); + } +#endif + Response_P(DOMOTICZ_MESSAGE, + Settings.domoticz_sensor_idx[idx], nvalue, data, DomoticzBatteryQuality(), DomoticzRssiQuality()); + } + MqttPublish(domoticz_in_topic); + memcpy(mqtt_data, dmess, sizeof(dmess)); + } +} + +void DomoticzSensor(uint8_t idx, uint32_t value) +{ + char data[16]; + snprintf_P(data, sizeof(data), PSTR("%d"), value); + DomoticzSensor(idx, data); +} + +void DomoticzTempHumSensor(char *temp, char *hum) +{ + char data[16]; + snprintf_P(data, sizeof(data), PSTR("%s;%s;%d"), temp, hum, DomoticzHumidityState(hum)); + DomoticzSensor(DZ_TEMP_HUM, data); +} + +void DomoticzTempHumPressureSensor(char *temp, char *hum, char *baro) +{ + char data[32]; + snprintf_P(data, sizeof(data), PSTR("%s;%s;%d;%s;5"), temp, hum, DomoticzHumidityState(hum), baro); + DomoticzSensor(DZ_TEMP_HUM_BARO, data); +} + +void DomoticzSensorPowerEnergy(int power, char *energy) +{ + char data[16]; + snprintf_P(data, sizeof(data), PSTR("%d;%s"), power, energy); + DomoticzSensor(DZ_POWER_ENERGY, data); +} + +void DomoticzSensorP1SmartMeter(char *usage1, char *usage2, char *return1, char *return2, int power) +{ + + + + + + int consumed = power; + int produced = 0; + if (power < 0) { + consumed = 0; + produced = -power; + } + char data[64]; + snprintf_P(data, sizeof(data), PSTR("%s;%s;%s;%s;%d;%d"), usage1, usage2, return1, return2, consumed, produced); + DomoticzSensor(DZ_P1_SMART_METER, data); +} + + + + + +void CmndDomoticzIdx(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_DOMOTICZ_IDX)) { + if (XdrvMailbox.payload >= 0) { + Settings.domoticz_relay_idx[XdrvMailbox.index -1] = XdrvMailbox.payload; + restart_flag = 2; + } + ResponseCmndIdxNumber(Settings.domoticz_relay_idx[XdrvMailbox.index -1]); + } +} + +void CmndDomoticzKeyIdx(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_DOMOTICZ_IDX)) { + if (XdrvMailbox.payload >= 0) { + Settings.domoticz_key_idx[XdrvMailbox.index -1] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Settings.domoticz_key_idx[XdrvMailbox.index -1]); + } +} + +void CmndDomoticzSwitchIdx(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_DOMOTICZ_IDX)) { + if (XdrvMailbox.payload >= 0) { + Settings.domoticz_switch_idx[XdrvMailbox.index -1] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Settings.domoticz_switch_idx[XdrvMailbox.index -1]); + } +} + +void CmndDomoticzSensorIdx(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= DZ_MAX_SENSORS)) { + if (XdrvMailbox.payload >= 0) { + Settings.domoticz_sensor_idx[XdrvMailbox.index -1] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Settings.domoticz_sensor_idx[XdrvMailbox.index -1]); + } +} + +void CmndDomoticzUpdateTimer(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { + Settings.domoticz_update_timer = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.domoticz_update_timer); +} + + + + + +#ifdef USE_WEBSERVER + +#define WEB_HANDLE_DOMOTICZ "dm" + +const char S_CONFIGURE_DOMOTICZ[] PROGMEM = D_CONFIGURE_DOMOTICZ; + +const char HTTP_BTN_MENU_DOMOTICZ[] PROGMEM = + "

"; + +const char HTTP_FORM_DOMOTICZ[] PROGMEM = + "
 " D_DOMOTICZ_PARAMETERS " " + "
" + ""; +const char HTTP_FORM_DOMOTICZ_RELAY[] PROGMEM = + "" + ""; +const char HTTP_FORM_DOMOTICZ_SWITCH[] PROGMEM = + ""; +const char HTTP_FORM_DOMOTICZ_SENSOR[] PROGMEM = + ""; +const char HTTP_FORM_DOMOTICZ_TIMER[] PROGMEM = + ""; + +void HandleDomoticzConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_DOMOTICZ); + + if (WebServer->hasArg("save")) { + DomoticzSaveSettings(); + WebRestart(1); + return; + } + + char stemp[40]; + + WSContentStart_P(S_CONFIGURE_DOMOTICZ); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_DOMOTICZ); + for (uint32_t i = 0; i < MAX_DOMOTICZ_IDX; i++) { + if (i < devices_present) { + WSContentSend_P(HTTP_FORM_DOMOTICZ_RELAY, + i +1, i, Settings.domoticz_relay_idx[i], + i +1, i, Settings.domoticz_key_idx[i]); + } + if (pin[GPIO_SWT1 +i] < 99) { + WSContentSend_P(HTTP_FORM_DOMOTICZ_SWITCH, + i +1, i, Settings.domoticz_switch_idx[i]); + } +#ifdef USE_SONOFF_IFAN + if (IsModuleIfan() && (1 == i)) { break; } +#endif + } + for (uint32_t i = 0; i < DZ_MAX_SENSORS; i++) { + WSContentSend_P(HTTP_FORM_DOMOTICZ_SENSOR, + i +1, GetTextIndexed(stemp, sizeof(stemp), i, kDomoticzSensors), i, Settings.domoticz_sensor_idx[i]); + } + WSContentSend_P(HTTP_FORM_DOMOTICZ_TIMER, Settings.domoticz_update_timer); + WSContentSend_P(PSTR("
" D_DOMOTICZ_IDX " %d
" D_DOMOTICZ_KEY_IDX " %d
" D_DOMOTICZ_SWITCH_IDX " %d
" D_DOMOTICZ_SENSOR_IDX " %d %s
" D_DOMOTICZ_UPDATE_TIMER " (" STR(DOMOTICZ_UPDATE_TIMER) ")
")); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void DomoticzSaveSettings(void) +{ + char stemp[20]; + char ssensor_indices[6 * MAX_DOMOTICZ_SNS_IDX]; + char tmp[100]; + + for (uint32_t i = 0; i < MAX_DOMOTICZ_IDX; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("r%d"), i); + WebGetArg(stemp, tmp, sizeof(tmp)); + Settings.domoticz_relay_idx[i] = (!strlen(tmp)) ? 0 : atoi(tmp); + snprintf_P(stemp, sizeof(stemp), PSTR("k%d"), i); + WebGetArg(stemp, tmp, sizeof(tmp)); + Settings.domoticz_key_idx[i] = (!strlen(tmp)) ? 0 : atoi(tmp); + snprintf_P(stemp, sizeof(stemp), PSTR("s%d"), i); + WebGetArg(stemp, tmp, sizeof(tmp)); + Settings.domoticz_switch_idx[i] = (!strlen(tmp)) ? 0 : atoi(tmp); + } + ssensor_indices[0] = '\0'; + for (uint32_t i = 0; i < DZ_MAX_SENSORS; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("l%d"), i); + WebGetArg(stemp, tmp, sizeof(tmp)); + Settings.domoticz_sensor_idx[i] = (!strlen(tmp)) ? 0 : atoi(tmp); + snprintf_P(ssensor_indices, sizeof(ssensor_indices), PSTR("%s%s%d"), ssensor_indices, (strlen(ssensor_indices)) ? "," : "", Settings.domoticz_sensor_idx[i]); + } + WebGetArg("ut", tmp, sizeof(tmp)); + Settings.domoticz_update_timer = (!strlen(tmp)) ? DOMOTICZ_UPDATE_TIMER : atoi(tmp); + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_DOMOTICZ D_CMND_IDX " %d,%d,%d,%d, " D_CMND_KEYIDX " %d,%d,%d,%d, " D_CMND_SWITCHIDX " %d,%d,%d,%d, " D_CMND_SENSORIDX " %s, " D_CMND_UPDATETIMER " %d"), + Settings.domoticz_relay_idx[0], Settings.domoticz_relay_idx[1], Settings.domoticz_relay_idx[2], Settings.domoticz_relay_idx[3], + Settings.domoticz_key_idx[0], Settings.domoticz_key_idx[1], Settings.domoticz_key_idx[2], Settings.domoticz_key_idx[3], + Settings.domoticz_switch_idx[0], Settings.domoticz_switch_idx[1], Settings.domoticz_switch_idx[2], Settings.domoticz_switch_idx[3], + ssensor_indices, Settings.domoticz_update_timer); +} +#endif + + + + + +bool Xdrv07(uint8_t function) +{ + bool result = false; + + if (Settings.flag.mqtt_enabled) { + switch (function) { + case FUNC_EVERY_SECOND: + DomoticzMqttUpdate(); + break; + case FUNC_MQTT_DATA: + result = DomoticzMqttData(); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_DOMOTICZ); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/" WEB_HANDLE_DOMOTICZ, HandleDomoticzConfiguration); + break; +#endif + case FUNC_MQTT_SUBSCRIBE: + DomoticzMqttSubscribe(); +#ifdef USE_SHUTTER + if (Settings.domoticz_sensor_idx[DZ_SHUTTER]) { domoticz_is_shutter = true; } +#endif + break; + case FUNC_MQTT_INIT: + domoticz_update_timer = 2; + break; + case FUNC_SHOW_SENSOR: + + break; + case FUNC_COMMAND: + result = DecodeCommand(kDomoticzCommands, DomoticzCommand); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_08_serial_bridge.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_08_serial_bridge.ino" +#ifdef USE_SERIAL_BRIDGE + + + + +#define XDRV_08 8 + +const uint8_t SERIAL_BRIDGE_BUFFER_SIZE = 130; + +const char kSerialBridgeCommands[] PROGMEM = "|" + D_CMND_SSERIALSEND "|" D_CMND_SBAUDRATE; + +void (* const SerialBridgeCommand[])(void) PROGMEM = { + &CmndSSerialSend, &CmndSBaudrate }; + +#include + +TasmotaSerial *SerialBridgeSerial = nullptr; + +unsigned long serial_bridge_polling_window = 0; +char *serial_bridge_buffer = nullptr; +int serial_bridge_in_byte_counter = 0; +bool serial_bridge_active = true; +bool serial_bridge_raw = false; + +void SerialBridgeInput(void) +{ + while (SerialBridgeSerial->available()) { + yield(); + uint8_t serial_in_byte = SerialBridgeSerial->read(); + + if ((serial_in_byte > 127) && !serial_bridge_raw) { + serial_bridge_in_byte_counter = 0; + SerialBridgeSerial->flush(); + return; + } + if (serial_in_byte || serial_bridge_raw) { + + if ((serial_bridge_in_byte_counter < SERIAL_BRIDGE_BUFFER_SIZE -1) && + ((isprint(serial_in_byte) && (128 == Settings.serial_delimiter)) || + ((serial_in_byte != Settings.serial_delimiter) && (128 != Settings.serial_delimiter)) || + serial_bridge_raw)) { + serial_bridge_buffer[serial_bridge_in_byte_counter++] = serial_in_byte; + serial_bridge_polling_window = millis(); + } else { + serial_bridge_polling_window = 0; + break; + } + } + } + + if (serial_bridge_in_byte_counter && (millis() > (serial_bridge_polling_window + SERIAL_POLLING))) { + serial_bridge_buffer[serial_bridge_in_byte_counter] = 0; + char hex_char[(serial_bridge_in_byte_counter * 2) + 2]; + bool assume_json = (!serial_bridge_raw && (serial_bridge_buffer[0] == '{')); + Response_P(PSTR("{\"" D_JSON_SSERIALRECEIVED "\":%s%s%s}"), + (assume_json) ? "" : """", + (serial_bridge_raw) ? ToHex_P((unsigned char*)serial_bridge_buffer, serial_bridge_in_byte_counter, hex_char, sizeof(hex_char)) : serial_bridge_buffer, + (assume_json) ? "" : """"); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_SSERIALRECEIVED)); + XdrvRulesProcess(); + serial_bridge_in_byte_counter = 0; + } +} + + + +void SerialBridgeInit(void) +{ + serial_bridge_active = false; + if ((pin[GPIO_SBR_RX] < 99) && (pin[GPIO_SBR_TX] < 99)) { + SerialBridgeSerial = new TasmotaSerial(pin[GPIO_SBR_RX], pin[GPIO_SBR_TX]); + if (SerialBridgeSerial->begin(Settings.sbaudrate * 300)) { + if (SerialBridgeSerial->hardwareSerial()) { + ClaimSerial(); + serial_bridge_buffer = serial_in_buffer; + } else { + serial_bridge_buffer = (char*)(malloc(SERIAL_BRIDGE_BUFFER_SIZE)); + } + serial_bridge_active = true; + SerialBridgeSerial->flush(); + } + } +} + + + + + +void CmndSSerialSend(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 5)) { + serial_bridge_raw = (XdrvMailbox.index > 3); + if (XdrvMailbox.data_len > 0) { + if (1 == XdrvMailbox.index) { + SerialBridgeSerial->write(XdrvMailbox.data, XdrvMailbox.data_len); + SerialBridgeSerial->write("\n"); + } + else if ((2 == XdrvMailbox.index) || (4 == XdrvMailbox.index)) { + SerialBridgeSerial->write(XdrvMailbox.data, XdrvMailbox.data_len); + } + else if (3 == XdrvMailbox.index) { + SerialBridgeSerial->write(Unescape(XdrvMailbox.data, &XdrvMailbox.data_len), XdrvMailbox.data_len); + } + else if (5 == XdrvMailbox.index) { + char *p; + char stemp[3]; + uint8_t code; + + char *codes = RemoveSpace(XdrvMailbox.data); + int size = strlen(XdrvMailbox.data); + + while (size > 1) { + strlcpy(stemp, codes, sizeof(stemp)); + code = strtol(stemp, &p, 16); + SerialBridgeSerial->write(code); + size -= 2; + codes += 2; + } + } + ResponseCmndDone(); + } + } +} + +void CmndSBaudrate(void) +{ + if (XdrvMailbox.payload >= 300) { + XdrvMailbox.payload /= 300; + Settings.sbaudrate = XdrvMailbox.payload; + SerialBridgeSerial->begin(Settings.sbaudrate * 300); + } + ResponseCmndNumber(Settings.sbaudrate * 300); +} + + + + + +bool Xdrv08(uint8_t function) +{ + bool result = false; + + if (serial_bridge_active) { + switch (function) { + case FUNC_LOOP: + if (SerialBridgeSerial) { SerialBridgeInput(); } + break; + case FUNC_PRE_INIT: + SerialBridgeInit(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kSerialBridgeCommands, SerialBridgeCommand); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_09_timers.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_09_timers.ino" +#ifdef USE_TIMERS +# 39 "S:/Development/Tasmota/tasmota/xdrv_09_timers.ino" +#define XDRV_09 9 + +const char kTimerCommands[] PROGMEM = "|" + D_CMND_TIMER "|" D_CMND_TIMERS +#ifdef USE_SUNRISE + "|" D_CMND_LATITUDE "|" D_CMND_LONGITUDE +#endif + ; + +void (* const TimerCommand[])(void) PROGMEM = { + &CmndTimer, &CmndTimers +#ifdef USE_SUNRISE + , &CmndLatitude, &CmndLongitude +#endif + }; + +uint16_t timer_last_minute = 60; +int8_t timer_window[MAX_TIMERS] = { 0 }; + +#ifdef USE_SUNRISE +# 67 "S:/Development/Tasmota/tasmota/xdrv_09_timers.ino" +const float pi2 = TWO_PI; +const float pi = PI; +const float RAD = DEG_TO_RAD; + +float JulianischesDatum(void) +{ + + int Gregor; + int Jahr = RtcTime.year; + int Monat = RtcTime.month; + int Tag = RtcTime.day_of_month; + + if (Monat <= 2) { + Monat += 12; + Jahr -= 1; + } + Gregor = (Jahr / 400) - (Jahr / 100) + (Jahr / 4); + return 2400000.5f + 365.0f*Jahr - 679004.0f + Gregor + (int)(30.6001f * (Monat +1)) + Tag + 0.5f; +} + +float InPi(float x) +{ + int n = (int)(x / pi2); + x = x - n*pi2; + if (x < 0) x += pi2; + return x; +} + +float eps(float T) +{ + + return RAD * (23.43929111f + (-46.8150f*T - 0.00059f*T*T + 0.001813f*T*T*T)/3600.0f); +} + +float BerechneZeitgleichung(float *DK,float T) +{ + float RA_Mittel = 18.71506921f + 2400.0513369f*T +(2.5862e-5f - 1.72e-9f*T)*T*T; + float M = InPi(pi2 * (0.993133f + 99.997361f*T)); + float L = InPi(pi2 * (0.7859453f + M/pi2 + (6893.0f*sinf(M)+72.0f*sinf(2.0f*M)+6191.2f*T) / 1296.0e3f)); + float e = eps(T); + float RA = atanf(tanf(L)*cosf(e)); + if (RA < 0.0) RA += pi; + if (L > pi) RA += pi; + RA = 24.0*RA/pi2; + *DK = asinf(sinf(e)*sinf(L)); + + RA_Mittel = 24.0f * InPi(pi2*RA_Mittel/24.0f)/pi2; + float dRA = RA_Mittel - RA; + if (dRA < -12.0f) dRA += 24.0f; + if (dRA > 12.0f) dRA -= 24.0f; + dRA = dRA * 1.0027379f; + return dRA; +} + +void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8_t *minute_down) +{ + float JD2000 = 2451545.0f; + float JD = JulianischesDatum(); + float T = (JD - JD2000) / 36525.0f; + float DK; + + + + + + + + float h = SUNRISE_DAWN_ANGLE *RAD; + float B = (((float)Settings.latitude)/1000000) * RAD; + float GeographischeLaenge = ((float)Settings.longitude)/1000000; + + + + float Zeitzone = ((float)Rtc.time_timezone) / 60; + float Zeitgleichung = BerechneZeitgleichung(&DK, T); + float Zeitdifferenz = 12.0f*acosf((sinf(h) - sinf(B)*sinf(DK)) / (cosf(B)*cosf(DK)))/pi; + float AufgangOrtszeit = 12.0f - Zeitdifferenz - Zeitgleichung; + float UntergangOrtszeit = 12.0f + Zeitdifferenz - Zeitgleichung; + float AufgangWeltzeit = AufgangOrtszeit - GeographischeLaenge / 15.0f; + float UntergangWeltzeit = UntergangOrtszeit - GeographischeLaenge / 15.0f; + float Aufgang = AufgangWeltzeit + Zeitzone; + if (Aufgang < 0.0f) { + Aufgang += 24.0f; + } else { + if (Aufgang >= 24.0f) Aufgang -= 24.0f; + } + float Untergang = UntergangWeltzeit + Zeitzone; + if (Untergang < 0.0f) { + Untergang += 24.0f; + } else { + if (Untergang >= 24.0f) Untergang -= 24.0f; + } + int AufgangMinuten = (int)(60.0f*(Aufgang - (int)Aufgang)+0.5f); + int AufgangStunden = (int)Aufgang; + if (AufgangMinuten >= 60.0f) { + AufgangMinuten -= 60.0f; + AufgangStunden++; + } else { + if (AufgangMinuten < 0.0f) { + AufgangMinuten += 60.0f; + AufgangStunden--; + if (AufgangStunden < 0.0f) AufgangStunden += 24.0f; + } + } + int UntergangMinuten = (int)(60.0f*(Untergang - (int)Untergang)+0.5f); + int UntergangStunden = (int)Untergang; + if (UntergangMinuten >= 60.0f) { + UntergangMinuten -= 60.0f; + UntergangStunden++; + } else { + if (UntergangMinuten<0) { + UntergangMinuten += 60.0f; + UntergangStunden--; + if (UntergangStunden < 0.0f) UntergangStunden += 24.0f; + } + } + *hour_up = AufgangStunden; + *minute_up = AufgangMinuten; + *hour_down = UntergangStunden; + *minute_down = UntergangMinuten; +} + +void ApplyTimerOffsets(Timer *duskdawn) +{ + uint8_t hour[2]; + uint8_t minute[2]; + Timer stored = (Timer)*duskdawn; + + + DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]); + uint8_t mode = (duskdawn->mode -1) &1; + duskdawn->time = (hour[mode] *60) + minute[mode]; + + + uint16_t timeBuffer; + if ((uint16_t)stored.time > 719) { + + timeBuffer = (uint16_t)stored.time - 720; + + if (timeBuffer > (uint16_t)duskdawn->time) { + timeBuffer = 1440 - (timeBuffer - (uint16_t)duskdawn->time); + duskdawn->days = duskdawn->days >> 1; + duskdawn->days |= (stored.days << 6); + } else { + timeBuffer = (uint16_t)duskdawn->time - timeBuffer; + } + } else { + + timeBuffer = (uint16_t)duskdawn->time + (uint16_t)stored.time; + + if (timeBuffer > 1440) { + timeBuffer -= 1440; + duskdawn->days = duskdawn->days << 1; + duskdawn->days |= (stored.days >> 6); + } + } + duskdawn->time = timeBuffer; +} + +String GetSun(uint32_t dawn) +{ + char stime[6]; + + uint8_t hour[2]; + uint8_t minute[2]; + + DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]); + dawn &= 1; + snprintf_P(stime, sizeof(stime), PSTR("%02d:%02d"), hour[dawn], minute[dawn]); + return String(stime); +} + +uint16_t SunMinutes(uint32_t dawn) +{ + uint8_t hour[2]; + uint8_t minute[2]; + + DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]); + dawn &= 1; + return (hour[dawn] *60) + minute[dawn]; +} + +#endif + + + +void TimerSetRandomWindow(uint32_t index) +{ + timer_window[index] = 0; + if (Settings.timer[index].window) { + timer_window[index] = (random(0, (Settings.timer[index].window << 1) +1)) - Settings.timer[index].window; + } +} + +void TimerSetRandomWindows(void) +{ + for (uint32_t i = 0; i < MAX_TIMERS; i++) { TimerSetRandomWindow(i); } +} + +void TimerEverySecond(void) +{ + if (RtcTime.valid) { + if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second) { TimerSetRandomWindows(); } + if (Settings.flag3.timers_enable && + (uptime > 60) && (RtcTime.minute != timer_last_minute)) { + timer_last_minute = RtcTime.minute; + int32_t time = (RtcTime.hour *60) + RtcTime.minute; + uint8_t days = 1 << (RtcTime.day_of_week -1); + + for (uint32_t i = 0; i < MAX_TIMERS; i++) { + + Timer xtimer = Settings.timer[i]; +#ifdef USE_SUNRISE + if ((1 == xtimer.mode) || (2 == xtimer.mode)) { + ApplyTimerOffsets(&xtimer); + } +#endif + if (xtimer.arm) { + int32_t set_time = xtimer.time + timer_window[i]; + if (set_time < 0) { + set_time = abs(timer_window[i]); + } + if (set_time > 1439) { + set_time = xtimer.time - abs(timer_window[i]); + } + if (set_time > 1439) { set_time = 1439; } + + DEBUG_DRIVER_LOG(PSTR("TIM: Timer %d, Time %d, Window %d, SetTime %d"), i +1, xtimer.time, timer_window[i], set_time); + + if (time == set_time) { + if (xtimer.days & days) { + Settings.timer[i].arm = xtimer.repeat; +#if defined(USE_RULES) || defined(USE_SCRIPT) + if (POWER_BLINK == xtimer.power) { + Response_P(PSTR("{\"Clock\":{\"Timer\":%d}}"), i +1); + XdrvRulesProcess(); + } else +#endif + if (devices_present) { ExecuteCommandPower(xtimer.device +1, xtimer.power, SRC_TIMER); } + } + } + } + } + } + } +} + +void PrepShowTimer(uint32_t index) +{ + Timer xtimer = Settings.timer[index -1]; + + char days[8] = { 0 }; + for (uint32_t i = 0; i < 7; i++) { + uint8_t mask = 1 << i; + snprintf(days, sizeof(days), "%s%d", days, ((xtimer.days & mask) > 0)); + } + + char soutput[80]; + soutput[0] = '\0'; + if (devices_present) { + snprintf_P(soutput, sizeof(soutput), PSTR(",\"" D_JSON_TIMER_OUTPUT "\":%d"), xtimer.device +1); + } +#ifdef USE_SUNRISE + char sign[2] = { 0 }; + int16_t hour = xtimer.time / 60; + if ((1 == xtimer.mode) || (2 == xtimer.mode)) { + if (hour > 11) { + hour -= 12; + sign[0] = '-'; + } + } + ResponseAppend_P(PSTR("\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_MODE "\":%d,\"" D_JSON_TIMER_TIME "\":\"%s%02d:%02d\",\"" D_JSON_TIMER_WINDOW "\":%d,\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d%s,\"" D_JSON_TIMER_ACTION "\":%d}"), + index, xtimer.arm, xtimer.mode, sign, hour, xtimer.time % 60, xtimer.window, days, xtimer.repeat, soutput, xtimer.power); +#else + ResponseAppend_P(PSTR("\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_WINDOW "\":%d,\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d%s,\"" D_JSON_TIMER_ACTION "\":%d}"), + index, xtimer.arm, xtimer.time / 60, xtimer.time % 60, xtimer.window, days, xtimer.repeat, soutput, xtimer.power); +#endif +} + + + + + +void CmndTimer(void) +{ + uint32_t index = XdrvMailbox.index; + if ((index > 0) && (index <= MAX_TIMERS)) { + uint32_t error = 0; + if (XdrvMailbox.data_len) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= MAX_TIMERS)) { + if (XdrvMailbox.payload == 0) { + Settings.timer[index -1].data = 0; + } else { + Settings.timer[index -1].data = Settings.timer[XdrvMailbox.payload -1].data; + } + } else { + +#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0 + if (devices_present) { +#endif + char dataBufUc[XdrvMailbox.data_len + 1]; + UpperCase(dataBufUc, XdrvMailbox.data); + StaticJsonBuffer<256> jsonBuffer; + JsonObject& root = jsonBuffer.parseObject(dataBufUc); + if (!root.success()) { + Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_INVALID_JSON "\"}"), index); + error = 1; + } + else { + char parm_uc[10]; + index--; + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_ARM))].success()) { + Settings.timer[index].arm = (root[parm_uc] != 0); + } +#ifdef USE_SUNRISE + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_MODE))].success()) { + Settings.timer[index].mode = (uint8_t)root[parm_uc] & 0x03; + } +#endif + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_TIME))].success()) { + uint16_t itime = 0; + int8_t value = 0; + uint8_t sign = 0; + char time_str[10]; + + strlcpy(time_str, root[parm_uc], sizeof(time_str)); + const char *substr = strtok(time_str, ":"); + if (substr != nullptr) { + if (strchr(substr, '-')) { + sign = 1; + substr++; + } + value = atoi(substr); + if (sign) { value += 12; } + if (value > 23) { value = 23; } + itime = value * 60; + substr = strtok(nullptr, ":"); + if (substr != nullptr) { + value = atoi(substr); + if (value < 0) { value = 0; } + if (value > 59) { value = 59; } + itime += value; + } + } + Settings.timer[index].time = itime; + } + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_WINDOW))].success()) { + Settings.timer[index].window = (uint8_t)root[parm_uc] & 0x0F; + TimerSetRandomWindow(index); + } + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_DAYS))].success()) { + + Settings.timer[index].days = 0; + const char *tday = root[parm_uc]; + uint8_t i = 0; + char ch = *tday++; + while ((ch != '\0') && (i < 7)) { + if (ch == '-') { ch = '0'; } + uint8_t mask = 1 << i++; + Settings.timer[index].days |= (ch == '0') ? 0 : mask; + ch = *tday++; + } + } + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_REPEAT))].success()) { + Settings.timer[index].repeat = (root[parm_uc] != 0); + } + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_OUTPUT))].success()) { + uint8_t device = ((uint8_t)root[parm_uc] -1) & 0x0F; + Settings.timer[index].device = (device < devices_present) ? device : 0; + } + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_ACTION))].success()) { + uint8_t action = (uint8_t)root[parm_uc] & 0x03; + Settings.timer[index].power = (devices_present) ? action : 3; + } + + index++; + } + +#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0 + } else { + Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_TIMER_NO_DEVICE "\"}"), index); + error = 1; + } +#endif + } + } + if (!error) { + Response_P(PSTR("{")); + PrepShowTimer(index); + ResponseJsonEnd(); + } + } +} + +void CmndTimers(void) +{ + if (XdrvMailbox.data_len) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.flag3.timers_enable = XdrvMailbox.payload; + } + if (XdrvMailbox.payload == 2) { + Settings.flag3.timers_enable = !Settings.flag3.timers_enable; + } + } + + ResponseCmndStateText(Settings.flag3.timers_enable); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, XdrvMailbox.command); + + uint32_t jsflg = 0; + uint32_t lines = 1; + for (uint32_t i = 0; i < MAX_TIMERS; i++) { + if (!jsflg) { + Response_P(PSTR("{\"" D_CMND_TIMERS "%d\":{"), lines++); + } else { + ResponseAppend_P(PSTR(",")); + } + jsflg++; + PrepShowTimer(i +1); + if (jsflg > 3) { + ResponseJsonEndEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_TIMERS)); + jsflg = 0; + } + } + mqtt_data[0] = '\0'; +} + +#ifdef USE_SUNRISE +void CmndLongitude(void) +{ + if (XdrvMailbox.data_len) { + Settings.longitude = (int)(CharToFloat(XdrvMailbox.data) *1000000); + } + ResponseCmndFloat((float)(Settings.longitude) /1000000, 6); +} + +void CmndLatitude(void) +{ + if (XdrvMailbox.data_len) { + Settings.latitude = (int)(CharToFloat(XdrvMailbox.data) *1000000); + } + ResponseCmndFloat((float)(Settings.latitude) /1000000, 6); +} +#endif + + + + + +#ifdef USE_WEBSERVER +#ifdef USE_TIMERS_WEB + +#define WEB_HANDLE_TIMER "tm" + +const char S_CONFIGURE_TIMER[] PROGMEM = D_CONFIGURE_TIMER; + +const char HTTP_BTN_MENU_TIMER[] PROGMEM = + "

"; + +const char HTTP_TIMER_SCRIPT1[] PROGMEM = + "var pt=[],ct=99;" + "function ce(i,q){" + "var o=document.createElement('option');" + "o.textContent=i;" + "q.appendChild(o);" + "}"; +#ifdef USE_SUNRISE +const char HTTP_TIMER_SCRIPT2[] PROGMEM = + "function gt(){" + "var m,p,q;" + "m=qs('input[name=\"rd\"]:checked').value;" + "p=pt[ct]&0x7FF;" + "if(m==0){" + "so(0);" + "q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" + "q=p%%60;if(q<10){q='0'+q;}qs('#mi').value=q;" + "}" + "if((m==1)||(m==2)){" + "so(1);" + "q=Math.floor(p/60);" + "if(q>=12){q-=12;qs('#dr').selectedIndex=1;}" + "else{qs('#dr').selectedIndex=0;}" + "if(q<10){q='0'+q;}qs('#ho').value=q;" + "q=p%%60;if(q<10){q='0'+q;}qs('#mi').value=q;" + "}" + "}" + "function so(b){" + "o=qs('#ho');" + "e=o.childElementCount;" + "if(b==1){" + "qs('#dr').style.visibility='';" + "if(e>12){for(i=12;i<=23;i++){o.removeChild(o.lastElementChild);}}" + "}else{" + "qs('#dr').style.visibility='hidden';" + "if(e<23){for(i=12;i<=23;i++){ce(i,o);}}" + "}" + "}"; +#endif +const char HTTP_TIMER_SCRIPT3[] PROGMEM = + "function st(){" + "var i,l,m,n,p,s;" + "m=0;s=0;" + "n=1<<31;if(eb('a0').checked){s|=n;}" + "n=1<<15;if(eb('r0').checked){s|=n;}" + "for(i=0;i<7;i++){n=1<<(16+i);if(eb('w'+i).checked){s|=n;}}" +#ifdef USE_SUNRISE + "m=qs('input[name=\"rd\"]:checked').value;" + "s|=(qs('input[name=\"rd\"]:checked').value<<29);" +#endif + "if(%d>0){" + "i=qs('#d1').selectedIndex;if(i>=0){s|=(i<<23);}" + "s|=(qs('#p1').selectedIndex<<27);" + "}else{" + "s|=3<<27;" + "}" + "l=((qs('#ho').selectedIndex*60)+qs('#mi').selectedIndex)&0x7FF;" + "if(m==0){s|=l;}" +#ifdef USE_SUNRISE + "if((m==1)||(m==2)){" + "if(qs('#dr').selectedIndex>0){if(l>0){l+=720;}}" + "s|=l&0x7FF;" + "}" +#endif + "s|=((qs('#mw').selectedIndex)&0x0F)<<11;" + "pt[ct]=s;" + "eb('t0').value=pt.join();" + "}"; +const char HTTP_TIMER_SCRIPT4[] PROGMEM = + "function ot(t,e){" + "var i,n,o,p,q,s;" + "if(ct<99){st();}" + "ct=t;" + "o=document.getElementsByClassName('tl');" + "for(i=0;i>29)&3;eb('b'+p).checked=1;" + "gt();" +#else + "p=s&0x7FF;" + "q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" + "q=p%%60;if(q<10){q='0'+q;}qs('#mi').value=q;" +#endif + "q=(s>>11)&0xF;if(q<10){q='0'+q;}qs('#mw').value=q;" + "for(i=0;i<7;i++){p=(s>>(16+i))&1;eb('w'+i).checked=p;}" + "if(%d>0){" + "p=(s>>23)&0xF;qs('#d1').value=p+1;" + "p=(s>>27)&3;qs('#p1').selectedIndex=p;" + "}" + "p=(s>>15)&1;eb('r0').checked=p;" + "p=(s>>31)&1;eb('a0').checked=p;" + "}"; +const char HTTP_TIMER_SCRIPT5[] PROGMEM = + "function it(){" + "var b,i,o,s;" + "pt=eb('t0').value.split(',').map(Number);" + "s='';" + "for(i=0;i<%d;i++){" + "b='';" + "if(0==i){b=\" id='dP'\";}" + "s+=\"\"" + "}" + "eb('bt').innerHTML=s;" + "if(%d>0){" + "eb('oa').innerHTML=\"" D_TIMER_OUTPUT " " D_TIMER_ACTION " \";" + "o=qs('#p1');ce('" D_OFF "',o);ce('" D_ON "',o);ce('" D_TOGGLE "',o);" +#if defined(USE_RULES) || defined(USE_SCRIPT) + "ce('" D_RULE "',o);" +#else + "ce('" D_BLINK "',o);" +#endif + "}else{" + "eb('oa').innerHTML=\"" D_TIMER_ACTION " " D_RULE "\";" + "}"; +const char HTTP_TIMER_SCRIPT6[] PROGMEM = +#ifdef USE_SUNRISE + "o=qs('#dr');ce('+',o);ce('-',o);" +#endif + "o=qs('#ho');for(i=0;i<=23;i++){ce((i<10)?('0'+i):i,o);}" + "o=qs('#mi');for(i=0;i<=59;i++){ce((i<10)?('0'+i):i,o);}" + "o=qs('#mw');for(i=0;i<=15;i++){ce((i<10)?('0'+i):i,o);}" + "o=qs('#d1');for(i=0;i<%d;i++){ce(i+1,o);}" + "var a='" D_DAY3LIST "';" + "s='';for(i=0;i<7;i++){s+=\"\"+a.substring(i*3,(i*3)+3)+\" \"}" + "eb('ds').innerHTML=s;" + "eb('dP').click();" + "}" + "wl(it);"; +const char HTTP_TIMER_STYLE[] PROGMEM = + ".tl{float:left;border-radius:0;border:1px solid #%06x;padding:1px;width:6.25%%;}"; +const char HTTP_FORM_TIMER1[] PROGMEM = + "
" + " " D_TIMER_PARAMETERS " " + "
" + "
" D_TIMER_ENABLE "


" + "



" + "

" + "
" + "" D_TIMER_ARM " " + "" D_TIMER_REPEAT "" + "

" + "
"; +#ifdef USE_SUNRISE +const char HTTP_FORM_TIMER3[] PROGMEM = + "
" + "" D_TIMER_TIME "
" + "" D_SUNRISE " (%s)
" + "" D_SUNSET " (%s)
" + "
" + "

" + "" + " "; +#else +const char HTTP_FORM_TIMER3[] PROGMEM = + "" D_TIMER_TIME " "; +#endif +const char HTTP_FORM_TIMER4[] PROGMEM = + "" + " " D_HOUR_MINUTE_SEPARATOR " " + "" + " +/- " + "" + "

" + "
"; + +void HandleTimerConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_TIMER); + + if (WebServer->hasArg("save")) { + TimerSaveSettings(); + HandleConfiguration(); + return; + } + + WSContentStart_P(S_CONFIGURE_TIMER); + WSContentSend_P(HTTP_TIMER_SCRIPT1); +#ifdef USE_SUNRISE + WSContentSend_P(HTTP_TIMER_SCRIPT2); +#endif + WSContentSend_P(HTTP_TIMER_SCRIPT3, devices_present); + WSContentSend_P(HTTP_TIMER_SCRIPT4, WebColor(COL_TIMER_TAB_BACKGROUND), WebColor(COL_TIMER_TAB_TEXT), WebColor(COL_FORM), WebColor(COL_TEXT), devices_present); + WSContentSend_P(HTTP_TIMER_SCRIPT5, MAX_TIMERS, devices_present); + WSContentSend_P(HTTP_TIMER_SCRIPT6, devices_present); + WSContentSendStyle_P(HTTP_TIMER_STYLE, WebColor(COL_FORM)); + WSContentSend_P(HTTP_FORM_TIMER1, (Settings.flag3.timers_enable) ? " checked" : ""); + for (uint32_t i = 0; i < MAX_TIMERS; i++) { + WSContentSend_P(PSTR("%s%u"), (i > 0) ? "," : "", Settings.timer[i].data); + } + WSContentSend_P(HTTP_FORM_TIMER2); +#ifdef USE_SUNRISE + WSContentSend_P(HTTP_FORM_TIMER3, 100 + (strlen(D_SUNSET) *12), GetSun(0).c_str(), GetSun(1).c_str()); +#else + WSContentSend_P(HTTP_FORM_TIMER3); +#endif + WSContentSend_P(HTTP_FORM_TIMER4); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void TimerSaveSettings(void) +{ + char tmp[MAX_TIMERS *12]; + char message[LOGSZ]; + Timer timer; + + Settings.flag3.timers_enable = WebServer->hasArg("e0"); + WebGetArg("t0", tmp, sizeof(tmp)); + char *p = tmp; + snprintf_P(message, sizeof(message), PSTR(D_LOG_MQTT D_CMND_TIMERS " %d"), Settings.flag3.timers_enable); + for (uint32_t i = 0; i < MAX_TIMERS; i++) { + timer.data = strtol(p, &p, 10); + p++; + if (timer.time < 1440) { + bool flag = (timer.window != Settings.timer[i].window); + Settings.timer[i].data = timer.data; + if (flag) TimerSetRandomWindow(i); + } + snprintf_P(message, sizeof(message), PSTR("%s,0x%08X"), message, Settings.timer[i].data); + } + AddLog_P(LOG_LEVEL_DEBUG, message); +} +#endif +#endif + + + + + +bool Xdrv09(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_PRE_INIT: + TimerSetRandomWindows(); + break; +#ifdef USE_WEBSERVER +#ifdef USE_TIMERS_WEB + case FUNC_WEB_ADD_BUTTON: +#if defined(USE_RULES) || defined(USE_SCRIPT) + WSContentSend_P(HTTP_BTN_MENU_TIMER); +#else + if (devices_present) { WSContentSend_P(HTTP_BTN_MENU_TIMER); } +#endif + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/" WEB_HANDLE_TIMER, HandleTimerConfiguration); + break; +#endif +#endif + case FUNC_EVERY_SECOND: + TimerEverySecond(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kTimerCommands, TimerCommand); + break; + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +#ifdef USE_RULES +#ifndef USE_SCRIPT +# 67 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +#define XDRV_10 10 + +#define D_CMND_RULE "Rule" +#define D_CMND_RULETIMER "RuleTimer" +#define D_CMND_EVENT "Event" +#define D_CMND_VAR "Var" +#define D_CMND_MEM "Mem" +#define D_CMND_ADD "Add" +#define D_CMND_SUB "Sub" +#define D_CMND_MULT "Mult" +#define D_CMND_SCALE "Scale" +#define D_CMND_CALC_RESOLUTION "CalcRes" +#define D_CMND_SUBSCRIBE "Subscribe" +#define D_CMND_UNSUBSCRIBE "Unsubscribe" +#define D_CMND_IF "If" + +#define D_JSON_INITIATED "Initiated" + +#define COMPARE_OPERATOR_NONE -1 +#define COMPARE_OPERATOR_EQUAL 0 +#define COMPARE_OPERATOR_BIGGER 1 +#define COMPARE_OPERATOR_SMALLER 2 +#define COMPARE_OPERATOR_EXACT_DIVISION 3 +#define COMPARE_OPERATOR_NUMBER_EQUAL 4 +#define COMPARE_OPERATOR_NOT_EQUAL 5 +#define COMPARE_OPERATOR_BIGGER_EQUAL 6 +#define COMPARE_OPERATOR_SMALLER_EQUAL 7 +#define MAXIMUM_COMPARE_OPERATOR COMPARE_OPERATOR_SMALLER_EQUAL +const char kCompareOperators[] PROGMEM = "=\0>\0<\0|\0==!=>=<="; + +#ifdef USE_EXPRESSION + #include + + const char kExpressionOperators[] PROGMEM = "+-*/%^\0"; + #define EXPRESSION_OPERATOR_ADD 0 + #define EXPRESSION_OPERATOR_SUBTRACT 1 + #define EXPRESSION_OPERATOR_MULTIPLY 2 + #define EXPRESSION_OPERATOR_DIVIDEDBY 3 + #define EXPRESSION_OPERATOR_MODULO 4 + #define EXPRESSION_OPERATOR_POWER 5 + + const uint8_t kExpressionOperatorsPriorities[] PROGMEM = {1, 1, 2, 2, 3, 4}; + #define MAX_EXPRESSION_OPERATOR_PRIORITY 4 + + + #define LOGIC_OPERATOR_AND 1 + #define LOGIC_OPERATOR_OR 2 + + #define IF_BLOCK_INVALID -1 + #define IF_BLOCK_ANY 0 + #define IF_BLOCK_ELSEIF 1 + #define IF_BLOCK_ELSE 2 + #define IF_BLOCK_ENDIF 3 +#endif + +const char kRulesCommands[] PROGMEM = "|" + D_CMND_RULE "|" D_CMND_RULETIMER "|" D_CMND_EVENT "|" D_CMND_VAR "|" D_CMND_MEM "|" + D_CMND_ADD "|" D_CMND_SUB "|" D_CMND_MULT "|" D_CMND_SCALE "|" D_CMND_CALC_RESOLUTION +#ifdef SUPPORT_MQTT_EVENT + "|" D_CMND_SUBSCRIBE "|" D_CMND_UNSUBSCRIBE +#endif +#ifdef SUPPORT_IF_STATEMENT + "|" D_CMND_IF +#endif + ; + +void (* const RulesCommand[])(void) PROGMEM = { + &CmndRule, &CmndRuleTimer, &CmndEvent, &CmndVariable, &CmndMemory, + &CmndAddition, &CmndSubtract, &CmndMultiply, &CmndScale, &CmndCalcResolution +#ifdef SUPPORT_MQTT_EVENT + , &CmndSubscribe, &CmndUnsubscribe +#endif +#ifdef SUPPORT_IF_STATEMENT + , &CmndIf +#endif + }; + +#ifdef SUPPORT_MQTT_EVENT + #include + typedef struct { + String Event; + String Topic; + String Key; + } MQTT_Subscription; + LinkedList subscriptions; +#endif + +struct RULES { + String event_value; + unsigned long timer[MAX_RULE_TIMERS] = { 0 }; + uint32_t triggers[MAX_RULE_SETS] = { 0 }; + uint8_t trigger_count[MAX_RULE_SETS] = { 0 }; + + long new_power = -1; + long old_power = -1; + long old_dimm = -1; + + uint16_t last_minute = 60; + uint16_t vars_event = 0; + uint8_t mems_event = 0; + bool teleperiod = false; + + char event_data[100]; +} Rules; + +char rules_vars[MAX_RULE_VARS][33] = {{ 0 }}; + +#if (MAX_RULE_VARS>16) +#error MAX_RULE_VARS is bigger than 16 +#endif +#if (MAX_RULE_MEMS>16) +#error MAX_RULE_MEMS is bigger than 16 +#endif + + + +bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) +{ + + + + + bool match = false; + char stemp[10]; + + + int pos = rule.indexOf('#'); + if (pos == -1) { return false; } + + String rule_task = rule.substring(0, pos); + if (Rules.teleperiod) { + int ppos = rule_task.indexOf("TELE-"); + if (ppos == -1) { return false; } + rule_task = rule.substring(5, pos); + } + + String rule_expr = rule.substring(pos +1); + String rule_name, rule_param; + int8_t compareOperator = parseCompareExpression(rule_expr, rule_name, rule_param); + + char rule_svalue[80] = { 0 }; + float rule_value = 0; + if (compareOperator != COMPARE_OPERATOR_NONE) { + for (uint32_t i = 0; i < MAX_RULE_VARS; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("%%VAR%d%%"), i +1); + if (rule_param.startsWith(stemp)) { + rule_param = rules_vars[i]; + break; + } + } + for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1); + if (rule_param.startsWith(stemp)) { + rule_param = SettingsText(SET_MEM1 + i); + break; + } + } + snprintf_P(stemp, sizeof(stemp), PSTR("%%TIME%%")); + if (rule_param.startsWith(stemp)) { + rule_param = String(MinutesPastMidnight()); + } + snprintf_P(stemp, sizeof(stemp), PSTR("%%UPTIME%%")); + if (rule_param.startsWith(stemp)) { + rule_param = String(MinutesUptime()); + } + snprintf_P(stemp, sizeof(stemp), PSTR("%%TIMESTAMP%%")); + if (rule_param.startsWith(stemp)) { + rule_param = GetDateAndTime(DT_LOCAL).c_str(); + } +#if defined(USE_TIMERS) && defined(USE_SUNRISE) + snprintf_P(stemp, sizeof(stemp), PSTR("%%SUNRISE%%")); + if (rule_param.startsWith(stemp)) { + rule_param = String(SunMinutes(0)); + } + snprintf_P(stemp, sizeof(stemp), PSTR("%%SUNSET%%")); + if (rule_param.startsWith(stemp)) { + rule_param = String(SunMinutes(1)); + } +#endif + rule_param.toUpperCase(); + strlcpy(rule_svalue, rule_param.c_str(), sizeof(rule_svalue)); + + int temp_value = GetStateNumber(rule_svalue); + if (temp_value > -1) { + rule_value = temp_value; + } else { + rule_value = CharToFloat((char*)rule_svalue); + } + } + + + int rule_name_idx = 0; + if ((pos = rule_name.indexOf("[")) > 0) { + rule_name_idx = rule_name.substring(pos +1).toInt(); + if ((rule_name_idx < 1) || (rule_name_idx > 6)) { + rule_name_idx = 1; + } + rule_name = rule_name.substring(0, pos); + } + + StaticJsonBuffer<1024> jsonBuf; + JsonObject &root = jsonBuf.parseObject(event); + if (!root.success()) { return false; } + if (!root[rule_task].success()) { return false; } + + JsonObject &obj1 = root[rule_task]; + JsonObject *obj = &obj1; + String subtype; + uint32_t i = 0; + while ((pos = rule_name.indexOf("#")) > 0) { + subtype = rule_name.substring(0, pos); + if (!(*obj)[subtype].success()) { return false; } + JsonObject &obj2 = (*obj)[subtype]; + obj = &obj2; + rule_name = rule_name.substring(pos +1); + if (i++ > 10) { return false; } + } + if (!(*obj)[rule_name].success()) { return false; } + const char* str_value; + if (rule_name_idx) { + str_value = (*obj)[rule_name][rule_name_idx -1]; + } else { + str_value = (*obj)[rule_name]; + } + + + + + Rules.event_value = str_value; + + + float value = 0; + if (str_value) { + value = CharToFloat((char*)str_value); + int int_value = int(value); + int int_rule_value = int(rule_value); + switch (compareOperator) { + case COMPARE_OPERATOR_EXACT_DIVISION: + match = (int_rule_value && (int_value % int_rule_value) == 0); + break; + case COMPARE_OPERATOR_EQUAL: + match = (!strcasecmp(str_value, rule_svalue)); + break; + case COMPARE_OPERATOR_BIGGER: + match = (value > rule_value); + break; + case COMPARE_OPERATOR_SMALLER: + match = (value < rule_value); + break; + case COMPARE_OPERATOR_NUMBER_EQUAL: + match = (value == rule_value); + break; + case COMPARE_OPERATOR_NOT_EQUAL: + match = (value != rule_value); + break; + case COMPARE_OPERATOR_BIGGER_EQUAL: + match = (value >= rule_value); + break; + case COMPARE_OPERATOR_SMALLER_EQUAL: + match = (value <= rule_value); + break; + default: + match = true; + } + } else match = true; + + if (bitRead(Settings.rule_once, rule_set)) { + if (match) { + if (!bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set])) { + bitSet(Rules.triggers[rule_set], Rules.trigger_count[rule_set]); + } else { + match = false; + } + } else { + bitClear(Rules.triggers[rule_set], Rules.trigger_count[rule_set]); + } + } + + return match; +} +# 363 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +int8_t parseCompareExpression(String &expr, String &leftExpr, String &rightExpr) +{ + char compare_operator[3]; + int8_t compare = COMPARE_OPERATOR_NONE; + leftExpr = expr; + int position; + for (int8_t i = MAXIMUM_COMPARE_OPERATOR; i >= 0; i--) { + snprintf_P(compare_operator, sizeof(compare_operator), kCompareOperators + (i *2)); + if ((position = expr.indexOf(compare_operator)) > 0) { + compare = i; + leftExpr = expr.substring(0, position); + leftExpr.trim(); + rightExpr = expr.substring(position + strlen(compare_operator)); + rightExpr.trim(); + break; + } + } + return compare; +} + +void RulesVarReplace(String &commands, const String &sfind, const String &replace) +{ + + + + char *find = (char*)sfind.c_str(); + uint32_t flen = strlen(find); + + String ucommand = commands; + ucommand.toUpperCase(); + char *read_from = (char*)ucommand.c_str(); + char *write_to = (char*)commands.c_str(); + char *found_at; + while ((found_at = strstr(read_from, find)) != nullptr) { + write_to += (found_at - read_from); + memmove_P(write_to, find, flen); + write_to += flen; + read_from = found_at + flen; + } + + commands.replace(find, replace); +} + + + +bool RuleSetProcess(uint8_t rule_set, String &event_saved) +{ + bool serviced = false; + char stemp[10]; + + delay(0); + + + + String rules = Settings.rules[rule_set]; + + Rules.trigger_count[rule_set] = 0; + int plen = 0; + int plen2 = 0; + bool stop_all_rules = false; + while (true) { + rules = rules.substring(plen); + rules.trim(); + if (!rules.length()) { return serviced; } + + String rule = rules; + rule.toUpperCase(); + if (!rule.startsWith("ON ")) { return serviced; } + + int pevt = rule.indexOf(" DO "); + if (pevt == -1) { return serviced; } + String event_trigger = rule.substring(3, pevt); + + plen = rule.indexOf(" ENDON"); + plen2 = rule.indexOf(" BREAK"); + if ((plen == -1) && (plen2 == -1)) { return serviced; } + + if (plen == -1) { plen = 9999; } + if (plen2 == -1) { plen2 = 9999; } + plen = tmin(plen, plen2); + + String commands = rules.substring(pevt +4, plen); + Rules.event_value = ""; + String event = event_saved; + + + + if (RulesRuleMatch(rule_set, event, event_trigger)) { + if (plen == plen2) { stop_all_rules = true; } + commands.trim(); + String ucommand = commands; + ucommand.toUpperCase(); + + + + if ((ucommand.indexOf("IF ") == -1) && + (ucommand.indexOf("EVENT ") != -1) && + (ucommand.indexOf("BACKLOG ") == -1)) { + commands = "backlog " + commands; + } + + RulesVarReplace(commands, F("%VALUE%"), Rules.event_value); + for (uint32_t i = 0; i < MAX_RULE_VARS; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("%%VAR%d%%"), i +1); + RulesVarReplace(commands, stemp, rules_vars[i]); + } + for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1); + RulesVarReplace(commands, stemp, SettingsText(SET_MEM1 +i)); + } + RulesVarReplace(commands, F("%TIME%"), String(MinutesPastMidnight())); + RulesVarReplace(commands, F("%UPTIME%"), String(MinutesUptime())); + RulesVarReplace(commands, F("%TIMESTAMP%"), GetDateAndTime(DT_LOCAL)); + RulesVarReplace(commands, F("%TOPIC%"), SettingsText(SET_MQTT_TOPIC)); +#if defined(USE_TIMERS) && defined(USE_SUNRISE) + RulesVarReplace(commands, F("%SUNRISE%"), String(SunMinutes(0))); + RulesVarReplace(commands, F("%SUNSET%"), String(SunMinutes(1))); +#endif + + char command[commands.length() +1]; + strlcpy(command, commands.c_str(), sizeof(command)); + + AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: %s performs \"%s\""), event_trigger.c_str(), command); + + + +#ifdef SUPPORT_IF_STATEMENT + char *pCmd = command; + RulesPreprocessCommand(pCmd); +#endif + ExecuteCommand(command, SRC_RULE); + serviced = true; + if (stop_all_rules) { return serviced; } + } + plen += 6; + Rules.trigger_count[rule_set]++; + } + return serviced; +} + + + +bool RulesProcessEvent(char *json_event) +{ + bool serviced = false; + +#ifdef USE_DEBUG_DRIVER + ShowFreeMem(PSTR("RulesProcessEvent")); +#endif + + String event_saved = json_event; + + + + char *p = strchr(json_event, ':'); + if ((p != NULL) && !(strchr(++p, ':'))) { + event_saved.replace(F(":"), F(":{\"Data\":")); + event_saved += F("}"); + + } + event_saved.toUpperCase(); + + + + for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { + if (strlen(Settings.rules[i]) && bitRead(Settings.rule_enabled, i)) { + if (RuleSetProcess(i, event_saved)) { serviced = true; } + } + } + return serviced; +} + +bool RulesProcess(void) +{ + return RulesProcessEvent(mqtt_data); +} + +void RulesInit(void) +{ + rules_flag.data = 0; + for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { + if (Settings.rules[i][0] == '\0') { + bitWrite(Settings.rule_enabled, i, 0); + bitWrite(Settings.rule_once, i, 0); + } + } + Rules.teleperiod = false; +} + +void RulesEvery50ms(void) +{ + if (Settings.rule_enabled) { + char json_event[120]; + + if (-1 == Rules.new_power) { Rules.new_power = power; } + if (Rules.new_power != Rules.old_power) { + if (Rules.old_power != -1) { + for (uint32_t i = 0; i < devices_present; i++) { + uint8_t new_state = (Rules.new_power >> i) &1; + if (new_state != ((Rules.old_power >> i) &1)) { + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Power%d\":{\"State\":%d}}"), i +1, new_state); + RulesProcessEvent(json_event); + } + } + } else { + + for (uint32_t i = 0; i < devices_present; i++) { + uint8_t new_state = (Rules.new_power >> i) &1; + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Power%d\":{\"Boot\":%d}}"), i +1, new_state); + RulesProcessEvent(json_event); + } + + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { +#ifdef USE_TM1638 + if ((pin[GPIO_SWT1 +i] < 99) || ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99))) { +#else + if (pin[GPIO_SWT1 +i] < 99) { +#endif + snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (SwitchState(i))); + RulesProcessEvent(json_event); + } + } + } + Rules.old_power = Rules.new_power; + } + else if (Rules.old_dimm != Settings.light_dimmer) { + if (Rules.old_dimm != -1) { + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Dimmer\":{\"State\":%d}}"), Settings.light_dimmer); + } else { + + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Dimmer\":{\"Boot\":%d}}"), Settings.light_dimmer); + } + RulesProcessEvent(json_event); + Rules.old_dimm = Settings.light_dimmer; + } + else if (Rules.event_data[0]) { + char *event; + char *parameter; + event = strtok_r(Rules.event_data, "=", ¶meter); + if (event) { + event = Trim(event); + if (parameter) { + parameter = Trim(parameter); + } else { + parameter = event + strlen(event); + } + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Event\":{\"%s\":\"%s\"}}"), event, parameter); + Rules.event_data[0] ='\0'; + RulesProcessEvent(json_event); + } else { + Rules.event_data[0] ='\0'; + } + } + else if (Rules.vars_event || Rules.mems_event){ + if (Rules.vars_event) { + for (uint32_t i = 0; i < MAX_RULE_VARS; i++) { + if (bitRead(Rules.vars_event, i)) { + bitClear(Rules.vars_event, i); + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Var%d\":{\"State\":%s}}"), i+1, rules_vars[i]); + RulesProcessEvent(json_event); + break; + } + } + } + if (Rules.mems_event) { + for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) { + if (bitRead(Rules.mems_event, i)) { + bitClear(Rules.mems_event, i); + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Mem%d\":{\"State\":%s}}"), i+1, SettingsText(SET_MEM1 +i)); + RulesProcessEvent(json_event); + break; + } + } + } + } + else if (rules_flag.data) { + uint16_t mask = 1; + for (uint32_t i = 0; i < MAX_RULES_FLAG; i++) { + if (rules_flag.data & mask) { + rules_flag.data ^= mask; + json_event[0] = '\0'; + switch (i) { + case 0: strncpy_P(json_event, PSTR("{\"System\":{\"Boot\":1}}"), sizeof(json_event)); break; + case 1: snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Initialized\":%d}}"), MinutesPastMidnight()); break; + case 2: snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Set\":%d}}"), MinutesPastMidnight()); break; + case 3: strncpy_P(json_event, PSTR("{\"MQTT\":{\"Connected\":1}}"), sizeof(json_event)); break; + case 4: strncpy_P(json_event, PSTR("{\"MQTT\":{\"Disconnected\":1}}"), sizeof(json_event)); break; + case 5: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Connected\":1}}"), sizeof(json_event)); break; + case 6: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Disconnected\":1}}"), sizeof(json_event)); break; + case 7: strncpy_P(json_event, PSTR("{\"HTTP\":{\"Initialized\":1}}"), sizeof(json_event)); break; +#ifdef USE_SHUTTER + case 8: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moved\":1}}"), sizeof(json_event)); break; + case 9: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moving\":1}}"), sizeof(json_event)); break; +#endif + } + if (json_event[0]) { + RulesProcessEvent(json_event); + break; + } + } + mask <<= 1; + } + } + } +} + +uint8_t rules_xsns_index = 0; + +void RulesEvery100ms(void) +{ + if (Settings.rule_enabled && (uptime > 4)) { + mqtt_data[0] = '\0'; + int tele_period_save = tele_period; + tele_period = 2; + XsnsNextCall(FUNC_JSON_APPEND, rules_xsns_index); + tele_period = tele_period_save; + if (strlen(mqtt_data)) { + mqtt_data[0] = '{'; + ResponseJsonEnd(); + RulesProcess(); + } + } +} + +void RulesEverySecond(void) +{ + if (Settings.rule_enabled) { + char json_event[120]; + + if (RtcTime.valid) { + if ((uptime > 60) && (RtcTime.minute != Rules.last_minute)) { + Rules.last_minute = RtcTime.minute; + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Minute\":%d}}"), MinutesPastMidnight()); + RulesProcessEvent(json_event); + } + } + for (uint32_t i = 0; i < MAX_RULE_TIMERS; i++) { + if (Rules.timer[i] != 0L) { + if (TimeReached(Rules.timer[i])) { + Rules.timer[i] = 0L; + snprintf_P(json_event, sizeof(json_event), PSTR("{\"Rules\":{\"Timer\":%d}}"), i +1); + RulesProcessEvent(json_event); + } + } + } + } +} + +void RulesSaveBeforeRestart(void) +{ + if (Settings.rule_enabled) { + char json_event[32]; + + strncpy_P(json_event, PSTR("{\"System\":{\"Save\":1}}"), sizeof(json_event)); + RulesProcessEvent(json_event); + } +} + +void RulesSetPower(void) +{ + Rules.new_power = XdrvMailbox.index; +} + +void RulesTeleperiod(void) +{ + Rules.teleperiod = true; + RulesProcess(); + Rules.teleperiod = false; +} + +#ifdef SUPPORT_MQTT_EVENT +# 744 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool RulesMqttData(void) +{ + if (XdrvMailbox.data_len < 1 || XdrvMailbox.data_len > 256) { + return false; + } + bool serviced = false; + String sTopic = XdrvMailbox.topic; + String sData = XdrvMailbox.data; + + MQTT_Subscription event_item; + + for (uint32_t index = 0; index < subscriptions.size(); index++) { + event_item = subscriptions.get(index); + + + if (sTopic.startsWith(event_item.Topic)) { + + serviced = true; + String value; + if (event_item.Key.length() == 0) { + value = sData; + } else { + StaticJsonBuffer<500> jsonBuf; + JsonObject& jsonData = jsonBuf.parseObject(sData); + String key1 = event_item.Key; + String key2; + if (!jsonData.success()) break; + int dot; + if ((dot = key1.indexOf('.')) > 0) { + key2 = key1.substring(dot+1); + key1 = key1.substring(0, dot); + if (!jsonData[key1][key2].success()) break; + value = (const char *)jsonData[key1][key2]; + } else { + if (!jsonData[key1].success()) break; + value = (const char *)jsonData[key1]; + } + } + value.trim(); + + snprintf_P(Rules.event_data, sizeof(Rules.event_data), PSTR("%s=%s"), event_item.Event.c_str(), value.c_str()); + } + } + return serviced; +} +# 806 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +void CmndSubscribe(void) +{ + MQTT_Subscription subscription_item; + String events; + if (XdrvMailbox.data_len > 0) { + char parameters[XdrvMailbox.data_len+1]; + memcpy(parameters, XdrvMailbox.data, XdrvMailbox.data_len); + parameters[XdrvMailbox.data_len] = '\0'; + String event_name, topic, key; + + char * pos = strtok(parameters, ","); + if (pos) { + event_name = Trim(pos); + pos = strtok(nullptr, ","); + if (pos) { + topic = Trim(pos); + pos = strtok(nullptr, ","); + if (pos) { + key = Trim(pos); + } + } + } + + event_name.toUpperCase(); + if (event_name.length() > 0 && topic.length() > 0) { + + for (uint32_t index=0; index < subscriptions.size(); index++) { + if (subscriptions.get(index).Event.equals(event_name)) { + + String stopic = subscriptions.get(index).Topic + "/#"; + MqttUnsubscribe(stopic.c_str()); + subscriptions.remove(index); + break; + } + } + + if (!topic.endsWith("#")) { + if (topic.endsWith("/")) { + topic.concat("#"); + } else { + topic.concat("/#"); + } + } + + + subscription_item.Event = event_name; + subscription_item.Topic = topic.substring(0, topic.length() - 2); + subscription_item.Key = key; + subscriptions.add(subscription_item); + + MqttSubscribe(topic.c_str()); + events.concat(event_name + "," + topic + + (key.length()>0 ? "," : "") + + key); + } else { + events = D_JSON_WRONG_PARAMETERS; + } + } else { + + for (uint32_t index=0; index < subscriptions.size(); index++) { + subscription_item = subscriptions.get(index); + events.concat(subscription_item.Event + "," + subscription_item.Topic + + (subscription_item.Key.length()>0 ? "," : "") + + subscription_item.Key + "; "); + } + } + ResponseCmndChar(events.c_str()); +} +# 886 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +void CmndUnsubscribe(void) +{ + MQTT_Subscription subscription_item; + String events; + if (XdrvMailbox.data_len > 0) { + for (uint32_t index = 0; index < subscriptions.size(); index++) { + subscription_item = subscriptions.get(index); + if (subscription_item.Event.equalsIgnoreCase(XdrvMailbox.data)) { + String stopic = subscription_item.Topic + "/#"; + MqttUnsubscribe(stopic.c_str()); + events = subscription_item.Event; + subscriptions.remove(index); + break; + } + } + } else { + + String stopic; + while (subscriptions.size() > 0) { + events.concat(subscriptions.get(0).Event + "; "); + stopic = subscriptions.get(0).Topic + "/#"; + MqttUnsubscribe(stopic.c_str()); + subscriptions.remove(0); + } + } + ResponseCmndChar(events.c_str()); +} + +#endif + +#ifdef USE_EXPRESSION +# 928 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +char * findClosureBracket(char * pStart) +{ + char * pointer = pStart + 1; + + bool bFindClosures = false; + uint8_t matchClosures = 1; + while (*pointer) + { + if (*pointer == ')') { + matchClosures--; + if (matchClosures == 0) { + bFindClosures = true; + break; + } + } else if (*pointer == '(') { + matchClosures++; + } + pointer++; + } + if (bFindClosures) { + return pointer; + } else { + return nullptr; + } +} +# 967 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool findNextNumber(char * &pNumber, float &value) +{ + bool bSucceed = false; + String sNumber = ""; + if (*pNumber == '-') { + sNumber = "-"; + pNumber++; + } + while (*pNumber) { + if (isdigit(*pNumber) || (*pNumber == '.')) { + sNumber += *pNumber; + pNumber++; + } else { + break; + } + } + if (sNumber.length() > 0) { + value = CharToFloat(sNumber.c_str()); + bSucceed = true; + } + return bSucceed; +} +# 1003 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool findNextVariableValue(char * &pVarname, float &value) +{ + bool succeed = true; + value = 0; + String sVarName = ""; + while (*pVarname) { + if (isalpha(*pVarname) || isdigit(*pVarname)) { + sVarName.concat(*pVarname); + pVarname++; + } else { + break; + } + } + sVarName.toUpperCase(); + if (sVarName.startsWith(F("VAR"))) { + int index = sVarName.substring(3).toInt(); + if (index > 0 && index <= MAX_RULE_VARS) { + value = CharToFloat(rules_vars[index -1]); + } + } else if (sVarName.startsWith(F("MEM"))) { + int index = sVarName.substring(3).toInt(); + if (index > 0 && index <= MAX_RULE_MEMS) { + value = CharToFloat(SettingsText(SET_MEM1 + index -1)); + } + } else if (sVarName.equals(F("TIME"))) { + value = MinutesPastMidnight(); + } else if (sVarName.equals(F("UPTIME"))) { + value = MinutesUptime(); + } else if (sVarName.equals(F("UTCTIME"))) { + value = UtcTime(); + } else if (sVarName.equals(F("LOCALTIME"))) { + value = LocalTime(); +#if defined(USE_TIMERS) && defined(USE_SUNRISE) + } else if (sVarName.equals(F("SUNRISE"))) { + value = SunMinutes(0); + } else if (sVarName.equals(F("SUNSET"))) { + value = SunMinutes(1); +#endif + } else { + succeed = false; + } + + return succeed; +} +# 1065 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool findNextObjectValue(char * &pointer, float &value) +{ + bool bSucceed = false; + while (*pointer) + { + if (isspace(*pointer)) { + pointer++; + continue; + } + if (isdigit(*pointer) || (*pointer) == '-') { + bSucceed = findNextNumber(pointer, value); + break; + } else if (isalpha(*pointer)) { + bSucceed = findNextVariableValue(pointer, value); + break; + } else if (*pointer == '(') { + char * closureBracket = findClosureBracket(pointer); + if (closureBracket != nullptr) { + value = evaluateExpression(pointer+1, closureBracket - pointer - 1); + pointer = closureBracket + 1; + bSucceed = true; + } + break; + } else { + break; + } + } + return bSucceed; +} +# 1109 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool findNextOperator(char * &pointer, int8_t &op) +{ + bool bSucceed = false; + while (*pointer) + { + if (isspace(*pointer)) { + pointer++; + continue; + } + op = EXPRESSION_OPERATOR_ADD; + const char *pch = kExpressionOperators; + char ch; + while ((ch = pgm_read_byte(pch++)) != '\0') { + if (ch == *pointer) { + bSucceed = true; + pointer++; + break; + } + op++; + } + break; + } + return bSucceed; +} +# 1146 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +float calculateTwoValues(float v1, float v2, uint8_t op) +{ + switch (op) + { + case EXPRESSION_OPERATOR_ADD: + return v1 + v2; + case EXPRESSION_OPERATOR_SUBTRACT: + return v1 - v2; + case EXPRESSION_OPERATOR_MULTIPLY: + return v1 * v2; + case EXPRESSION_OPERATOR_DIVIDEDBY: + return (0 == v2) ? 0 : (v1 / v2); + case EXPRESSION_OPERATOR_MODULO: + return (0 == v2) ? 0 : (int(v1) % int(v2)); + case EXPRESSION_OPERATOR_POWER: + return FastPrecisePow(v1, v2); + } + return 0; +} +# 1199 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +float evaluateExpression(const char * expression, unsigned int len) +{ + char expbuf[len + 1]; + memcpy(expbuf, expression, len); + expbuf[len] = '\0'; + char * scan_pointer = expbuf; + + LinkedList object_values; + LinkedList operators; + int8_t op; + float va; + + if (findNextObjectValue(scan_pointer, va)) { + object_values.add(va); + } else { + return 0; + } + while (*scan_pointer) + { + if (findNextOperator(scan_pointer, op) + && *scan_pointer + && findNextObjectValue(scan_pointer, va)) + { + operators.add(op); + object_values.add(va); + } else { + + break; + } + } + + + + for (int32_t priority = MAX_EXPRESSION_OPERATOR_PRIORITY; priority>0; priority--) { + int index = 0; + while (index < operators.size()) { + if (priority == pgm_read_byte(kExpressionOperatorsPriorities + operators.get(index))) { + + va = calculateTwoValues(object_values.get(index), object_values.remove(index + 1), operators.remove(index)); + + object_values.set(index, va); + } else { + index++; + } + } + } + return object_values.get(0); +} +#endif + +#ifdef SUPPORT_IF_STATEMENT +void CmndIf(void) +{ + if (XdrvMailbox.data_len > 0) { + char parameters[XdrvMailbox.data_len+1]; + memcpy(parameters, XdrvMailbox.data, XdrvMailbox.data_len); + parameters[XdrvMailbox.data_len] = '\0'; + ProcessIfStatement(parameters); + } + ResponseCmndDone(); +} +# 1273 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool evaluateComparisonExpression(const char *expression, int len) +{ + bool bResult = true; + char expbuf[len + 1]; + memcpy(expbuf, expression, len); + expbuf[len] = '\0'; + String compare_expression = expbuf; + String leftExpr, rightExpr; + int8_t compareOp = parseCompareExpression(compare_expression, leftExpr, rightExpr); + + double leftValue = evaluateExpression(leftExpr.c_str(), leftExpr.length()); + double rightValue = evaluateExpression(rightExpr.c_str(), rightExpr.length()); + switch (compareOp) { + case COMPARE_OPERATOR_EXACT_DIVISION: + bResult = (rightValue != 0 && leftValue == int(leftValue) + && rightValue == int(rightValue) && (int(leftValue) % int(rightValue)) == 0); + break; + case COMPARE_OPERATOR_EQUAL: + bResult = leftExpr.equalsIgnoreCase(rightExpr); + break; + case COMPARE_OPERATOR_BIGGER: + bResult = (leftValue > rightValue); + break; + case COMPARE_OPERATOR_SMALLER: + bResult = (leftValue < rightValue); + break; + case COMPARE_OPERATOR_NUMBER_EQUAL: + bResult = (leftValue == rightValue); + break; + case COMPARE_OPERATOR_NOT_EQUAL: + bResult = (leftValue != rightValue); + break; + case COMPARE_OPERATOR_BIGGER_EQUAL: + bResult = (leftValue >= rightValue); + break; + case COMPARE_OPERATOR_SMALLER_EQUAL: + bResult = (leftValue <= rightValue); + break; + } + return bResult; +} +# 1329 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool findNextLogicOperator(char * &pointer, int8_t &op) +{ + bool bSucceed = false; + while (*pointer && isspace(*pointer)) { + + pointer++; + } + if (*pointer) { + if (strncasecmp_P(pointer, PSTR("AND "), 4) == 0) { + op = LOGIC_OPERATOR_AND; + pointer += 4; + bSucceed = true; + } else if (strncasecmp_P(pointer, PSTR("OR "), 3) == 0) { + op = LOGIC_OPERATOR_OR; + pointer += 3; + bSucceed = true; + } + } + return bSucceed; +} +# 1366 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool findNextLogicObjectValue(char * &pointer, bool &value) +{ + bool bSucceed = false; + while (*pointer && isspace(*pointer)) { + + pointer++; + } + char * pExpr = pointer; + while (*pointer) { + if (isalpha(*pointer) + && (strncasecmp_P(pointer, PSTR("AND "), 4) == 0 + || strncasecmp_P(pointer, PSTR("OR "), 3) == 0)) + { + value = evaluateComparisonExpression(pExpr, pointer - pExpr); + bSucceed = true; + break; + } else if (*pointer == '(') { + char * closureBracket = findClosureBracket(pointer); + if (closureBracket != nullptr) { + value = evaluateLogicalExpression(pointer+1, closureBracket - pointer - 1); + pointer = closureBracket + 1; + bSucceed = true; + } + break; + } + pointer++; + } + if (!bSucceed && pointer > pExpr) { + + value = evaluateComparisonExpression(pExpr, pointer - pExpr); + bSucceed = true; + } + return bSucceed; +} +# 1415 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +bool evaluateLogicalExpression(const char * expression, int len) +{ + bool bResult = false; + + char expbuff[len + 1]; + memcpy(expbuff, expression, len); + expbuff[len] = '\0'; + + + char * pointer = expbuff; + LinkedList values; + LinkedList logicOperators; + + bool bValue; + if (findNextLogicObjectValue(pointer, bValue)) { + values.add(bValue); + } else { + return false; + } + int8_t op; + while (*pointer) { + if (findNextLogicOperator(pointer, op) + && (*pointer) && findNextLogicObjectValue(pointer, bValue)) + { + logicOperators.add(op); + values.add(bValue); + } else { + break; + } + } + + int index = 0; + while (index < logicOperators.size()) { + if (logicOperators.get(index) == LOGIC_OPERATOR_AND) { + values.set(index, values.get(index) && values.get(index+1)); + values.remove(index + 1); + logicOperators.remove(index); + } else { + index++; + } + } + + index = 0; + while (index < logicOperators.size()) { + if (logicOperators.get(index) == LOGIC_OPERATOR_OR) { + values.set(index, values.get(index) || values.get(index+1)); + values.remove(index + 1); + logicOperators.remove(index); + } else { + index++; + } + } + return values.get(0); +} +# 1486 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +int8_t findIfBlock(char * &pointer, int &lenWord, int8_t block_type) +{ + int8_t foundBlock = IF_BLOCK_INVALID; + + const char * word; + while (*pointer) { + if (!isalpha(*pointer)) { + pointer++; + continue; + } + word = pointer; + while (*pointer && isalpha(*pointer)) { + pointer++; + } + lenWord = pointer - word; + + if (2 == lenWord && 0 == strncasecmp_P(word, PSTR("IF"), 2)) { + + + if (findIfBlock(pointer, lenWord, IF_BLOCK_ENDIF) != IF_BLOCK_ENDIF) { + + break; + } + } else if ( (IF_BLOCK_ENDIF == block_type || IF_BLOCK_ANY == block_type) + && (5 == lenWord) && (0 == strncasecmp_P(word, PSTR("ENDIF"), 5))) + { + + foundBlock = IF_BLOCK_ENDIF; + break; + } else if ( (IF_BLOCK_ELSEIF == block_type || IF_BLOCK_ANY == block_type) + && (6 == lenWord) && (0 == strncasecmp_P(word, PSTR("ELSEIF"), 6))) + { + + foundBlock = IF_BLOCK_ELSEIF; + break; + } else if ( (IF_BLOCK_ELSE == block_type || IF_BLOCK_ANY == block_type) + && (4 == lenWord) && (0 == strncasecmp_P(word, PSTR("ELSE"), 4))) + { + + foundBlock = IF_BLOCK_ELSE; + break; + } + } + return foundBlock; +} +# 1543 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +void ExecuteCommandBlock(const char * commands, int len) +{ + char cmdbuff[len + 1]; + memcpy(cmdbuff, commands, len); + cmdbuff[len] = '\0'; + + + char oneCommand[len + 1]; + int insertPosition = 0; + char * pos = cmdbuff; + int lenEndBlock = 0; + while (*pos) { + if (isspace(*pos) || '\x1e' == *pos || ';' == *pos) { + pos++; + continue; + } + if (strncasecmp_P(pos, PSTR("BACKLOG "), 8) == 0) { + + pos += 8; + continue; + } + if (strncasecmp_P(pos, PSTR("IF "), 3) == 0) { + + + char *pEndif = pos + 3; + if (IF_BLOCK_ENDIF != findIfBlock(pEndif, lenEndBlock, IF_BLOCK_ENDIF)) { + + break; + } + + memcpy(oneCommand, pos, pEndif - pos); + oneCommand[pEndif - pos] = '\0'; + pos = pEndif; + } else { + + char *pEndOfCommand = strpbrk(pos, "\x1e;"); + if (NULL == pEndOfCommand) { + pEndOfCommand = pos + strlen(pos); + } + memcpy(oneCommand, pos, pEndOfCommand - pos); + oneCommand[pEndOfCommand - pos] = '\0'; + pos = pEndOfCommand; + } + + + String sCurrentCommand = oneCommand; + sCurrentCommand.trim(); + if (sCurrentCommand.length() > 0 + && backlog.size() < MAX_BACKLOG && !backlog_mutex) + { + + backlog_mutex = true; + backlog.add(insertPosition, sCurrentCommand); + backlog_mutex = false; + insertPosition++; + } + } + return; +} +# 1613 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +void ProcessIfStatement(const char* statements) +{ + String conditionExpression; + int len = strlen(statements); + char statbuff[len + 1]; + memcpy(statbuff, statements, len + 1); + char *pos = statbuff; + int lenEndBlock = 0; + while (true) { + + + while (*pos && *pos != '(') { + pos++; + } + if (0 == *pos) { break; } + char * posEnd = findClosureBracket(pos); + + if (true == evaluateLogicalExpression(pos + 1, posEnd - (pos + 1))) { + + char * cmdBlockStart = posEnd + 1; + char * cmdBlockEnd = cmdBlockStart; + int8_t nextBlock = findIfBlock(cmdBlockEnd, lenEndBlock, IF_BLOCK_ANY); + if (IF_BLOCK_INVALID == nextBlock) { + + break; + } + ExecuteCommandBlock(cmdBlockStart, cmdBlockEnd - cmdBlockStart - lenEndBlock); + pos = cmdBlockEnd; + break; + } else { + pos = posEnd + 1; + int8_t nextBlock = findIfBlock(pos, lenEndBlock, IF_BLOCK_ANY); + if (IF_BLOCK_ELSEIF == nextBlock) { + + continue; + } else if (IF_BLOCK_ELSE == nextBlock) { + + char * cmdBlockEnd = pos; + int8_t nextBlock = findIfBlock(cmdBlockEnd, lenEndBlock, IF_BLOCK_ENDIF); + if (IF_BLOCK_ENDIF != nextBlock) { + + break; + } + ExecuteCommandBlock(pos, cmdBlockEnd - pos - lenEndBlock); + break; + } else { + + break; + } + } + } +} +# 1677 "S:/Development/Tasmota/tasmota/xdrv_10_rules.ino" +void RulesPreprocessCommand(char *pCommands) +{ + char * cmd = pCommands; + int lenEndBlock = 0; + while (*cmd) { + + if (';' == *cmd || isspace(*cmd)) { + cmd++; + } + else if (strncasecmp_P(cmd, PSTR("IF "), 3) == 0) { + + char * pIfStart = cmd; + char * pIfEnd = pIfStart + 3; + + if (IF_BLOCK_ENDIF == findIfBlock(pIfEnd, lenEndBlock, IF_BLOCK_ENDIF)) { + + cmd = pIfEnd; + + + while (pIfStart < pIfEnd) { + if (';' == *pIfStart) + *pIfStart = '\x1e'; + pIfStart++; + } + } + else { + break; + } + } + else { + while (*cmd && ';' != *cmd) { + cmd++; + } + } + } + return; +} +#endif + + + + + +void CmndRule(void) +{ + uint8_t index = XdrvMailbox.index; + if ((index > 0) && (index <= MAX_RULE_SETS)) { + if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules[index -1]))) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) { + switch (XdrvMailbox.payload) { + case 0: + case 1: + bitWrite(Settings.rule_enabled, index -1, XdrvMailbox.payload); + break; + case 2: + bitWrite(Settings.rule_enabled, index -1, bitRead(Settings.rule_enabled, index -1) ^1); + break; + case 4: + case 5: + bitWrite(Settings.rule_once, index -1, XdrvMailbox.payload &1); + break; + case 6: + bitWrite(Settings.rule_once, index -1, bitRead(Settings.rule_once, index -1) ^1); + break; + case 8: + case 9: + bitWrite(Settings.rule_stop, index -1, XdrvMailbox.payload &1); + break; + case 10: + bitWrite(Settings.rule_stop, index -1, bitRead(Settings.rule_stop, index -1) ^1); + break; + } + } else { + int offset = 0; + if ('+' == XdrvMailbox.data[0]) { + offset = strlen(Settings.rules[index -1]); + if (XdrvMailbox.data_len < (sizeof(Settings.rules[index -1]) - offset -1)) { + XdrvMailbox.data[0] = ' '; + } else { + offset = -1; + } + } + if (offset != -1) { + strlcpy(Settings.rules[index -1] + offset, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.rules[index -1])); + } + } + Rules.triggers[index -1] = 0; + } + snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"), + XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)), + GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[index -1]) - strlen(Settings.rules[index -1]) -1, Settings.rules[index -1]); + } +} + +void CmndRuleTimer(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_TIMERS)) { + if (XdrvMailbox.data_len > 0) { +#ifdef USE_EXPRESSION + float timer_set = evaluateExpression(XdrvMailbox.data, XdrvMailbox.data_len); + Rules.timer[XdrvMailbox.index -1] = (timer_set > 0) ? millis() + (1000 * timer_set) : 0; +#else + Rules.timer[XdrvMailbox.index -1] = (XdrvMailbox.payload > 0) ? millis() + (1000 * XdrvMailbox.payload) : 0; +#endif + } + mqtt_data[0] = '\0'; + for (uint32_t i = 0; i < MAX_RULE_TIMERS; i++) { + ResponseAppend_P(PSTR("%c\"T%d\":%d"), (i) ? ',' : '{', i +1, (Rules.timer[i]) ? (Rules.timer[i] - millis()) / 1000 : 0); + } + ResponseJsonEnd(); + } +} + +void CmndEvent(void) +{ + if (XdrvMailbox.data_len > 0) { + strlcpy(Rules.event_data, XdrvMailbox.data, sizeof(Rules.event_data)); + } + ResponseCmndDone(); +} + +void CmndVariable(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) { + if (!XdrvMailbox.usridx) { + mqtt_data[0] = '\0'; + for (uint32_t i = 0; i < MAX_RULE_VARS; i++) { + ResponseAppend_P(PSTR("%c\"Var%d\":\"%s\""), (i) ? ',' : '{', i +1, rules_vars[i]); + } + ResponseJsonEnd(); + } else { + if (XdrvMailbox.data_len > 0) { +#ifdef USE_EXPRESSION + if (XdrvMailbox.data[0] == '=') { + dtostrfd(evaluateExpression(XdrvMailbox.data + 1, XdrvMailbox.data_len - 1), Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]); + } else { + strlcpy(rules_vars[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(rules_vars[XdrvMailbox.index -1])); + } +#else + strlcpy(rules_vars[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(rules_vars[XdrvMailbox.index -1])); +#endif + bitSet(Rules.vars_event, XdrvMailbox.index -1); + } + ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]); + } + } +} + +void CmndMemory(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_MEMS)) { + if (!XdrvMailbox.usridx) { + ResponseCmndAll(SET_MEM1, MAX_RULE_MEMS); + } else { + if (XdrvMailbox.data_len > 0) { +#ifdef USE_EXPRESSION + if (XdrvMailbox.data[0] == '=') { + dtostrfd(evaluateExpression(XdrvMailbox.data + 1, XdrvMailbox.data_len - 1), Settings.flag2.calc_resolution, SettingsText(SET_MEM1 + XdrvMailbox.index -1)); + } else { + SettingsUpdateText(SET_MEM1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data); + } +#else + SettingsUpdateText(SET_MEM1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data); +#endif + bitSet(Rules.mems_event, XdrvMailbox.index -1); + } + ResponseCmndIdxChar(SettingsText(SET_MEM1 + XdrvMailbox.index -1)); + } + } +} + +void CmndCalcResolution(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 7)) { + Settings.flag2.calc_resolution = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.flag2.calc_resolution); +} + +void CmndAddition(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) { + if (XdrvMailbox.data_len > 0) { + float tempvar = CharToFloat(rules_vars[XdrvMailbox.index -1]) + CharToFloat(XdrvMailbox.data); + dtostrfd(tempvar, Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]); + bitSet(Rules.vars_event, XdrvMailbox.index -1); + } + ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]); + } +} + +void CmndSubtract(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) { + if (XdrvMailbox.data_len > 0) { + float tempvar = CharToFloat(rules_vars[XdrvMailbox.index -1]) - CharToFloat(XdrvMailbox.data); + dtostrfd(tempvar, Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]); + bitSet(Rules.vars_event, XdrvMailbox.index -1); + } + ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]); + } +} + +void CmndMultiply(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) { + if (XdrvMailbox.data_len > 0) { + float tempvar = CharToFloat(rules_vars[XdrvMailbox.index -1]) * CharToFloat(XdrvMailbox.data); + dtostrfd(tempvar, Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]); + bitSet(Rules.vars_event, XdrvMailbox.index -1); + } + ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]); + } +} + +void CmndScale(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) { + if (XdrvMailbox.data_len > 0) { + if (strstr(XdrvMailbox.data, ",") != nullptr) { + char sub_string[XdrvMailbox.data_len +1]; + + float valueIN = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 1)); + float fromLow = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 2)); + float fromHigh = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 3)); + float toLow = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)); + float toHigh = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 5)); + float value = map_double(valueIN, fromLow, fromHigh, toLow, toHigh); + dtostrfd(value, Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]); + bitSet(Rules.vars_event, XdrvMailbox.index -1); + } + } + ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]); + } +} + +float map_double(float x, float in_min, float in_max, float out_min, float out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + + + + + +bool Xdrv10(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_50_MSECOND: + RulesEvery50ms(); + break; + case FUNC_EVERY_100_MSECOND: + RulesEvery100ms(); + break; + case FUNC_EVERY_SECOND: + RulesEverySecond(); + break; + case FUNC_SET_POWER: + RulesSetPower(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kRulesCommands, RulesCommand); + break; + case FUNC_RULES_PROCESS: + result = RulesProcess(); + break; + case FUNC_SAVE_BEFORE_RESTART: + RulesSaveBeforeRestart(); + break; +#ifdef SUPPORT_MQTT_EVENT + case FUNC_MQTT_DATA: + result = RulesMqttData(); + break; +#endif + case FUNC_PRE_INIT: + RulesInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_10_scripter.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_10_scripter.ino" +#ifdef USE_SCRIPT +#ifndef USE_RULES +# 40 "S:/Development/Tasmota/tasmota/xdrv_10_scripter.ino" +#define XDRV_10 10 +#define XI2C_37 37 + +#define SCRIPT_DEBUG 0 + + +#ifndef MAXVARS +#define MAXVARS 50 +#endif +#ifndef MAXSVARS +#define MAXSVARS 5 +#endif +#define MAXNVARS MAXVARS-MAXSVARS + +#define MAXFILT 5 +#define SCRIPT_SVARSIZE 20 +#define SCRIPT_MAXSSIZE 48 +#define SCRIPT_EOL '\n' +#define SCRIPT_FLOAT_PRECISION 2 +#define PMEM_SIZE sizeof(Settings.script_pram) +#define SCRIPT_MAXPERM (PMEM_SIZE)-4/sizeof(float) +#define MAX_SCRIPT_SIZE MAX_RULE_SIZE*MAX_RULE_SETS + + +#define EPOCH_OFFSET 1546300800 + +enum {OPER_EQU=1,OPER_PLS,OPER_MIN,OPER_MUL,OPER_DIV,OPER_PLSEQU,OPER_MINEQU,OPER_MULEQU,OPER_DIVEQU,OPER_EQUEQU,OPER_NOTEQU,OPER_GRTEQU,OPER_LOWEQU,OPER_GRT,OPER_LOW,OPER_PERC,OPER_XOR,OPER_AND,OPER_OR,OPER_ANDEQU,OPER_OREQU,OPER_XOREQU,OPER_PERCEQU}; +enum {SCRIPT_LOGLEVEL=1,SCRIPT_TELEPERIOD}; + +#ifdef USE_SCRIPT_FATFS +#include +#include +#ifndef FAT_SCRIPT_SIZE +#define FAT_SCRIPT_SIZE 4096 +#endif +#define FAT_SCRIPT_NAME "script.txt" +#if USE_LONG_FILE_NAMES==1 +#warning ("FATFS long filenames not supported"); +#endif +#if USE_STANDARD_SPI_LIBRARY==0 +#warning ("FATFS standard spi should be used"); +#endif +#endif + +#ifdef SUPPORT_MQTT_EVENT + #include + typedef struct { + String Event; + String Topic; + String Key; + } MQTT_Subscription; + LinkedList subscriptions; +#endif + +#ifdef USE_DISPLAY +#ifdef USE_TOUCH_BUTTONS +#include +extern VButton *buttons[MAXBUTTONS]; +#endif +#endif + +typedef union { + uint8_t data; + struct { + uint8_t is_string : 1; + uint8_t is_permanent : 1; + uint8_t is_timer : 1; + uint8_t is_autoinc : 1; + uint8_t changed : 1; + uint8_t settable : 1; + uint8_t is_filter : 1; + uint8_t constant : 1; + }; +} SCRIPT_TYPE; + +struct T_INDEX { + uint8_t index; + SCRIPT_TYPE bits; +}; + +struct M_FILT { + uint8_t numvals; + uint8_t index; + float maccu; + float rbuff[1]; +}; + +typedef union { + uint8_t data; + struct { + uint8_t nutu8 : 1; + uint8_t nutu7 : 1; + uint8_t nutu6 : 1; + uint8_t nutu5 : 1; + uint8_t nutu4 : 1; + uint8_t nutu3 : 1; + uint8_t is_dir : 1; + uint8_t is_open : 1; + }; +} FILE_FLAGS; + +#define SFS_MAX 4 + +struct SCRIPT_MEM { + float *fvars; + float *s_fvars; + struct T_INDEX *type; + struct M_FILT *mfilt; + char *glob_vnp; + uint8_t *vnp_offset; + char *glob_snp; + char *scriptptr; + char *section_ptr; + char *scriptptr_bu; + char *script_ram; + uint16_t script_size; + uint8_t *script_pram; + uint16_t script_pram_size; + uint8_t numvars; + void *script_mem; + uint16_t script_mem_size; + uint8_t script_dprec; + uint8_t var_not_found; + uint8_t glob_error; + uint8_t max_ssize; + uint8_t script_loglevel; + uint8_t flags; +#ifdef USE_SCRIPT_FATFS + File files[SFS_MAX]; + FILE_FLAGS file_flags[SFS_MAX]; + uint8_t script_sd_found; + char flink[2][14]; +#endif +} glob_script_mem; + + +int16_t last_findex; +uint8_t tasm_cmd_activ=0; +uint8_t fast_script=0; +uint32_t script_lastmillis; + + +#ifdef USE_BUTTON_EVENT +int8_t script_button[MAX_KEYS]; +#endif + +char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo); +char *GetStringResult(char *lp,uint8_t lastop,char *cp,JsonObject *jo); +char *ForceStringVar(char *lp,char *dstr); +void send_download(void); +uint8_t reject(char *name); + +void ScriptEverySecond(void) { + + if (bitRead(Settings.rule_enabled, 0)) { + struct T_INDEX *vtp=glob_script_mem.type; + float delta=(millis()-script_lastmillis)/1000; + script_lastmillis=millis(); + for (uint8_t count=0; count0) { + + *fp-=delta; + if (*fp<0) *fp=0; + } + } + if (vtp[count].bits.is_autoinc) { + + float *fp=&glob_script_mem.fvars[vtp[count].index]; + if (*fp>=0) { + *fp+=delta; + } + } + } + Run_Scripter(">S",2,0); + } +} + +void RulesTeleperiod(void) { + if (bitRead(Settings.rule_enabled, 0) && mqtt_data[0]) Run_Scripter(">T",2, mqtt_data); +} + + +#ifdef USE_24C256 +#ifndef USE_SCRIPT_FATFS + +#include +#define EEPROM_ADDRESS 0x50 + +#ifndef EEP_SCRIPT_SIZE +#define EEP_SCRIPT_SIZE 4095 +#endif + + +static Eeprom24C128_256 eeprom(EEPROM_ADDRESS); + +#define EEP_WRITE(A,B,C) eeprom.writeBytes(A,B,(uint8_t*)C); + +#define EEP_READ(A,B,C) eeprom.readBytes(A,B,(uint8_t*)C); +#endif +#endif + +#define SCRIPT_SKIP_SPACES while (*lp==' ' || *lp=='\t') lp++; +#define SCRIPT_SKIP_EOL while (*lp==SCRIPT_EOL) lp++; + + +int16_t Init_Scripter(void) { +char *script; + + script=glob_script_mem.script_ram; + + + uint16_t lines=0,nvars=0,svars=0,vars=0; + char *lp=script; + char vnames[MAXVARS*10]; + char *vnames_p=vnames; + char *vnp[MAXVARS]; + char **vnp_p=vnp; + char strings[MAXSVARS*SCRIPT_MAXSSIZE]; + struct M_FILT mfilt[MAXFILT]; + + char *strings_p=strings; + char *snp[MAXSVARS]; + char **snp_p=snp; + uint8_t numperm=0,numflt=0,count; + + glob_script_mem.max_ssize=SCRIPT_SVARSIZE; + glob_script_mem.scriptptr=0; + + if (!*script) return -999; + + float fvalues[MAXVARS]; + struct T_INDEX vtypes[MAXVARS]; + char init=0; + while (1) { + + + SCRIPT_SKIP_SPACES + + if (*lp=='\n' || *lp=='\r') goto next_line; + + if (*lp==';') goto next_line; + if (init) { + + if (*lp=='>') { + init=0; + break; + } + char *op=strchr(lp,'='); + if (op) { + vtypes[vars].bits.data=0; + + if (*lp=='p' && *(lp+1)==':') { + lp+=2; + if (numpermMAXFILT) { + return -6; + } + } else { + vtypes[vars].bits.is_filter=0; + } + *vnp_p++=vnames_p; + while (lpMAXNVARS) { + return -1; + } + if (vtypes[vars].bits.is_filter) { + while (isdigit(*op) || *op=='.' || *op=='-') { + op++; + } + while (*op==' ') op++; + if (isdigit(*op)) { + + uint8_t flen=atoi(op); + mfilt[numflt-1].numvals&=0x80; + mfilt[numflt-1].numvals|=flen&0x7f; + } + } + + } else { + + op++; + *snp_p++=strings_p; + while (*op!='\"') { + if (*op==SCRIPT_EOL) break; + *strings_p++=*op++; + } + *strings_p++=0; + vtypes[vars].bits.is_string=1; + vtypes[vars].index=svars; + svars++; + if (svars>MAXSVARS) { + return -2; + } + } + vars++; + if (vars>MAXVARS) { + return -3; + } + } + } else { + if (!strncmp(lp,">D",2)) { + lp+=2; + SCRIPT_SKIP_SPACES + if (isdigit(*lp)) { + uint8_t ssize=atoi(lp)+1; + if (ssize<10 || ssize>SCRIPT_MAXSSIZE) ssize=SCRIPT_MAXSSIZE; + glob_script_mem.max_ssize=ssize; + } + init=1; + } + } + + next_line: + lp = strchr(lp, SCRIPT_EOL); + if (!lp) break; + lp++; + } + + uint16_t fsize=0; + for (count=0; count255) { + free(glob_script_mem.script_mem); + return -5; + } + } + + AddLog_P2(LOG_LEVEL_INFO, PSTR("Script: nv=%d, tv=%d, vns=%d, ram=%d"), nvars, svars, index, glob_script_mem.script_mem_size); + + + char *cp1=glob_script_mem.glob_snp; + char *sp=strings; + for (count=0; countnumvals=mfilt[count].numvals; + mp+=sizeof(struct M_FILT)+((mfilt[count].numvals&0x7f)-1)*sizeof(float); + } + + glob_script_mem.numvars=vars; + glob_script_mem.script_dprec=SCRIPT_FLOAT_PRECISION; + glob_script_mem.script_loglevel=LOG_LEVEL_INFO; + + +#if SCRIPT_DEBUG>2 + struct T_INDEX *dvtp=glob_script_mem.type; + for (uint8_t count=0; count0 + ClaimSerial(); + SetSerialBaudrate(9600); +#endif + + + glob_script_mem.scriptptr=lp-1; + glob_script_mem.scriptptr_bu=glob_script_mem.scriptptr; + return 0; + +} + +#ifdef USE_LIGHT +#ifdef USE_WS2812 +void ws2812_set_array(float *array ,uint8_t len) { + + Ws2812ForceSuspend(); + for (uint8_t cnt=0;cntSettings.light_pixels) break; + uint32_t col=array[cnt]; + Ws2812SetColor(cnt+1,col>>16,col>>8,col,0); + } + Ws2812ForceUpdate(); +} +#endif +#endif + +#define NUM_RES 0xfe +#define STR_RES 0xfd +#define VAR_NV 0xff + +#define NTYPE 0 +#define STYPE 0x80 + +#ifndef FLT_MAX +#define FLT_MAX 99999999 +#endif + +float median_array(float *array,uint8_t len) { + uint8_t ind[len]; + uint8_t mind=0,index=0,flg; + float min=FLT_MAX; + + for (uint8_t hcnt=0; hcntnumvals&0x7f; + return mflp->rbuff; + } + mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float); + } + return 0; +} + + +float Get_MFVal(uint8_t index,uint8_t bind) { + uint8_t *mp=(uint8_t*)glob_script_mem.mfilt; + for (uint8_t count=0; countnumvals&0x7f; + if (!bind) { + return mflp->index; + } + if (bind<1 || bind>maxind) bind=maxind; + return mflp->rbuff[bind-1]; + } + mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float); + } + return 0; +} + +void Set_MFVal(uint8_t index,uint8_t bind,float val) { + uint8_t *mp=(uint8_t*)glob_script_mem.mfilt; + for (uint8_t count=0; countnumvals&0x7f; + if (!bind) { + mflp->index=val; + } else { + if (bind<1 || bind>maxind) bind=maxind; + mflp->rbuff[bind-1]=val; + } + return; + } + mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float); + } +} + + +float Get_MFilter(uint8_t index) { + uint8_t *mp=(uint8_t*)glob_script_mem.mfilt; + for (uint8_t count=0; countnumvals&0x80) { + + return mflp->maccu/(mflp->numvals&0x7f); + } else { + + return median_array(mflp->rbuff,mflp->numvals); + } + } + mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float); + } + return 0; +} + +void Set_MFilter(uint8_t index, float invar) { + uint8_t *mp=(uint8_t*)glob_script_mem.mfilt; + for (uint8_t count=0; countnumvals&0x80) { + + mflp->maccu-=mflp->rbuff[mflp->index]; + mflp->maccu+=invar; + mflp->rbuff[mflp->index]=invar; + mflp->index++; + if (mflp->index>=(mflp->numvals&0x7f)) mflp->index=0; + } else { + + mflp->rbuff[mflp->index]=invar; + mflp->index++; + if (mflp->index>=mflp->numvals) mflp->index=0; + } + break; + } + mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float); + } +} + +#define MEDIAN_SIZE 5 +#define MEDIAN_FILTER_NUM 2 + +struct MEDIAN_FILTER { +float buffer[MEDIAN_SIZE]; +int8_t index; +} script_mf[MEDIAN_FILTER_NUM]; + +float DoMedian5(uint8_t index, float in) { + + if (index>=MEDIAN_FILTER_NUM) index=0; + + struct MEDIAN_FILTER* mf=&script_mf[index]; + mf->buffer[mf->index]=in; + mf->index++; + if (mf->index>=MEDIAN_SIZE) mf->index=0; + return median_array(mf->buffer,MEDIAN_SIZE); +} + +#ifdef USE_LIGHT + +uint32_t HSVToRGB(uint16_t hue, uint8_t saturation, uint8_t value) { +float r = 0, g = 0, b = 0; +struct HSV { + float H; + float S; + float V; +} hsv; + +hsv.H=hue; +hsv.S=(float)saturation/100.0; +hsv.V=(float)value/100.0; + +if (hsv.S == 0) { + r = hsv.V; + g = hsv.V; + b = hsv.V; + } else { + int i; + float f, p, q, t; + + if (hsv.H == 360) + hsv.H = 0; + else + hsv.H = hsv.H / 60; + + i = (int)trunc(hsv.H); + f = hsv.H - i; + + p = hsv.V * (1.0 - hsv.S); + q = hsv.V * (1.0 - (hsv.S * f)); + t = hsv.V * (1.0 - (hsv.S * (1.0 - f))); + + switch (i) + { + case 0: + r = hsv.V; + g = t; + b = p; + break; + + case 1: + r = q; + g = hsv.V; + b = p; + break; + + case 2: + r = p; + g = hsv.V; + b = t; + break; + + case 3: + r = p; + g = q; + b = hsv.V; + break; + + case 4: + r = t; + g = p; + b = hsv.V; + break; + + default: + r = hsv.V; + g = p; + b = q; + break; + } + + } + + uint8_t ir,ig,ib; + ir=r*255; + ig=g*255; + ib=b*255; + + uint32_t rgb=(ir<<16)|(ig<<8)|ib; + return rgb; +} +#endif + + + + +char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,JsonObject *jo) { + uint16_t count,len=0; + uint8_t nres=0; + char vname[32]; + float fvar=0; + tind->index=0; + tind->bits.data=0; + + if (isdigit(*lp) || (*lp=='-' && isdigit(*(lp+1))) || *lp=='.') { + + if (fp) { + if (*lp=='0' && *(lp+1)=='x') { + lp+=2; + *fp=strtol(lp,0,16); + } else { + *fp=CharToFloat(lp); + } + } + if (*lp=='-') lp++; + while (isdigit(*lp) || *lp=='.') { + if (*lp==0 || *lp==SCRIPT_EOL) break; + lp++; + } + tind->bits.constant=1; + tind->bits.is_string=0; + *vtype=NUM_RES; + return lp; + } + if (*lp=='"') { + lp++; + while (*lp!='"') { + if (*lp==0 || *lp==SCRIPT_EOL) break; + uint8_t iob=*lp; + if (iob=='\\') { + lp++; + if (*lp=='t') { + iob='\t'; + } else if (*lp=='n') { + iob='\n'; + } else if (*lp=='r') { + iob='\r'; + } else if (*lp=='\\') { + iob='\\'; + } else { + lp--; + } + if (sp) *sp++=iob; + } else { + if (sp) *sp++=iob; + } + lp++; + } + if (sp) *sp=0; + *vtype=STR_RES; + tind->bits.constant=1; + tind->bits.is_string=1; + return lp+1; + } + + if (*lp=='-') { + + nres=1; + lp++; + } + + const char *term="\n\r ])=+-/*%>index=VAR_NV; + glob_script_mem.var_not_found=1; + return lp; + } + + struct T_INDEX *vtp=glob_script_mem.type; + char dvnam[32]; + strcpy (dvnam,vname); + uint8_t olen=len; + last_findex=-1; + char *ja=strchr(dvnam,'['); + if (ja) { + *ja=0; + ja++; + olen=strlen(dvnam); + } + for (count=0; countindex=count; + if (vtp[count].bits.is_string==0) { + *vtype=NTYPE|index; + if (vtp[count].bits.is_filter) { + if (ja) { + lp+=olen+1; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + last_findex=fvar; + fvar=Get_MFVal(index,fvar); + len=1; + } else { + fvar=Get_MFilter(index); + } + } else { + fvar=glob_script_mem.fvars[index]; + } + if (nres) fvar=-fvar; + if (fp) *fp=fvar; + } else { + *vtype=STYPE|index; + if (sp) strlcpy(sp,glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize),SCRIPT_MAXSSIZE); + } + return lp+len; + } + } + } + + if (jo) { + + const char* str_value; + uint8_t aindex; + String vn; + char *ja=strchr(vname,'['); + if (ja) { + + *ja=0; + ja++; + + float fvar; + GetNumericResult(ja,OPER_EQU,&fvar,0); + aindex=fvar; + if (aindex<1 || aindex>6) aindex=1; + aindex--; + } + if (jo->success()) { + char *subtype=strchr(vname,'#'); + char *subtype2; + if (subtype) { + *subtype=0; + subtype++; + subtype2=strchr(subtype,'#'); + if (subtype2) { + *subtype2=0; + *subtype2++; + } + } + vn=vname; + str_value = (*jo)[vn]; + if ((*jo)[vn].success()) { + if (subtype) { + JsonObject &jobj1=(*jo)[vn]; + if (jobj1.success()) { + vn=subtype; + jo=&jobj1; + str_value = (*jo)[vn]; + if ((*jo)[vn].success()) { + + if (subtype2) { + JsonObject &jobj2=(*jo)[vn]; + if ((*jo)[vn].success()) { + vn=subtype2; + jo=&jobj2; + str_value = (*jo)[vn]; + if ((*jo)[vn].success()) { + goto skip; + } else { + goto chknext; + } + } else { + goto chknext; + } + } + + goto skip; + } + } else { + goto chknext; + } + } + skip: + if (ja) { + + str_value = (*jo)[vn][aindex]; + } + if (str_value && *str_value) { + if ((*jo).is(vn)) { + if (!strncmp(str_value,"ON",2)) { + if (fp) *fp=1; + goto nexit; + } else if (!strncmp(str_value,"OFF",3)) { + if (fp) *fp=0; + goto nexit; + } else { + *vtype=STR_RES; + tind->bits.constant=1; + tind->bits.is_string=1; + if (sp) strlcpy(sp,str_value,SCRIPT_MAXSSIZE); + return lp+len; + } + + } else { + if (fp) { + if (!strncmp(vn.c_str(),"Epoch",5)) { + *fp=atoi(str_value)-(uint32_t)EPOCH_OFFSET; + } else { + *fp=CharToFloat((char*)str_value); + } + } + nexit: + *vtype=NUM_RES; + tind->bits.constant=1; + tind->bits.is_string=0; + return lp+len; + } + } + } + } + } + +chknext: + switch (vname[0]) { + case 'a': +#ifdef USE_ANGLE_FUNC + if (!strncmp(vname,"acos(",5)) { + lp+=5; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + fvar=acosf(fvar); + lp++; + len=0; + goto exit; + } +#endif + break; + + case 'b': + if (!strncmp(vname,"boot",4)) { + if (rules_flag.system_boot) { + rules_flag.system_boot=0; + fvar=1; + } + goto exit; + } +#ifdef USE_BUTTON_EVENT + if (!strncmp(vname,"bt[",3)) { + + GetNumericResult(vname+3,OPER_EQU,&fvar,0); + uint32_t index=fvar; + if (index<1 || index>MAX_KEYS) index=1; + fvar=script_button[index-1]; + script_button[index-1]|=0x80; + len++; + goto exit; + } +#endif + break; + case 'c': + if (!strncmp(vname,"chg[",4)) { + + struct T_INDEX ind; + uint8_t vtype; + isvar(vname+4,&vtype,&ind,0,0,0); + if (!ind.bits.constant) { + uint8_t index=glob_script_mem.type[ind.index].index; + if (glob_script_mem.fvars[index]!=glob_script_mem.s_fvars[index]) { + + glob_script_mem.s_fvars[index]=glob_script_mem.fvars[index]; + fvar=1; + len++; + goto exit; + } else { + fvar=0; + len++; + goto exit; + } + } + } + break; + case 'd': + if (!strncmp(vname,"day",3)) { + fvar=RtcTime.day_of_month; + goto exit; + } + break; + case 'e': + if (!strncmp(vname,"epoch",5)) { + fvar=UtcTime()-(uint32_t)EPOCH_OFFSET; + goto exit; + } + break; +#ifdef USE_SCRIPT_FATFS + case 'f': + if (!strncmp(vname,"fo(",3)) { + lp+=3; + char str[SCRIPT_MAXSSIZE]; + lp=GetStringResult(lp,OPER_EQU,str,0); + while (*lp==' ') lp++; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + uint8_t mode=fvar; + fvar=-1; + for (uint8_t cnt=0;cnt=SFS_MAX) ind=SFS_MAX-1; + glob_script_mem.files[ind].close(); + glob_script_mem.file_flags[ind].is_open=0; + fvar=0; + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"ff(",3)) { + lp+=3; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + uint8_t ind=fvar; + if (ind>=SFS_MAX) ind=SFS_MAX-1; + glob_script_mem.files[ind].flush(); + fvar=0; + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"fw(",3)) { + lp+=3; + char str[SCRIPT_MAXSSIZE]; + lp=ForceStringVar(lp,str); + while (*lp==' ') lp++; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + uint8_t ind=fvar; + if (ind>=SFS_MAX) ind=SFS_MAX-1; + if (glob_script_mem.file_flags[ind].is_open) { + fvar=glob_script_mem.files[ind].print(str); + } else { + fvar=0; + } + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"fr(",3)) { + lp+=3; + struct T_INDEX ind; + uint8_t vtype; + uint8_t sindex=0; + lp=isvar(lp,&vtype,&ind,0,0,0); + if (vtype!=VAR_NV) { + + if ((vtype&STYPE)==0) { + + fvar=0; + goto exit; + } else { + + sindex=glob_script_mem.type[ind.index].index; + } + } else { + + fvar=0; + goto exit; + } + while (*lp==' ') lp++; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + uint8_t find=fvar; + if (find>=SFS_MAX) find=SFS_MAX-1; + uint8_t index=0; + char str[glob_script_mem.max_ssize+1]; + char *cp=str; + if (glob_script_mem.file_flags[find].is_open) { + if (glob_script_mem.file_flags[find].is_dir) { + while (true) { + File entry=glob_script_mem.files[find].openNextFile(); + if (entry) { + if (!reject((char*)entry.name())) { + strcpy(str,entry.name()); + entry.close(); + break; + } + } else { + *cp=0; + break; + } + entry.close(); + } + index=strlen(str); + } else { + while (glob_script_mem.files[find].available()) { + uint8_t buf[1]; + glob_script_mem.files[find].read(buf,1); + if (buf[0]=='\t' || buf[0]==',' || buf[0]=='\n' || buf[0]=='\r') { + break; + } else { + *cp++=buf[0]; + index++; + if (index>=glob_script_mem.max_ssize-1) break; + } + } + *cp=0; + } + } else { + strcpy(str,"file error"); + } + lp++; + strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize); + fvar=index; + len=0; + goto exit; + } + if (!strncmp(vname,"fd(",3)) { + lp+=3; + char str[glob_script_mem.max_ssize+1]; + lp=GetStringResult(lp,OPER_EQU,str,0); + SD.remove(str); + lp++; + len=0; + goto exit; + } +#ifdef USE_SCRIPT_FATFS_EXT + if (!strncmp(vname,"fe(",3)) { + lp+=3; + char str[glob_script_mem.max_ssize+1]; + lp=GetStringResult(lp,OPER_EQU,str,0); + + File ef=SD.open(str); + if (ef) { + uint16_t fsiz=ef.size(); + if (fsiz<2048) { + char *script=(char*)calloc(fsiz+16,1); + if (script) { + ef.read((uint8_t*)script,fsiz); + execute_script(script); + free(script); + fvar=1; + } + } + ef.close(); + } + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"fmd(",4)) { + lp+=4; + char str[glob_script_mem.max_ssize+1]; + lp=GetStringResult(lp,OPER_EQU,str,0); + fvar=SD.mkdir(str); + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"frd(",4)) { + lp+=4; + char str[glob_script_mem.max_ssize+1]; + lp=GetStringResult(lp,OPER_EQU,str,0); + fvar=SD.rmdir(str); + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"fx(",3)) { + lp+=3; + char str[glob_script_mem.max_ssize+1]; + lp=GetStringResult(lp,OPER_EQU,str,0); + if (SD.exists(str)) fvar=1; + else fvar=0; + lp++; + len=0; + goto exit; + } +#endif + if (!strncmp(vname,"fl1(",4) || !strncmp(vname,"fl2(",4) ) { + uint8_t lknum=*(lp+2)&3; + lp+=4; + char str[glob_script_mem.max_ssize+1]; + lp=GetStringResult(lp,OPER_EQU,str,0); + if (lknum<1 || lknum>2) lknum=1; + strlcpy(glob_script_mem.flink[lknum-1],str,14); + lp++; + fvar=0; + len=0; + goto exit; + } + if (!strncmp(vname,"fsm",3)) { + fvar=glob_script_mem.script_sd_found; + + goto exit; + } + break; + +#endif + case 'g': + if (!strncmp(vname,"gtmp",4)) { + fvar=global_temperature; + goto exit; + } + if (!strncmp(vname,"ghum",4)) { + fvar=global_humidity; + goto exit; + } + if (!strncmp(vname,"gprs",4)) { + fvar=global_pressure; + goto exit; + } + if (!strncmp(vname,"gtopic",6)) { + if (sp) strlcpy(sp,SettingsText(SET_MQTT_GRP_TOPIC),glob_script_mem.max_ssize); + goto strexit; + } + break; + case 'h': + if (!strncmp(vname,"hours",5)) { + fvar=RtcTime.hour; + goto exit; + } + if (!strncmp(vname,"heap",4)) { + fvar=ESP.getFreeHeap(); + goto exit; + } + if (!strncmp(vname,"hn(",3)) { + lp=GetNumericResult(lp+3,OPER_EQU,&fvar,0); + if (fvar<0 || fvar>255) fvar=0; + lp++; + len=0; + if (sp) { + sprintf(sp,"%02x",(uint8_t)fvar); + } + goto strexit; + } + if (!strncmp(vname,"hx(",3)) { + lp=GetNumericResult(lp+3,OPER_EQU,&fvar,0); + lp++; + len=0; + if (sp) { + sprintf(sp,"%08x",(uint32_t)fvar); + } + goto strexit; + } +#ifdef USE_LIGHT + + if (!strncmp(vname,"hsvrgb(",7)) { + lp=GetNumericResult(lp+7,OPER_EQU,&fvar,0); + if (fvar<0 || fvar>360) fvar=0; + SCRIPT_SKIP_SPACES + + float fvar2; + lp=GetNumericResult(lp,OPER_EQU,&fvar2,0); + if (fvar2<0 || fvar2>100) fvar2=0; + SCRIPT_SKIP_SPACES + + float fvar3; + lp=GetNumericResult(lp,OPER_EQU,&fvar3,0); + if (fvar3<0 || fvar3>100) fvar3=0; + + fvar=HSVToRGB(fvar,fvar2,fvar3); + + lp++; + len=0; + goto exit; + } + +#endif + break; + case 'i': + if (!strncmp(vname,"int(",4)) { + lp=GetNumericResult(lp+4,OPER_EQU,&fvar,0); + fvar=floor(fvar); + lp++; + len=0; + goto exit; + } + break; + case 'l': + if (!strncmp(vname,"loglvl",6)) { + fvar=glob_script_mem.script_loglevel; + tind->index=SCRIPT_LOGLEVEL; + exit_settable: + if (fp) *fp=fvar; + *vtype=NTYPE; + tind->bits.settable=1; + tind->bits.is_string=0; + return lp+len; + } + break; + case 'm': + if (!strncmp(vname,"med(",4)) { + float fvar1; + lp=GetNumericResult(lp+4,OPER_EQU,&fvar1,0); + SCRIPT_SKIP_SPACES + + float fvar2; + lp=GetNumericResult(lp,OPER_EQU,&fvar2,0); + fvar=DoMedian5(fvar1,fvar2); + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"micros",6)) { + fvar=micros(); + goto exit; + } + if (!strncmp(vname,"millis",6)) { + fvar=millis(); + goto exit; + } + if (!strncmp(vname,"mins",4)) { + fvar=RtcTime.minute; + goto exit; + } + if (!strncmp(vname,"month",5)) { + fvar=RtcTime.month; + goto exit; + } + if (!strncmp(vname,"mqttc",5)) { + if (rules_flag.mqtt_connected) { + rules_flag.mqtt_connected=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"mqttd",5)) { + if (rules_flag.mqtt_disconnected) { + rules_flag.mqtt_disconnected=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"mqtts",5)) { + fvar=!global_state.mqtt_down; + goto exit; + } + break; + case 'p': + if (!strncmp(vname,"pin[",4)) { + + GetNumericResult(vname+4,OPER_EQU,&fvar,0); + fvar=digitalRead((uint8_t)fvar); + + len++; + goto exit; + } + if (!strncmp(vname,"pn[",3)) { + GetNumericResult(vname+3,OPER_EQU,&fvar,0); + fvar=pin[(uint8_t)fvar]; + + len++; + goto exit; + } + if (!strncmp(vname,"pd[",3)) { + GetNumericResult(vname+3,OPER_EQU,&fvar,0); + uint8_t gpiopin=fvar; + for (uint8_t i=0;iMAX_COUNTERS) index=1; + fvar=RtcSettings.pulse_counter[index-1]; + len+=1; + goto exit; + } + break; + + case 'r': + if (!strncmp(vname,"ram",3)) { + fvar=glob_script_mem.script_mem_size+(glob_script_mem.script_size)+(PMEM_SIZE); + goto exit; + } + break; + case 's': + if (!strncmp(vname,"secs",4)) { + fvar=RtcTime.second; + goto exit; + } + if (!strncmp(vname,"sw[",3)) { + + GetNumericResult(vname+3,OPER_EQU,&fvar,0); + fvar=SwitchLastState((uint32_t)fvar); + + len++; + goto exit; + } + if (!strncmp(vname,"stack",5)) { + fvar=GetStack(); + goto exit; + } + if (!strncmp(vname,"slen",4)) { + fvar=strlen(glob_script_mem.script_ram); + goto exit; + } + if (!strncmp(vname,"sl(",3)) { + lp+=3; + char str[SCRIPT_MAXSSIZE]; + lp=GetStringResult(lp,OPER_EQU,str,0); + lp++; + len=0; + fvar=strlen(str); + goto exit; + } + if (!strncmp(vname,"sb(",3)) { + lp+=3; + char str[SCRIPT_MAXSSIZE]; + lp=GetStringResult(lp,OPER_EQU,str,0); + SCRIPT_SKIP_SPACES + float fvar1; + lp=GetNumericResult(lp,OPER_EQU,&fvar1,0); + SCRIPT_SKIP_SPACES + float fvar2; + lp=GetNumericResult(lp,OPER_EQU,&fvar2,0); + lp++; + len=0; + if (fvar1<0) { + fvar1=strlen(str)+fvar1; + } + memcpy(sp,&str[(uint8_t)fvar1],(uint8_t)fvar2); + sp[(uint8_t)fvar2] = '\0'; + goto strexit; + } + if (!strncmp(vname,"st(",3)) { + lp+=3; + char str[SCRIPT_MAXSSIZE]; + lp=GetStringResult(lp,OPER_EQU,str,0); + while (*lp==' ') lp++; + char token[2]; + token[0]=*lp++; + token[1]=0; + while (*lp==' ') lp++; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + + lp++; + len=0; + if (sp) { + + char *st=strtok(str,token); + if (!st) { + *sp=0; + } else { + for (uint8_t cnt=1; cnt<=fvar; cnt++) { + if (cnt==fvar) { + strcpy(sp,st); + break; + } + st=strtok(NULL,token); + if (!st) { + *sp=0; + break; + } + } + } + } + goto strexit; + } + if (!strncmp(vname,"s(",2)) { + lp+=2; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + char str[glob_script_mem.max_ssize+1]; + dtostrfd(fvar,glob_script_mem.script_dprec,str); + if (sp) strlcpy(sp,str,glob_script_mem.max_ssize); + lp++; + len=0; + goto strexit; + } +#if defined(USE_TIMERS) && defined(USE_SUNRISE) + if (!strncmp(vname,"sunrise",7)) { + fvar=SunMinutes(0); + goto exit; + } + if (!strncmp(vname,"sunset",6)) { + fvar=SunMinutes(1); + goto exit; + } +#endif + +#ifdef USE_SHUTTER + if (!strncmp(vname,"sht[",4)) { + GetNumericResult(vname+4,OPER_EQU,&fvar,0); + uint8_t index=fvar; + if (index<=shutters_present) { + fvar=Settings.shutter_position[index-1]; + } else { + fvar=-1; + } + len+=1; + goto exit; + } +#endif +#ifdef USE_ANGLE_FUNC + if (!strncmp(vname,"sin(",4)) { + lp+=4; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + fvar=sinf(fvar); + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"sqrt(",5)) { + lp+=5; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + fvar=sqrtf(fvar); + lp++; + len=0; + goto exit; + } +#endif +#ifdef USE_SML_SCRIPT_CMD + if (!strncmp(vname,"sml(",4)) { + lp+=4; + float fvar1; + lp=GetNumericResult(lp,OPER_EQU,&fvar1,0); + SCRIPT_SKIP_SPACES + float fvar2; + lp=GetNumericResult(lp,OPER_EQU,&fvar2,0); + SCRIPT_SKIP_SPACES + if (fvar2==0) { + float fvar3; + lp=GetNumericResult(lp,OPER_EQU,&fvar3,0); + fvar=SML_SetBaud(fvar1,fvar3); + } else { + char str[SCRIPT_MAXSSIZE]; + lp=GetStringResult(lp,OPER_EQU,str,0); + fvar=SML_Write(fvar1,str); + } + lp++; + fvar=0; + len=0; + goto exit; + } +#endif + break; + case 't': + if (!strncmp(vname,"time",4)) { + fvar=MinutesPastMidnight(); + goto exit; + } + if (!strncmp(vname,"tper",4)) { + fvar=Settings.tele_period; + tind->index=SCRIPT_TELEPERIOD; + goto exit_settable; + } + if (!strncmp(vname,"tinit",5)) { + if (rules_flag.time_init) { + rules_flag.time_init=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"tset",4)) { + if (rules_flag.time_set) { + rules_flag.time_set=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"tstamp",6)) { + if (sp) strlcpy(sp,GetDateAndTime(DT_LOCAL).c_str(),glob_script_mem.max_ssize); + goto strexit; + } + if (!strncmp(vname,"topic",5)) { + if (sp) strlcpy(sp,SettingsText(SET_MQTT_TOPIC),glob_script_mem.max_ssize); + goto strexit; + } +#ifdef USE_DISPLAY +#ifdef USE_TOUCH_BUTTONS + if (!strncmp(vname,"tbut[",5)) { + GetNumericResult(vname+5,OPER_EQU,&fvar,0); + uint8_t index=fvar; + if (index<1 || index>MAXBUTTONS) index=1; + index--; + if (buttons[index]) { + fvar=buttons[index]->vpower&0x80; + } else { + fvar=-1; + } + len+=1; + goto exit; + } + +#endif +#endif + break; + case 'u': + if (!strncmp(vname,"uptime",6)) { + fvar=MinutesUptime(); + goto exit; + } + if (!strncmp(vname,"upsecs",6)) { + fvar=uptime; + goto exit; + } + if (!strncmp(vname,"upd[",4)) { + + struct T_INDEX ind; + uint8_t vtype; + isvar(vname+4,&vtype,&ind,0,0,0); + if (!ind.bits.constant) { + if (!ind.bits.changed) { + fvar=0; + len++; + goto exit; + } else { + glob_script_mem.type[ind.index].bits.changed=0; + fvar=1; + len++; + goto exit; + } + } + goto notfound; + } + break; + + case 'w': + if (!strncmp(vname,"wday",4)) { + fvar=RtcTime.day_of_week; + goto exit; + } + if (!strncmp(vname,"wific",5)) { + if (rules_flag.wifi_connected) { + rules_flag.wifi_connected=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"wifid",5)) { + if (rules_flag.wifi_disconnected) { + rules_flag.wifi_disconnected=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"wifis",5)) { + fvar=!global_state.wifi_down; + goto exit; + } + break; + case 'y': + if (!strncmp(vname,"year",4)) { + fvar=RtcTime.year; + goto exit; + } + break; + default: + break; + } + +notfound: + if (fp) *fp=0; + *vtype=VAR_NV; + tind->index=VAR_NV; + glob_script_mem.var_not_found=1; + return lp; + +exit: + if (fp) *fp=fvar; + *vtype=NUM_RES; + tind->bits.constant=1; + tind->bits.is_string=0; + return lp+len; + +strexit: + *vtype=STYPE; + tind->bits.constant=1; + tind->bits.is_string=1; + return lp+len; +} + + + +char *getop(char *lp, uint8_t *operand) { + switch (*lp) { + case '=': + if (*(lp+1)=='=') { + *operand=OPER_EQUEQU; + return lp+2; + } else { + *operand=OPER_EQU; + return lp+1; + } + break; + case '+': + if (*(lp+1)=='=') { + *operand=OPER_PLSEQU; + return lp+2; + } else { + *operand=OPER_PLS; + return lp+1; + } + break; + case '-': + if (*(lp+1)=='=') { + *operand=OPER_MINEQU; + return lp+2; + } else { + *operand=OPER_MIN; + return lp+1; + } + break; + case '*': + if (*(lp+1)=='=') { + *operand=OPER_MULEQU; + return lp+2; + } else { + *operand=OPER_MUL; + return lp+1; + } + break; + case '/': + if (*(lp+1)=='=') { + *operand=OPER_DIVEQU; + return lp+2; + } else { + *operand=OPER_DIV; + return lp+1; + } + break; + case '!': + if (*(lp+1)=='=') { + *operand=OPER_NOTEQU; + return lp+2; + } + break; + case '>': + if (*(lp+1)=='=') { + *operand=OPER_GRTEQU; + return lp+2; + } else { + *operand=OPER_GRT; + return lp+1; + + } + break; + case '<': + if (*(lp+1)=='=') { + *operand=OPER_LOWEQU; + return lp+2; + } else { + *operand=OPER_LOW; + return lp+1; + } + break; + case '%': + if (*(lp+1)=='=') { + *operand=OPER_PERCEQU; + return lp+2; + } else { + *operand=OPER_PERC; + return lp+1; + } + break; + case '^': + if (*(lp+1)=='=') { + *operand=OPER_XOREQU; + return lp+2; + } else { + *operand=OPER_XOR; + return lp+1; + } + break; + case '&': + if (*(lp+1)=='=') { + *operand=OPER_ANDEQU; + return lp+2; + } else { + *operand=OPER_AND; + return lp+1; + } + break; + case '|': + if (*(lp+1)=='=') { + *operand=OPER_OREQU; + return lp+2; + } else { + *operand=OPER_OR; + return lp+1; + } + break; + } + *operand=0; + return lp; +} + + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) + + +extern "C" { +#include + extern cont_t g_cont; +} +uint16_t GetStack(void) { + register uint32_t *sp asm("a1"); + return (4 * (sp - g_cont.stack)); +} + +#else +extern "C" { +#include + extern cont_t* g_pcont; +} +uint16_t GetStack(void) { + register uint32_t *sp asm("a1"); + return (4 * (sp - g_pcont->stack)); +} +#endif + +char *GetStringResult(char *lp,uint8_t lastop,char *cp,JsonObject *jo) { + uint8_t operand=0; + uint8_t vtype; + char *slp; + struct T_INDEX ind; + char str[SCRIPT_MAXSSIZE],str1[SCRIPT_MAXSSIZE]; + while (1) { + lp=isvar(lp,&vtype,&ind,0,str1,jo); + if (vtype!=STR_RES && !(vtype&STYPE)) { + + glob_script_mem.glob_error=1; + return lp; + } + switch (lastop) { + case OPER_EQU: + strlcpy(str,str1,sizeof(str)); + break; + case OPER_PLS: + strncat(str,str1,sizeof(str)); + break; + } + slp=lp; + lp=getop(lp,&operand); + switch (operand) { + case OPER_EQUEQU: + case OPER_NOTEQU: + case OPER_LOW: + case OPER_LOWEQU: + case OPER_GRT: + case OPER_GRTEQU: + lp=slp; + strcpy(cp,str); + return lp; + break; + default: + break; + } + lastop=operand; + if (!operand) { + strcpy(cp,str); + return lp; + } + } +} + +char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo) { +uint8_t operand=0; +float fvar1,fvar; +char *slp; +uint8_t vtype; +struct T_INDEX ind; + while (1) { + + if (*lp=='(') { + lp++; + lp=GetNumericResult(lp,OPER_EQU,&fvar1,jo); + lp++; + + } else { + lp=isvar(lp,&vtype,&ind,&fvar1,0,jo); + if (vtype!=NUM_RES && vtype&STYPE) { + + glob_script_mem.glob_error=1; + } + } + switch (lastop) { + case OPER_EQU: + fvar=fvar1; + break; + case OPER_PLS: + fvar+=fvar1; + break; + case OPER_MIN: + fvar-=fvar1; + break; + case OPER_MUL: + fvar*=fvar1; + break; + case OPER_DIV: + fvar/=fvar1; + break; + case OPER_PERC: + fvar=fmodf(fvar,fvar1); + break; + case OPER_XOR: + fvar=(uint32_t)fvar^(uint32_t)fvar1; + break; + case OPER_AND: + fvar=(uint32_t)fvar&(uint32_t)fvar1; + break; + case OPER_OR: + fvar=(uint32_t)fvar|(uint32_t)fvar1; + break; + default: + break; + + } + slp=lp; + lp=getop(lp,&operand); + switch (operand) { + case OPER_EQUEQU: + case OPER_NOTEQU: + case OPER_LOW: + case OPER_LOWEQU: + case OPER_GRT: + case OPER_GRTEQU: + lp=slp; + *fp=fvar; + return lp; + break; + default: + break; + } + lastop=operand; + if (!operand) { + *fp=fvar; + return lp; + } + } +} + + +char *ForceStringVar(char *lp,char *dstr) { + float fvar; + char *slp=lp; + glob_script_mem.glob_error=0; + lp=GetStringResult(lp,OPER_EQU,dstr,0); + if (glob_script_mem.glob_error) { + + lp=GetNumericResult(slp,OPER_EQU,&fvar,0); + dtostrfd(fvar,6,dstr); + glob_script_mem.glob_error=0; + } + return lp; +} + + +void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) { + char *cp; + uint16_t count; + uint8_t vtype; + uint8_t dprec=glob_script_mem.script_dprec; + float fvar; + cp=srcbuf; + struct T_INDEX ind; + char string[SCRIPT_MAXSSIZE]; + dstsize-=2; + for (count=0;count=sizeof(str)) len=len>=sizeof(str); + strlcpy(str,cp,len); + toSLog(str); +} + +void toLogEOL(const char *s1,const char *str) { + if (!str) return; + uint8_t index=0; + char *cp=log_data; + strcpy(cp,s1); + cp+=strlen(s1); + while (*str) { + if (*str==SCRIPT_EOL) break; + *cp++=*str++; + } + *cp=0; + AddLog(LOG_LEVEL_INFO); +} + + +void toSLog(const char *str) { + if (!str) return; +#if SCRIPT_DEBUG>0 + while (*str) { + Serial.write(*str); + str++; + } +#endif +} + +char *Evaluate_expression(char *lp,uint8_t and_or, uint8_t *result,JsonObject *jo) { + float fvar,*dfvar,fvar1; + uint8_t numeric; + struct T_INDEX ind; + uint8_t vtype=0,lastop; + uint8_t res=0; + char *llp=lp; + char *slp; + + SCRIPT_SKIP_SPACES + if (*lp=='(') { + uint8_t res=0; + uint8_t xand_or=0; + lp++; + +loop: + SCRIPT_SKIP_SPACES + lp=Evaluate_expression(lp,xand_or,&res,jo); + if (*lp==')') { + lp++; + goto exit0; + } + + SCRIPT_SKIP_SPACES + if (!strncmp(lp,"or",2)) { + lp+=2; + xand_or=1; + goto loop; + } else if (!strncmp(lp,"and",3)) { + lp+=3; + xand_or=2; + goto loop; + } +exit0: + if (!and_or) { + *result=res; + } else if (and_or==1) { + *result|=res; + } else { + *result&=res; + } + goto exit10; + } + + llp=lp; + + dfvar=&fvar; + glob_script_mem.glob_error=0; + slp=lp; + numeric=1; + lp=GetNumericResult(lp,OPER_EQU,dfvar,0); + if (glob_script_mem.glob_error==1) { + + char cmpstr[SCRIPT_MAXSSIZE]; + lp=slp; + numeric=0; + + lp=isvar(lp,&vtype,&ind,0,cmpstr,0); + lp=getop(lp,&lastop); + + char str[SCRIPT_MAXSSIZE]; + lp=GetStringResult(lp,OPER_EQU,str,jo); + if (lastop==OPER_EQUEQU || lastop==OPER_NOTEQU) { + res=strcmp(cmpstr,str); + if (lastop==OPER_EQUEQU) res=!res; + goto exit; + } + + } else { + + + lp=getop(lp,&lastop); + lp=GetNumericResult(lp,OPER_EQU,&fvar1,jo); + switch (lastop) { + case OPER_EQUEQU: + res=(*dfvar==fvar1); + break; + case OPER_NOTEQU: + res=(*dfvar!=fvar1); + break; + case OPER_LOW: + res=(*dfvarfvar1); + break; + case OPER_GRTEQU: + res=(*dfvar>=fvar1); + break; + default: + + break; + } + +exit: + if (!and_or) { + *result=res; + } else if (and_or==1) { + *result|=res; + } else { + *result&=res; + } + } + + +exit10: +#if SCRIPT_DEBUG>0 + char tbuff[128]; + sprintf(tbuff,"p1=%d,p2=%d,cmpres=%d,and_or=%d line: ",(int32_t)*dfvar,(int32_t)fvar1,*result,and_or); + toLogEOL(tbuff,llp); +#endif + return lp; +} + + + +#define IF_NEST 8 + +int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { + + if (tasm_cmd_activ && tlen>0) return 0; + + uint8_t vtype=0,sindex,xflg,floop=0,globvindex,fromscriptcmd=0; + int8_t globaindex; + struct T_INDEX ind; + uint8_t operand,lastop,numeric=1,if_state[IF_NEST],if_exe[IF_NEST],if_result[IF_NEST],and_or,ifstck=0; + if_state[ifstck]=0; + if_result[ifstck]=0; + if_exe[ifstck]=1; + char cmpstr[SCRIPT_MAXSSIZE]; + uint8_t check=0; + if (tlen<0) { + tlen=abs(tlen); + check=1; + } + + float *dfvar,*cv_count,cv_max,cv_inc; + char *cv_ptr; + float fvar=0,fvar1,sysvar,swvar; + uint8_t section=0,sysv_type=0,swflg=0; + + if (!glob_script_mem.scriptptr) { + return -99; + } + + DynamicJsonBuffer jsonBuffer; + JsonObject &jobj=jsonBuffer.parseObject(js); + JsonObject *jo; + if (js) jo=&jobj; + else jo=0; + + char *lp=glob_script_mem.scriptptr; + + while (1) { + + + startline: + SCRIPT_SKIP_SPACES + + SCRIPT_SKIP_EOL + + if (*lp==';') goto next_line; + if (!*lp) break; + + if (section) { + + if (*lp=='>') { + return 0; + } + if (*lp=='#') { + return 0; + } + glob_script_mem.var_not_found=0; + + +#ifdef IFTHEN_DEBUG + char tbuff[128]; + sprintf(tbuff,"stack=%d,exe=%d,state=%d,cmpres=%d line: ",ifstck,if_exe[ifstck],if_state[ifstck],if_result[ifstck]); + toLogEOL(tbuff,lp); +#endif + + + + + if (!strncmp(lp,"if",2)) { + lp+=2; + if (ifstck=2) { + lp+=5; + if (ifstck>0) { + if_state[ifstck]=0; + ifstck--; + } + goto next_line; + } else if (!strncmp(lp,"or",2) && if_state[ifstck]==1) { + lp+=2; + and_or=1; + } else if (!strncmp(lp,"and",3) && if_state[ifstck]==1) { + lp+=3; + and_or=2; + } + + if (*lp=='{' && if_state[ifstck]==1) { + lp+=1; + if_state[ifstck]=2; + if (if_exe[ifstck-1]) if_exe[ifstck]=if_result[ifstck]; + } else if (*lp=='{' && if_state[ifstck]==3) { + lp+=1; + + } else if (*lp=='}' && if_state[ifstck]>=2) { + lp++; + char *slp=lp; + uint8_t iselse=0; + for (uint8_t count=0; count<8;count++) { + if (*lp=='}') { + + break; + } + if (!strncmp(lp,"else",4)) { + + if_state[ifstck]=3; + if (if_exe[ifstck-1]) if_exe[ifstck]=!if_result[ifstck]; + lp+=4; + iselse=1; + SCRIPT_SKIP_SPACES + if (*lp=='{') lp++; + break; + } + lp++; + } + if (!iselse) { + lp=slp; + + if (ifstck>0) { + if_state[ifstck]=0; + ifstck--; + } + goto next_line; + } + } + + if (!strncmp(lp,"for",3)) { + + + lp+=3; + SCRIPT_SKIP_SPACES + lp=isvar(lp,&vtype,&ind,0,0,0); + if ((vtype!=VAR_NV) && (vtype&STYPE)==0) { + + uint8_t index=glob_script_mem.type[ind.index].index; + cv_count=&glob_script_mem.fvars[index]; + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,cv_count,0); + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&cv_max,0); + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&cv_inc,0); + + cv_ptr=lp; + floop=1; + } else { + + toLogEOL("for error",lp); + } + } else if (!strncmp(lp,"next",4) && floop>0) { + + *cv_count+=cv_inc; + if (*cv_count<=cv_max) { + lp=cv_ptr; + } else { + lp+=4; + floop=0; + } + } + + if (!strncmp(lp,"switch",6)) { + lp+=6; + SCRIPT_SKIP_SPACES + char *slp=lp; + lp=GetNumericResult(lp,OPER_EQU,&swvar,0); + if (glob_script_mem.glob_error==1) { + + lp=slp; + + lp=isvar(lp,&vtype,&ind,0,cmpstr,0); + swflg=0x81; + } else { + swflg=1; + } + } else if (!strncmp(lp,"case",4) && swflg>0) { + lp+=4; + SCRIPT_SKIP_SPACES + float cvar; + if (!(swflg&0x80)) { + lp=GetNumericResult(lp,OPER_EQU,&cvar,0); + if (swvar!=cvar) { + swflg=2; + } else { + swflg=1; + } + } else { + char str[SCRIPT_MAXSSIZE]; + lp=GetStringResult(lp,OPER_EQU,str,0); + if (!strcmp(cmpstr,str)) { + swflg=0x81; + } else { + swflg=0x82; + } + } + } else if (!strncmp(lp,"ends",4) && swflg>0) { + lp+=4; + swflg=0; + } + if ((swflg&3)==2) goto next_line; + + SCRIPT_SKIP_SPACES + + if (*lp==SCRIPT_EOL) { + goto next_line; + } + + + if (!if_exe[ifstck] && if_state[ifstck]!=1) goto next_line; + +#ifdef IFTHEN_DEBUG + sprintf(tbuff,"stack=%d,exe=%d,state=%d,cmpres=%d execute line: ",ifstck,if_exe[ifstck],if_state[ifstck],if_result[ifstck]); + toLogEOL(tbuff,lp); +#endif + + if (!strncmp(lp,"break",5)) { + if (floop) { + + floop=0; + } else { + section=0; + } + break; + } else if (!strncmp(lp,"dp",2) && isdigit(*(lp+2))) { + lp+=2; + + glob_script_mem.script_dprec=atoi(lp); + goto next_line; + } else if (!strncmp(lp,"delay(",6)) { + lp+=5; + + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + delay(fvar); + goto next_line; + } else if (!strncmp(lp,"spinm(",6)) { + lp+=6; + + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + int8_t pinnr=fvar; + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + int8_t mode=fvar; + pinMode(pinnr,mode&3); + goto next_line; + } else if (!strncmp(lp,"spin(",5)) { + lp+=5; + + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + int8_t pinnr=fvar; + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + int8_t mode=fvar; + digitalWrite(pinnr,mode&1); + goto next_line; + } else if (!strncmp(lp,"svars(",5)) { + lp+=5; + + Scripter_save_pvars(); + goto next_line; + } +#ifdef USE_LIGHT +#ifdef USE_WS2812 + else if (!strncmp(lp,"ws2812(",7)) { + lp+=7; + lp=isvar(lp,&vtype,&ind,0,0,0); + if (vtype!=VAR_NV) { + + uint8_t index=glob_script_mem.type[ind.index].index; + if ((vtype&STYPE)==0) { + + if (glob_script_mem.type[index].bits.is_filter) { + uint8_t len=0; + float *fa=Get_MFAddr(index,&len); + + if (fa && len) ws2812_set_array(fa,len); + } + } + } + goto next_line; + } +#endif +#endif + + else if (!strncmp(lp,"=>",2) || !strncmp(lp,"->",2) || !strncmp(lp,"+>",2) || !strncmp(lp,"print",5)) { + + uint8_t sflag=0,pflg=0,svmqtt,swll; + if (*lp=='p') { + pflg=1; + lp+=5; + } + else { + if (*lp=='-') sflag=1; + if (*lp=='+') sflag=2; + lp+=2; + } + char *slp=lp; + SCRIPT_SKIP_SPACES + #define SCRIPT_CMDMEM 512 + char *cmdmem=(char*)malloc(SCRIPT_CMDMEM); + if (cmdmem) { + char *cmd=cmdmem; + uint16_t count; + for (count=0; count=0) { + Set_MFVal(glob_script_mem.type[globvindex].index,globaindex,*dfvar); + } else { + Set_MFilter(glob_script_mem.type[globvindex].index,*dfvar); + } + } + + if (sysv_type) { + switch (sysv_type) { + case SCRIPT_LOGLEVEL: + glob_script_mem.script_loglevel=*dfvar; + break; + case SCRIPT_TELEPERIOD: + if (*dfvar<10) *dfvar=10; + if (*dfvar>300) *dfvar=300; + Settings.tele_period=*dfvar; + break; + } + sysv_type=0; + } + } else { + + numeric=0; + sindex=index; + + char str[SCRIPT_MAXSSIZE]; + lp=getop(lp,&lastop); + char *slp=lp; + glob_script_mem.glob_error=0; + lp=GetStringResult(lp,OPER_EQU,str,jo); + if (!js && glob_script_mem.glob_error) { + + lp=GetNumericResult(slp,OPER_EQU,&fvar,0); + dtostrfd(fvar,6,str); + glob_script_mem.glob_error=0; + } + + if (!glob_script_mem.var_not_found) { + + glob_script_mem.type[globvindex].bits.changed=1; + if (lastop==OPER_EQU) { + strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize); + } else if (lastop==OPER_PLSEQU) { + strncat(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize); + } + } + } + + } + SCRIPT_SKIP_SPACES + if (*lp=='{' && if_state[ifstck]==3) { + lp+=1; + + } + goto next_line; + } + } else { + + if (*lp=='>' && tlen==1) { + + lp++; + section=1; + fromscriptcmd=1; + goto startline; + } + if (!strncmp(lp,type,tlen)) { + + section=1; + glob_script_mem.section_ptr=lp; + if (check) { + return 99; + } + + char *ctype=(char*)type; + if (*ctype=='#') { + + ctype+=tlen; + if (*ctype=='(' && *(lp+tlen)=='(') { + float fparam; + numeric=1; + glob_script_mem.glob_error=0; + GetNumericResult((char*)ctype,OPER_EQU,&fparam,0); + if (glob_script_mem.glob_error==1) { + + numeric=0; + + GetStringResult((char*)ctype+1,OPER_EQU,cmpstr,0); + } + lp+=tlen; + if (*lp=='(') { + + lp++; + lp=isvar(lp,&vtype,&ind,0,0,0); + if (vtype!=VAR_NV) { + + uint8_t index=glob_script_mem.type[ind.index].index; + if ((vtype&STYPE)==0) { + + dfvar=&glob_script_mem.fvars[index]; + if (numeric) { + *dfvar=fparam; + } else { + + *dfvar=CharToFloat(cmpstr); + } + } else { + + sindex=index; + if (!numeric) { + strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),cmpstr,glob_script_mem.max_ssize); + } else { + + dtostrfd(fparam,6,glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize)); + } + } + } + } + } else { + lp+=tlen; + if (*ctype=='(' || (*lp!=SCRIPT_EOL && *lp!='?')) { + + section=0; + } + } + } + } + } + + next_line: + if (*lp==SCRIPT_EOL) { + lp++; + } else { + lp = strchr(lp, SCRIPT_EOL); + if (!lp) { + if (section) { + return 0; + } else { + return -1; + } + } + lp++; + } + } + return -1; +} + +uint8_t script_xsns_index = 0; + + +void ScripterEvery100ms(void) { + + if (Settings.rule_enabled && (uptime > 4)) { + mqtt_data[0] = '\0'; + uint16_t script_tele_period_save = tele_period; + tele_period = 2; + XsnsNextCall(FUNC_JSON_APPEND, script_xsns_index); + tele_period = script_tele_period_save; + if (strlen(mqtt_data)) { + mqtt_data[0] = '{'; + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); + Run_Scripter(">T",2, mqtt_data); + } + } + if (fast_script==99) Run_Scripter(">F",2,0); +} + + + + +void Scripter_save_pvars(void) { + int16_t mlen=0; + float *fp=(float*)glob_script_mem.script_pram; + mlen+=sizeof(float); + struct T_INDEX *vtp=glob_script_mem.type; + for (uint8_t count=0; countPMEM_SIZE) { + vtp[count].bits.is_permanent=0; + return; + } + *fp++=glob_script_mem.fvars[index]; + } + } + char *cp=(char*)fp; + for (uint8_t count=0; countPMEM_SIZE) { + vtp[count].bits.is_permanent=0; + return; + } + strcpy(cp,sp); + cp+=slen+1; + } + } +} + + +#ifdef USE_WEBSERVER + +#define WEB_HANDLE_SCRIPT "s10" + +const char S_CONFIGURE_SCRIPT[] PROGMEM = D_CONFIGURE_SCRIPT; + +const char HTTP_BTN_MENU_RULES[] PROGMEM = + "

"; + + +const char HTTP_FORM_SCRIPT[] PROGMEM = + "
 " D_SCRIPT " " + "
"; + +const char HTTP_FORM_SCRIPT1[] PROGMEM = + "
" + "" D_SCRIPT_ENABLE "
" + "
" + ""; + +const char HTTP_SCRIPT_FORM_END[] PROGMEM = + "
" + "" + "
"; + +#ifdef USE_SCRIPT_FATFS +const char HTTP_FORM_SCRIPT1c[] PROGMEM = + ""; +#ifdef SDCARD_DIR +const char HTTP_FORM_SCRIPT1d[] PROGMEM = + ""; +#else +const char HTTP_FORM_SCRIPT1d[] PROGMEM = + ""; +#endif + +#ifdef SDCARD_DIR +const char S_SCRIPT_FILE_UPLOAD[] PROGMEM = D_SDCARD_DIR; +#else +const char S_SCRIPT_FILE_UPLOAD[] PROGMEM = D_SDCARD_UPLOAD; +#endif + +const char HTTP_FORM_FILE_UPLOAD[] PROGMEM = +"
" +"
 %s" " "; +const char HTTP_FORM_FILE_UPG[] PROGMEM = +"
" +"

" +"
"; + +const char HTTP_FORM_FILE_UPGb[] PROGMEM = +"
" +"
" +""; + +const char HTTP_FORM_SDC_DIRa[] PROGMEM = +"
"; +const char HTTP_FORM_SDC_DIRb[] PROGMEM = + "
%s    %d
"; +const char HTTP_FORM_SDC_DIRd[] PROGMEM = +"
%s
"; +const char HTTP_FORM_SDC_DIRc[] PROGMEM = +"
"; +const char HTTP_FORM_SDC_HREF[] PROGMEM = +"http://%s/upl?download=%s/%s"; +#endif + + + +#ifdef USE_SCRIPT_FATFS + +#if USE_LONG_FILE_NAMES>0 +#undef REJCMPL +#define REJCMPL 6 +#else +#undef REJCMPL +#define REJCMPL 8 +#endif + +uint8_t reject(char *name) { + + if (*name=='_') return 1; + if (*name=='.') return 1; + +#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 + if (!strncasecmp(name,"SPOTLI~1",REJCMPL)) return 1; + if (!strncasecmp(name,"TRASHE~1",REJCMPL)) return 1; + if (!strncasecmp(name,"FSEVEN~1",REJCMPL)) return 1; + if (!strncasecmp(name,"SYSTEM~1",REJCMPL)) return 1; +#else + if (!strcasecmp(name,"SPOTLI~1")) return 1; + if (!strcasecmp(name,"TRASHE~1")) return 1; + if (!strcasecmp(name,"FSEVEN~1")) return 1; + if (!strcasecmp(name,"SYSTEM~1")) return 1; +#endif + return 0; +} + +void ListDir(char *path, uint8_t depth) { + char name[32]; + char npath[128]; + char format[12]; + sprintf(format,"%%-%ds",24-depth); + + File dir=SD.open(path); + if (dir) { + dir.rewindDirectory(); + if (strlen(path)>1) { + snprintf_P(npath,sizeof(npath),PSTR("http://%s/upl?download=%s"),WiFi.localIP().toString().c_str(),path); + for (uint8_t cnt=strlen(npath)-1;cnt>0;cnt--) { + if (npath[cnt]=='/') { + if (npath[cnt-1]=='=') npath[cnt+1]=0; + else npath[cnt]=0; + break; + } + } + WSContentSend_P(HTTP_FORM_SDC_DIRd,npath,path,".."); + } + while (true) { + File entry=dir.openNextFile(); + if (!entry) { + break; + } + char *pp=path; + if (!*(pp+1)) pp++; + char *cp=name; + + if (reject((char*)entry.name())) goto fclose; + + for (uint8_t cnt=0;cnt1) { + strcat(path,"/"); + } + strcat(path,entry.name()); + ListDir(path,depth+4); + path[plen]=0; + } else { + snprintf_P(npath,sizeof(npath),HTTP_FORM_SDC_HREF,WiFi.localIP().toString().c_str(),pp,entry.name()); + WSContentSend_P(HTTP_FORM_SDC_DIRb,npath,entry.name(),name,entry.size()); + } + fclose: + entry.close(); + } + dir.close(); + } +} + +char path[48]; + +void Script_FileUploadConfiguration(void) +{ + uint8_t depth=0; + strcpy(path,"/"); + + if (!HttpCheckPriviledgedAccess()) { return; } + + if (WebServer->hasArg("download")) { + String stmp = WebServer->arg("download"); + char *cp=(char*)stmp.c_str(); + if (DownloadFile(cp)) { + + strcpy(path,cp); + } + } + + WSContentStart_P(S_SCRIPT_FILE_UPLOAD); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_FILE_UPLOAD,D_SDCARD_DIR); + WSContentSend_P(HTTP_FORM_FILE_UPG, D_SCRIPT_UPLOAD); +#ifdef SDCARD_DIR + WSContentSend_P(HTTP_FORM_SDC_DIRa); + if (glob_script_mem.script_sd_found) { + ListDir(path,depth); + } + WSContentSend_P(HTTP_FORM_SDC_DIRc); +#endif + WSContentSend_P(HTTP_FORM_FILE_UPGb); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); + Web.upload_error = 0; +} + +File upload_file; + +void ScriptFileUploadSuccess(void) { + WSContentStart_P(S_INFORMATION); + WSContentSendStyle(); + WSContentSend_P(PSTR("
" D_UPLOAD " " D_SUCCESSFUL "
"), WebColor(COL_TEXT_SUCCESS)); + WSContentSend_P(PSTR("

")); + WSContentSend_P(PSTR("

"),"/upl",D_UPL_DONE); + + WSContentStop(); +} + + + +void script_upload(void) { + + + + HTTPUpload& upload = WebServer->upload(); + if (upload.status == UPLOAD_FILE_START) { + char npath[48]; + sprintf(npath,"%s/%s",path,upload.filename.c_str()); + SD.remove(npath); + upload_file=SD.open(npath,FILE_WRITE); + if (!upload_file) Web.upload_error=1; + } else if(upload.status == UPLOAD_FILE_WRITE) { + if (upload_file) upload_file.write(upload.buf,upload.currentSize); + } else if(upload.status == UPLOAD_FILE_END) { + if (upload_file) upload_file.close(); + if (Web.upload_error) { + AddLog_P(LOG_LEVEL_INFO, PSTR("HTP: upload error")); + } + } else { + Web.upload_error=1; + WebServer->send(500, "text/plain", "500: couldn't create file"); + } +} + +uint8_t DownloadFile(char *file) { + File download_file; + WiFiClient download_Client; + + if (!SD.exists(file)) { + AddLog_P(LOG_LEVEL_INFO,PSTR("file not found")); + return 0; + } + + download_file=SD.open(file,FILE_READ); + if (!download_file) { + AddLog_P(LOG_LEVEL_INFO,PSTR("could not open file")); + return 0; + } + + if (download_file.isDirectory()) { + download_file.close(); + return 1; + } + + uint32_t flen=download_file.size(); + + download_Client = WebServer->client(); + WebServer->setContentLength(flen); + + char attachment[100]; + char *cp; + for (uint8_t cnt=strlen(file); cnt>=0; cnt--) { + if (file[cnt]=='/') { + cp=&file[cnt+1]; + break; + } + } + snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"),cp); + WebServer->sendHeader(F("Content-Disposition"), attachment); + WSSend(200, CT_STREAM, ""); + + uint8_t buff[512]; + uint16_t bread; + + + uint8_t cnt=0; + while (download_file.available()) { + bread=download_file.read(buff,sizeof(buff)); + uint16_t bw=download_Client.write((const char*)buff,bread); + if (!bw) break; + cnt++; + if (cnt>7) { + cnt=0; + if (glob_script_mem.script_loglevel&0x80) { + + loop(); + } + } + } + download_file.close(); + download_Client.stop(); + return 0; +} + +#endif + + +void HandleScriptTextareaConfiguration(void) { + if (!HttpCheckPriviledgedAccess()) { return; } + + if (WebServer->hasArg("save")) { + ScriptSaveSettings(); + HandleConfiguration(); + return; + } +} + +void HandleScriptConfiguration(void) { + + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_SCRIPT); + +#ifdef USE_SCRIPT_FATFS + if (WebServer->hasArg("d1")) { + DownloadFile(glob_script_mem.flink[0]); + } + if (WebServer->hasArg("d2")) { + DownloadFile(glob_script_mem.flink[1]); + } + if (WebServer->hasArg("upl")) { + Script_FileUploadConfiguration(); + } +#endif + + WSContentStart_P(S_CONFIGURE_SCRIPT); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_SCRIPT); + + +#ifdef xSCRIPT_STRIP_COMMENTS + uint16_t ssize=glob_script_mem.script_size; + if (bitRead(Settings.rule_enabled, 1)) ssize*=2; + WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? " checked" : "",ssize); +#else + WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? " checked" : "",glob_script_mem.script_size); +#endif + + + if (glob_script_mem.script_ram[0]) { + _WSContentSend(glob_script_mem.script_ram); + } + WSContentSend_P(HTTP_FORM_SCRIPT1b); + +#ifdef USE_SCRIPT_FATFS + if (glob_script_mem.script_sd_found) { + WSContentSend_P(HTTP_FORM_SCRIPT1d); + if (glob_script_mem.flink[0][0]) WSContentSend_P(HTTP_FORM_SCRIPT1c,1,glob_script_mem.flink[0]); + if (glob_script_mem.flink[1][0]) WSContentSend_P(HTTP_FORM_SCRIPT1c,2,glob_script_mem.flink[1]); + } +#endif + + WSContentSend_P(HTTP_SCRIPT_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); + } + + +void ScriptSaveSettings(void) { + + if (WebServer->hasArg("c1")) { + bitWrite(Settings.rule_enabled,0,1); + } else { + bitWrite(Settings.rule_enabled,0,0); + } + + + String str = WebServer->arg("t1"); + + if (*str.c_str()) { + + str.replace("\r\n","\n"); + str.replace("\r","\n"); + +#ifdef xSCRIPT_STRIP_COMMENTS + if (bitRead(Settings.rule_enabled, 1)) { + char *sp=(char*)str.c_str(); + char *sp1=sp; + char *dp=sp; + uint8_t flg=0; + while (*sp) { + while (*sp==' ') sp++; + sp1=sp; + sp=strchr(sp,'\n'); + if (!sp) { + flg=1; + } else { + *sp=0; + } + if (*sp1!=';') { + uint8_t slen=strlen(sp1); + if (slen) { + strcpy(dp,sp1); + dp+=slen; + *dp++='\n'; + } + } + if (flg) { + *dp=0; + break; + } + sp++; + } + } +#endif + + strlcpy(glob_script_mem.script_ram,str.c_str(), glob_script_mem.script_size); + +#ifdef USE_24C256 +#ifndef USE_SCRIPT_FATFS + if (glob_script_mem.flags&1) { + EEP_WRITE(0,EEP_SCRIPT_SIZE,glob_script_mem.script_ram); + } +#endif +#endif + +#ifdef USE_SCRIPT_FATFS + if (glob_script_mem.flags&1) { + SD.remove(FAT_SCRIPT_NAME); + File file=SD.open(FAT_SCRIPT_NAME,FILE_WRITE); + file.write(glob_script_mem.script_ram,FAT_SCRIPT_SIZE); + file.close(); + } +#endif + + } + + if (glob_script_mem.script_mem) { + Scripter_save_pvars(); + free(glob_script_mem.script_mem); + glob_script_mem.script_mem=0; + glob_script_mem.script_mem_size=0; + } + + if (bitRead(Settings.rule_enabled, 0)) { + int16_t res=Init_Scripter(); + if (res) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("script init error: %d"), res); + return; + } + Run_Scripter(">B",2,0); + fast_script=Run_Scripter(">F",-2,0); + } +} + +#endif + + +#if defined(USE_SCRIPT_HUE) && defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT) + + +#define HUE_DEV_MVNUM 5 +#define HUE_DEV_NSIZE 16 +struct HUE_SCRIPT { + char name[HUE_DEV_NSIZE]; + uint8_t type; + uint8_t index[HUE_DEV_MVNUM]; + uint8_t vindex[HUE_DEV_MVNUM]; +} hue_script[32]; + + +const char SCRIPT_HUE_LIGHTS_STATUS_JSON1[] PROGMEM = + "{\"state\":" + "{\"on\":{state}," + "{light_status}" + "\"alert\":\"none\"," + "\"effect\":\"none\"," + "\"reachable\":true}" + ",\"type\":\"{type}\"," + "\"name\":\"{j1\"," + "\"modelid\":\"{m1}\"," + "\"uniqueid\":\"{j2\"," + "\"swversion\":\"5.50.1.19085\"}"; +# 3624 "S:/Development/Tasmota/tasmota/xdrv_10_scripter.ino" +const char SCRIPT_HUE_LIGHTS_STATUS_JSON2[] PROGMEM = +"{\"state\":{" +"\"presence\":{state}," +"\"lastupdated\":\"2017-10-01T12:37:30\"" +"}," +"\"swupdate\":{" +"\"state\":\"noupdates\"," +"\"lastinstall\": null" +"}," +"\"config\":{" +"\"on\":true," +"\"battery\":100," +"\"reachable\":true," +"\"alert\":\"none\"," +"\"ledindication\":false," +"\"usertest\":false," +"\"sensitivity\":2," +"\"sensitivitymax\":2," +"\"pending\":[]" +"}," +"\"name\":\"{j1\"," +"\"type\":\"ZLLPresence\"," +"\"modelid\":\"SML001\"," +"\"manufacturername\":\"Philips\"," +"\"swversion\":\"6.1.0.18912\"," +"\"uniqueid\":\"{j2\"" +"}"; +# 3705 "S:/Development/Tasmota/tasmota/xdrv_10_scripter.ino" +void Script_HueStatus(String *response, uint16_t hue_devs) { + + if (hue_script[hue_devs].type=='p') { + *response+=FPSTR(SCRIPT_HUE_LIGHTS_STATUS_JSON2); + response->replace("{j1",hue_script[hue_devs].name); + response->replace("{j2", GetHueDeviceId(hue_devs)); + uint8_t pwr=glob_script_mem.fvars[hue_script[hue_devs].index[0]-1]; + response->replace("{state}", (pwr ? "true" : "false")); + return; + } + + *response+=FPSTR(SCRIPT_HUE_LIGHTS_STATUS_JSON1); + uint8_t pwr=glob_script_mem.fvars[hue_script[hue_devs].index[0]-1]; + response->replace("{state}", (pwr ? "true" : "false")); + String light_status = ""; + if (hue_script[hue_devs].index[1]>0) { + + light_status += "\"bri\":"; + uint32_t bri=glob_script_mem.fvars[hue_script[hue_devs].index[1]-1]; + if (bri > 254) bri = 254; + if (bri < 1) bri = 1; + light_status += String(bri); + light_status += ","; + } + if (hue_script[hue_devs].index[2]>0) { + + uint32_t hue=glob_script_mem.fvars[hue_script[hue_devs].index[2]-1]; + + light_status += "\"hue\":"; + light_status += String(hue); + light_status += ","; + } + if (hue_script[hue_devs].index[3]>0) { + + uint32_t sat=glob_script_mem.fvars[hue_script[hue_devs].index[3]-1] ; + if (sat > 254) sat = 254; + if (sat < 1) sat = 1; + light_status += "\"sat\":"; + light_status += String(sat); + light_status += ","; + } + if (hue_script[hue_devs].index[4]>0) { + + uint32_t ct=glob_script_mem.fvars[hue_script[hue_devs].index[4]-1]; + light_status += "\"ct\":"; + light_status += String(ct); + light_status += ","; + } + + float temp; + switch (hue_script[hue_devs].type) { + case 'C': + response->replace("{type}","Color Ligh"); + response->replace("{m1","LST001"); + break; + case 'D': + response->replace("{type}","Dimmable Light"); + response->replace("{m1","LWB004"); + break; + case 'T': + response->replace("{type}","Color Temperature Light"); + response->replace("{m1","LTW011"); + break; + case 'E': + response->replace("{type}","Extended color light"); + response->replace("{m1","LCT007"); + break; + case 'S': + response->replace("{type}","On/Off light"); + response->replace("{m1","LCT007"); + break; + default: + response->replace("{type}","color light"); + response->replace("{m1","LST001"); + break; + } + + response->replace("{light_status}", light_status); + response->replace("{j1",hue_script[hue_devs].name); + response->replace("{j2", GetHueDeviceId(hue_devs)); + +} + +void Script_Check_Hue(String *response) { + if (!bitRead(Settings.rule_enabled, 0)) return; + + uint8_t hue_script_found=Run_Scripter(">H",-2,0); + if (hue_script_found!=99) return; + + char line[128]; + char tmp[128]; + uint8_t hue_devs=0; + uint8_t vindex=0; + char *cp; + char *lp=glob_script_mem.section_ptr+2; + while (lp) { + SCRIPT_SKIP_SPACES + while (*lp==SCRIPT_EOL) { + lp++; + } + if (!*lp || *lp=='#' || *lp=='>') { + break; + } + if (*lp!=';') { + + memcpy(line,lp,sizeof(line)); + line[sizeof(line)-1]=0; + cp=line; + for (uint32_t i=0; i0) *response+=",\""; + } + *response+=String(EncodeLightId(hue_devs+devices_present+1))+"\":"; + Script_HueStatus(response,hue_devs); + } + + hue_devs++; + } + if (*lp==SCRIPT_EOL) { + lp++; + } else { + lp = strchr(lp, SCRIPT_EOL); + if (!lp) break; + lp++; + } + } +#if 0 + if (response) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Hue: %d"), hue_devs); + toLog(">>>>"); + toLog(response->c_str()); + toLog(response->c_str()+LOGSZ); + } +#endif +} + +const char sHUE_LIGHT_RESPONSE_JSON[] PROGMEM = + "{\"success\":{\"/lights/{id/state/{cm\":{re}}"; + +const char sHUE_SENSOR_RESPONSE_JSON[] PROGMEM = + "{\"success\":{\"/lights/{id/state/{cm\":{re}}"; + +const char sHUE_ERROR_JSON[] PROGMEM = + "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"; + + + +void Script_Handle_Hue(String *path) { + String response; + int code = 200; + uint16_t tmp = 0; + uint16_t hue = 0; + uint8_t sat = 0; + uint8_t bri = 254; + uint16_t ct = 0; + bool resp = false; + + uint8_t device = DecodeLightId(atoi(path->c_str())); + uint8_t index = device-devices_present-1; + + if (WebServer->args()) { + response = "["; + + StaticJsonBuffer<400> jsonBuffer; + JsonObject &hue_json = jsonBuffer.parseObject(WebServer->arg((WebServer->args())-1)); + if (hue_json.containsKey("on")) { + + response += FPSTR(sHUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(EncodeLightId(device))); + response.replace("{cm", "on"); + + bool on = hue_json["on"]; + switch(on) + { + case false : glob_script_mem.fvars[hue_script[index].index[0]-1]=0; + response.replace("{re", "false"); + break; + case true : glob_script_mem.fvars[hue_script[index].index[0]-1]=1; + response.replace("{re", "true"); + break; + } + glob_script_mem.type[hue_script[index].vindex[0]].bits.changed=1; + resp = true; + } + if (hue_json.containsKey("bri")) { + tmp = hue_json["bri"]; + bri=tmp; + if (254 <= bri) { bri = 255; } + if (resp) { response += ","; } + response += FPSTR(sHUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(EncodeLightId(device))); + response.replace("{cm", "bri"); + response.replace("{re", String(tmp)); + glob_script_mem.fvars[hue_script[index].index[1]-1]=bri; + glob_script_mem.type[hue_script[index].vindex[1]].bits.changed=1; + resp = true; + } + if (hue_json.containsKey("xy")) { + float x, y; + x = hue_json["xy"][0]; + y = hue_json["xy"][1]; + const String &x_str = hue_json["xy"][0]; + const String &y_str = hue_json["xy"][1]; + uint8_t rr,gg,bb; + LightStateClass::XyToRgb(x, y, &rr, &gg, &bb); + LightStateClass::RgbToHsb(rr, gg, bb, &hue, &sat, nullptr); + if (resp) { response += ","; } + response += FPSTR(sHUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(device)); + response.replace("{cm", "xy"); + response.replace("{re", "[" + x_str + "," + y_str + "]"); + glob_script_mem.fvars[hue_script[index].index[2]-1]=hue; + glob_script_mem.type[hue_script[index].vindex[2]].bits.changed=1; + glob_script_mem.fvars[hue_script[index].index[3]-1]=sat; + glob_script_mem.type[hue_script[index].vindex[3]].bits.changed=1; + resp = true; + } + + if (hue_json.containsKey("hue")) { + tmp = hue_json["hue"]; + + + hue=tmp; + if (resp) { response += ","; } + response += FPSTR(sHUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(EncodeLightId(device))); + response.replace("{cm", "hue"); + response.replace("{re", String(tmp)); + glob_script_mem.fvars[hue_script[index].index[2]-1]=hue; + glob_script_mem.type[hue_script[index].vindex[2]].bits.changed=1; + resp = true; + } + if (hue_json.containsKey("sat")) { + tmp = hue_json["sat"]; + sat=tmp; + if (254 <= sat) { sat = 255; } + if (resp) { response += ","; } + response += FPSTR(sHUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(EncodeLightId(device))); + response.replace("{cm", "sat"); + response.replace("{re", String(tmp)); + glob_script_mem.fvars[hue_script[index].index[3]-1]=sat; + glob_script_mem.type[hue_script[index].vindex[3]].bits.changed=1; + resp = true; + } + if (hue_json.containsKey("ct")) { + ct = hue_json["ct"]; + if (resp) { response += ","; } + response += FPSTR(sHUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(EncodeLightId(device))); + response.replace("{cm", "ct"); + response.replace("{re", String(ct)); + glob_script_mem.fvars[hue_script[index].index[4]-1]=ct; + glob_script_mem.type[hue_script[index].vindex[4]].bits.changed=1; + resp = true; + } + response += "]"; + + } else { + response = FPSTR(sHUE_ERROR_JSON); + } + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str()); + WSSend(code, CT_JSON, response); + if (resp) { + Run_Scripter(">E",2,0); + } +} +#endif + + +#ifdef USE_SCRIPT_SUB_COMMAND +bool Script_SubCmd(void) { + if (!bitRead(Settings.rule_enabled, 0)) return false; + + if (tasm_cmd_activ) return false; + + char command[CMDSZ]; + strlcpy(command,XdrvMailbox.topic,CMDSZ); + uint32_t pl=XdrvMailbox.payload; + char pld[64]; + strlcpy(pld,XdrvMailbox.data,sizeof(pld)); + + char cmdbuff[128]; + char *cp=cmdbuff; + *cp++='#'; + strcpy(cp,XdrvMailbox.topic); + uint8_t tlen=strlen(XdrvMailbox.topic); + cp+=tlen; + if (XdrvMailbox.index > 0) { + *cp++=XdrvMailbox.index|0x30; + tlen++; + } + if ((XdrvMailbox.payload>0) || (XdrvMailbox.data_len>0)) { + *cp++='('; + strncpy(cp,XdrvMailbox.data,XdrvMailbox.data_len); + cp+=XdrvMailbox.data_len; + *cp++=')'; + *cp=0; + } + + uint32_t res=Run_Scripter(cmdbuff,tlen+1,0); + + if (res) return false; + else { + if (pl>=0) { + Response_P(S_JSON_COMMAND_NVALUE, command, pl); + } else { + Response_P(S_JSON_COMMAND_SVALUE, command, pld); + } + } + return true; +} +#endif + +void execute_script(char *script) { + char *svd_sp=glob_script_mem.scriptptr; + strcat(script,"\n#"); + glob_script_mem.scriptptr=script; + Run_Scripter(">",1,0); + glob_script_mem.scriptptr=svd_sp; +} +#define D_CMND_SCRIPT "Script" +#define D_CMND_SUBSCRIBE "Subscribe" +#define D_CMND_UNSUBSCRIBE "Unsubscribe" + +enum ScriptCommands { CMND_SCRIPT,CMND_SUBSCRIBE, CMND_UNSUBSCRIBE }; +const char kScriptCommands[] PROGMEM = D_CMND_SCRIPT "|" D_CMND_SUBSCRIBE "|" D_CMND_UNSUBSCRIBE; + +bool ScriptCommand(void) { + char command[CMDSZ]; + bool serviced = true; + uint8_t index = XdrvMailbox.index; + + if (tasm_cmd_activ) return false; + + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kScriptCommands); + if (-1 == command_code) { + serviced = false; + } + else if ((CMND_SCRIPT == command_code) && (index > 0)) { + + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 4)) { + switch (XdrvMailbox.payload) { + case 0: + case 1: + bitWrite(Settings.rule_enabled, index -1, XdrvMailbox.payload); + break; +#ifdef xSCRIPT_STRIP_COMMENTS + case 2: + bitWrite(Settings.rule_enabled, 1,0); + break; + case 3: + bitWrite(Settings.rule_enabled, 1,1); + break; +#endif + } + } else { + if ('>' == XdrvMailbox.data[0]) { + + snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\"}"),command,XdrvMailbox.data); + if (bitRead(Settings.rule_enabled, 0)) { + for (uint8_t count=0; count> 1; +} + +void dateTime(uint16_t* date, uint16_t* time) { + + *date = xFAT_DATE(RtcTime.year,RtcTime.month, RtcTime.day_of_month); + + *time = xFAT_TIME(RtcTime.hour,RtcTime.minute,RtcTime.second); +} + +#endif + + + +#ifdef SUPPORT_MQTT_EVENT +# 4199 "S:/Development/Tasmota/tasmota/xdrv_10_scripter.ino" +bool ScriptMqttData(void) +{ + bool serviced = false; + + toLog(XdrvMailbox.data); + if (XdrvMailbox.data_len < 1 || XdrvMailbox.data_len > 256) { + return false; + } + String sTopic = XdrvMailbox.topic; + String sData = XdrvMailbox.data; + + MQTT_Subscription event_item; + + for (uint32_t index = 0; index < subscriptions.size(); index++) { + event_item = subscriptions.get(index); + + + if (sTopic.startsWith(event_item.Topic)) { + + serviced = true; + String value; + String lkey; + if (event_item.Key.length() == 0) { + value = sData; + } else { + StaticJsonBuffer<400> jsonBuf; + JsonObject& jsonData = jsonBuf.parseObject(sData); + String key1 = event_item.Key; + String key2; + if (!jsonData.success()) break; + int dot; + if ((dot = key1.indexOf('.')) > 0) { + key2 = key1.substring(dot+1); + key1 = key1.substring(0, dot); + lkey=key2; + if (!jsonData[key1][key2].success()) break; + value = (const char *)jsonData[key1][key2]; + } else { + if (!jsonData[key1].success()) break; + value = (const char *)jsonData[key1]; + lkey=key1; + } + } + value.trim(); + char sbuffer[128]; + + if (!strncmp(lkey.c_str(),"Epoch",5)) { + uint32_t ep=atoi(value.c_str())-(uint32_t)EPOCH_OFFSET; + snprintf_P(sbuffer, sizeof(sbuffer), PSTR(">%s=%d\n"), event_item.Event.c_str(),ep); + } else { + snprintf_P(sbuffer, sizeof(sbuffer), PSTR(">%s=\"%s\"\n"), event_item.Event.c_str(), value.c_str()); + } + + execute_script(sbuffer); + } + } + return serviced; +} +# 4274 "S:/Development/Tasmota/tasmota/xdrv_10_scripter.ino" +String ScriptSubscribe(const char *data, int data_len) +{ + MQTT_Subscription subscription_item; + String events; + if (data_len > 0) { + char parameters[data_len+1]; + memcpy(parameters, data, data_len); + parameters[data_len] = '\0'; + String event_name, topic, key; + + char * pos = strtok(parameters, ","); + if (pos) { + event_name = Trim(pos); + pos = strtok(nullptr, ","); + if (pos) { + topic = Trim(pos); + pos = strtok(nullptr, ","); + if (pos) { + key = Trim(pos); + } + } + } + + + if (event_name.length() > 0 && topic.length() > 0) { + + for (uint32_t index=0; index < subscriptions.size(); index++) { + if (subscriptions.get(index).Event.equals(event_name)) { + + String stopic = subscriptions.get(index).Topic + "/#"; + MqttUnsubscribe(stopic.c_str()); + subscriptions.remove(index); + break; + } + } + + if (!topic.endsWith("#")) { + if (topic.endsWith("/")) { + topic.concat("#"); + } else { + topic.concat("/#"); + } + } + + + subscription_item.Event = event_name; + subscription_item.Topic = topic.substring(0, topic.length() - 2); + subscription_item.Key = key; + subscriptions.add(subscription_item); + + MqttSubscribe(topic.c_str()); + events.concat(event_name + "," + topic + + (key.length()>0 ? "," : "") + + key); + } else { + events = D_JSON_WRONG_PARAMETERS; + } + } else { + + for (uint32_t index=0; index < subscriptions.size(); index++) { + subscription_item = subscriptions.get(index); + events.concat(subscription_item.Event + "," + subscription_item.Topic + + (subscription_item.Key.length()>0 ? "," : "") + + subscription_item.Key + "; "); + } + } + return events; +} +# 4354 "S:/Development/Tasmota/tasmota/xdrv_10_scripter.ino" +String ScriptUnsubscribe(const char * data, int data_len) +{ + MQTT_Subscription subscription_item; + String events; + if (data_len > 0) { + for (uint32_t index = 0; index < subscriptions.size(); index++) { + subscription_item = subscriptions.get(index); + if (subscription_item.Event.equalsIgnoreCase(data)) { + String stopic = subscription_item.Topic + "/#"; + MqttUnsubscribe(stopic.c_str()); + events = subscription_item.Event; + subscriptions.remove(index); + break; + } + } + } else { + + String stopic; + while (subscriptions.size() > 0) { + events.concat(subscriptions.get(0).Event + "; "); + stopic = subscriptions.get(0).Topic + "/#"; + MqttUnsubscribe(stopic.c_str()); + subscriptions.remove(0); + } + } + return events; +} +#endif + + + +#ifdef USE_SCRIPT_WEB_DISPLAY + +void Script_Check_HTML_Setvars(void) { + + if (!HttpCheckPriviledgedAccess()) { return; } + + if (WebServer->hasArg("sv")) { + String stmp = WebServer->arg("sv"); + char cmdbuf[64]; + memset(cmdbuf,0,sizeof(cmdbuf)); + char *cp=cmdbuf; + *cp++='>'; + strncpy(cp,stmp.c_str(),sizeof(cmdbuf)-1); + char *cp1=strchr(cp,'_'); + if (!cp1) return; + *cp1=0; + char vname[32]; + strncpy(vname,cp,sizeof(vname)); + *cp1='='; + cp1++; + + struct T_INDEX ind; + uint8_t vtype; + isvar(vname,&vtype,&ind,0,0,0); + if (vtype!=NUM_RES && vtype&STYPE) { + + uint8_t tlen=strlen(cp1); + memmove(cp1+1,cp1,tlen); + *cp1='\"'; + *(cp1+tlen+1)='\"'; + } + + + execute_script(cmdbuf); + Run_Scripter(">E",2,0); + } +} + + +const char SCRIPT_MSG_BUTTONa[] PROGMEM = + ""; + +const char SCRIPT_MSG_BUTTONa_TBL[] PROGMEM = + ""; + +const char SCRIPT_MSG_BUTTONb[] PROGMEM = + ""; + +const char SCRIPT_MSG_BUT_START[] PROGMEM = + "
"; +const char SCRIPT_MSG_BUT_START_TBL[] PROGMEM = + ""; + +const char SCRIPT_MSG_BUT_STOP[] PROGMEM = + ""; +const char SCRIPT_MSG_BUT_STOP_TBL[] PROGMEM = + "
"; + +const char SCRIPT_MSG_SLIDER[] PROGMEM = + "
%s
%s%s
" + "
"; + +const char SCRIPT_MSG_CHKBOX[] PROGMEM = + "
"; + +const char SCRIPT_MSG_TEXTINP[] PROGMEM = + "
"; + +const char SCRIPT_MSG_NUMINP[] PROGMEM = + "
"; + + +void ScriptGetVarname(char *nbuf,char *sp, uint32_t blen) { +uint32_t cnt; + for (cnt=0;cntW",-2,0); + if (web_script==99) { + char line[128]; + char tmp[128]; + uint8_t optflg=0; + char *lp=glob_script_mem.section_ptr+2; + while (lp) { + while (*lp==SCRIPT_EOL) { + lp++; + } + if (!*lp || *lp=='#' || *lp=='>') { + break; + } + if (*lp!=';') { + + memcpy(line,lp,sizeof(line)); + line[sizeof(line)-1]=0; + char *cp=line; + for (uint32_t i=0; i0) { + cp="checked='checked'"; + uval=0; + } else { + cp=""; + uval=1; + } + WSContentSend_PD(SCRIPT_MSG_CHKBOX,label,(char*)cp,uval,vname); + + } else if (!strncmp(lin,"bu(",3)) { + char *lp=lin+3; + uint8_t bcnt=0; + char *found=lin; + while (bcnt<4) { + found=strstr(found,"bu("); + if (!found) break; + found+=3; + bcnt++; + } + uint8_t proz=100/bcnt; + if (!optflg && bcnt>1) proz-=2; + if (optflg) WSContentSend_PD(SCRIPT_MSG_BUT_START_TBL); + else WSContentSend_PD(SCRIPT_MSG_BUT_START); + for (uint32_t cnt=0;cnt0) { + cp=ontxt; + uval=0; + } else { + cp=offtxt; + uval=1; + } + if (bcnt>1 && cnt==bcnt-1) { + if (!optflg) proz+=2; + } + if (!optflg) { + WSContentSend_PD(SCRIPT_MSG_BUTTONa,proz,uval,vname,cp); + } else { + WSContentSend_PD(SCRIPT_MSG_BUTTONa_TBL,proz,uval,vname,cp); + } + if (bcnt>1 && cnt%s
"),tmp); + } else { + WSContentSend_PD(PSTR("{s}%s{e}"),tmp); + } + } + } + if (*lp==SCRIPT_EOL) { + lp++; + } else { + lp = strchr(lp, SCRIPT_EOL); + if (!lp) break; + lp++; + } + } + } +} +#endif + + +#ifdef USE_SENDMAIL +void script_send_email_body(BearSSL::WiFiClientSecure_light *client) { +uint8_t msect=Run_Scripter(">m",-2,0); + if (msect==99) { + char line[128]; + char tmp[128]; + char *lp=glob_script_mem.section_ptr+2; + while (lp) { + while (*lp==SCRIPT_EOL) { + lp++; + } + if (!*lp || *lp=='#' || *lp=='>') { + break; + } + if (*lp!=';') { + + memcpy(line,lp,sizeof(line)); + line[sizeof(line)-1]=0; + char *cp=line; + for (uint32_t i=0; iprintln(tmp); + } + if (*lp==SCRIPT_EOL) { + lp++; + } else { + lp = strchr(lp, SCRIPT_EOL); + if (!lp) break; + lp++; + } + } + } else { + client->println("*"); + } +} +#endif + +#ifdef USE_SCRIPT_JSON_EXPORT +void ScriptJsonAppend(void) { + uint8_t web_script=Run_Scripter(">J",-2,0); + if (web_script==99) { + char line[128]; + char tmp[128]; + char *lp=glob_script_mem.section_ptr+2; + while (lp) { + while (*lp==SCRIPT_EOL) { + lp++; + } + if (!*lp || *lp=='#' || *lp=='>') { + break; + } + if (*lp!=';') { + + memcpy(line,lp,sizeof(line)); + line[sizeof(line)-1]=0; + char *cp=line; + for (uint32_t i=0; iB",2,0); + fast_script=Run_Scripter(">F",-2,0); +#if defined(USE_SCRIPT_HUE) && defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT) + Script_Check_Hue(0); +#endif + } + break; + case FUNC_EVERY_100_MSECOND: + ScripterEvery100ms(); + break; + case FUNC_EVERY_SECOND: + ScriptEverySecond(); + break; + case FUNC_COMMAND: + result = ScriptCommand(); + break; + case FUNC_SET_POWER: +#ifdef SCRIPT_POWER_SECTION + if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">P",2,0); +#else + if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">E",2,0); +#endif + break; + case FUNC_RULES_PROCESS: + if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">E",2,mqtt_data); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_RULES); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/" WEB_HANDLE_SCRIPT, HandleScriptConfiguration); + WebServer->on("/ta",HTTP_POST, HandleScriptTextareaConfiguration); + +#ifdef USE_SCRIPT_FATFS + WebServer->on("/u3", HTTP_POST,[]() { WebServer->sendHeader("Location","/u3");WebServer->send(303);},script_upload); + WebServer->on("/u3", HTTP_GET,ScriptFileUploadSuccess); + WebServer->on("/upl", HTTP_GET,Script_FileUploadConfiguration); +#endif + break; +#endif + case FUNC_SAVE_BEFORE_RESTART: + if (bitRead(Settings.rule_enabled, 0)) { + Run_Scripter(">R",2,0); + Scripter_save_pvars(); + } + break; +#ifdef SUPPORT_MQTT_EVENT + case FUNC_MQTT_DATA: + if (bitRead(Settings.rule_enabled, 0)) { + result = ScriptMqttData(); + } + break; +#endif +#ifdef USE_SCRIPT_WEB_DISPLAY + case FUNC_WEB_SENSOR: + if (bitRead(Settings.rule_enabled, 0)) { + ScriptWebShow(); + } + break; +#endif + +#ifdef USE_SCRIPT_JSON_EXPORT + case FUNC_JSON_APPEND: + if (bitRead(Settings.rule_enabled, 0)) { + ScriptJsonAppend(); + } + break; +#endif + +#ifdef USE_BUTTON_EVENT + case FUNC_BUTTON_PRESSED: + if (bitRead(Settings.rule_enabled, 0)) { + if ((script_button[XdrvMailbox.index]&1)!=(XdrvMailbox.payload&1)) { + script_button[XdrvMailbox.index]=XdrvMailbox.payload; + Run_Scripter(">b",2,0); + } + } + break; +#endif + + } + return result; +} + + + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_11_knx.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_11_knx.ino" +#ifdef USE_KNX +# 51 "S:/Development/Tasmota/tasmota/xdrv_11_knx.ino" +#define XDRV_11 11 + +#include + +address_t KNX_physs_addr; +address_t KNX_addr; + +#define KNX_Empty 255 + +#define TOGGLE_INHIBIT_TIME 15 + +float last_temp; +float last_hum; +uint8_t toggle_inhibit; + +typedef struct __device_parameters +{ + uint8_t type; + + + + + bool show; + + bool last_state; + + callback_id_t CB_id; + + + + + +} device_parameters_t; + + +device_parameters_t device_param[] = { + { 1, false, false, KNX_Empty }, + { 2, false, false, KNX_Empty }, + { 3, false, false, KNX_Empty }, + { 4, false, false, KNX_Empty }, + { 5, false, false, KNX_Empty }, + { 6, false, false, KNX_Empty }, + { 7, false, false, KNX_Empty }, + { 8, false, false, KNX_Empty }, + { 9, false, false, KNX_Empty }, + { 10, false, false, KNX_Empty }, + { 11, false, false, KNX_Empty }, + { 12, false, false, KNX_Empty }, + { 13, false, false, KNX_Empty }, + { 14, false, false, KNX_Empty }, + { 15, false, false, KNX_Empty }, + { 16, false, false, KNX_Empty }, + { KNX_TEMPERATURE, false, false, KNX_Empty }, + { KNX_HUMIDITY , false, false, KNX_Empty }, + { KNX_ENERGY_VOLTAGE , false, false, KNX_Empty }, + { KNX_ENERGY_CURRENT , false, false, KNX_Empty }, + { KNX_ENERGY_POWER , false, false, KNX_Empty }, + { KNX_ENERGY_POWERFACTOR , false, false, KNX_Empty }, + { KNX_ENERGY_DAILY , false, false, KNX_Empty }, + { KNX_ENERGY_START , false, false, KNX_Empty }, + { KNX_ENERGY_TOTAL , false, false, KNX_Empty }, + { KNX_SLOT1 , false, false, KNX_Empty }, + { KNX_SLOT2 , false, false, KNX_Empty }, + { KNX_SLOT3 , false, false, KNX_Empty }, + { KNX_SLOT4 , false, false, KNX_Empty }, + { KNX_SLOT5 , false, false, KNX_Empty }, + { KNX_Empty, false, false, KNX_Empty} +}; + + +const char * device_param_ga[] = { + D_TIMER_OUTPUT " 1", + D_TIMER_OUTPUT " 2", + D_TIMER_OUTPUT " 3", + D_TIMER_OUTPUT " 4", + D_TIMER_OUTPUT " 5", + D_TIMER_OUTPUT " 6", + D_TIMER_OUTPUT " 7", + D_TIMER_OUTPUT " 8", + D_SENSOR_BUTTON " 1", + D_SENSOR_BUTTON " 2", + D_SENSOR_BUTTON " 3", + D_SENSOR_BUTTON " 4", + D_SENSOR_BUTTON " 5", + D_SENSOR_BUTTON " 6", + D_SENSOR_BUTTON " 7", + D_SENSOR_BUTTON " 8", + D_TEMPERATURE , + D_HUMIDITY , + D_VOLTAGE , + D_CURRENT , + D_POWERUSAGE , + D_POWER_FACTOR , + D_ENERGY_TODAY , + D_ENERGY_YESTERDAY , + D_ENERGY_TOTAL , + D_KNX_TX_SLOT " 1", + D_KNX_TX_SLOT " 2", + D_KNX_TX_SLOT " 3", + D_KNX_TX_SLOT " 4", + D_KNX_TX_SLOT " 5", + nullptr +}; + + +const char *device_param_cb[] = { + D_TIMER_OUTPUT " 1", + D_TIMER_OUTPUT " 2", + D_TIMER_OUTPUT " 3", + D_TIMER_OUTPUT " 4", + D_TIMER_OUTPUT " 5", + D_TIMER_OUTPUT " 6", + D_TIMER_OUTPUT " 7", + D_TIMER_OUTPUT " 8", + D_TIMER_OUTPUT " 1 " D_BUTTON_TOGGLE, + D_TIMER_OUTPUT " 2 " D_BUTTON_TOGGLE, + D_TIMER_OUTPUT " 3 " D_BUTTON_TOGGLE, + D_TIMER_OUTPUT " 4 " D_BUTTON_TOGGLE, + D_TIMER_OUTPUT " 5 " D_BUTTON_TOGGLE, + D_TIMER_OUTPUT " 6 " D_BUTTON_TOGGLE, + D_TIMER_OUTPUT " 7 " D_BUTTON_TOGGLE, + D_TIMER_OUTPUT " 8 " D_BUTTON_TOGGLE, + D_REPLY " " D_TEMPERATURE, + D_REPLY " " D_HUMIDITY, + D_REPLY " " D_VOLTAGE , + D_REPLY " " D_CURRENT , + D_REPLY " " D_POWERUSAGE , + D_REPLY " " D_POWER_FACTOR , + D_REPLY " " D_ENERGY_TODAY , + D_REPLY " " D_ENERGY_YESTERDAY , + D_REPLY " " D_ENERGY_TOTAL , + D_KNX_RX_SLOT " 1", + D_KNX_RX_SLOT " 2", + D_KNX_RX_SLOT " 3", + D_KNX_RX_SLOT " 4", + D_KNX_RX_SLOT " 5", + nullptr +}; + + +#define D_PRFX_KNX "Knx" +#define D_CMND_KNXTXCMND "Tx_Cmnd" +#define D_CMND_KNXTXVAL "Tx_Val" +#define D_CMND_KNX_ENABLED "_Enabled" +#define D_CMND_KNX_ENHANCED "_Enhanced" +#define D_CMND_KNX_PA "_PA" +#define D_CMND_KNX_GA "_GA" +#define D_CMND_KNX_CB "_CB" + +const char kKnxCommands[] PROGMEM = D_PRFX_KNX "|" + D_CMND_KNXTXCMND "|" D_CMND_KNXTXVAL "|" D_CMND_KNX_ENABLED "|" D_CMND_KNX_ENHANCED "|" D_CMND_KNX_PA "|" D_CMND_KNX_GA "|" D_CMND_KNX_CB ; + +void (* const KnxCommand[])(void) PROGMEM = { + &CmndKnxTxCmnd, &CmndKnxTxVal, &CmndKnxEnabled, &CmndKnxEnhanced, &CmndKnxPa, &CmndKnxGa, &CmndKnxCb }; + +uint8_t KNX_GA_Search( uint8_t param, uint8_t start = 0 ) +{ + for (uint32_t i = start; i < Settings.knx_GA_registered; ++i) + { + if ( Settings.knx_GA_param[i] == param ) + { + if ( Settings.knx_GA_addr[i] != 0 ) + { + if ( i >= start ) { return i; } + } + } + } + return KNX_Empty; +} + + +uint8_t KNX_CB_Search( uint8_t param, uint8_t start = 0 ) +{ + for (uint32_t i = start; i < Settings.knx_CB_registered; ++i) + { + if ( Settings.knx_CB_param[i] == param ) + { + if ( Settings.knx_CB_addr[i] != 0 ) + { + if ( i >= start ) { return i; } + } + } + } + return KNX_Empty; +} + + +void KNX_ADD_GA( uint8_t GAop, uint8_t GA_FNUM, uint8_t GA_AREA, uint8_t GA_FDEF ) +{ + + if ( Settings.knx_GA_registered >= MAX_KNX_GA ) { return; } + if ( GA_FNUM == 0 && GA_AREA == 0 && GA_FDEF == 0 ) { return; } + + + Settings.knx_GA_param[Settings.knx_GA_registered] = GAop; + KNX_addr.ga.area = GA_FNUM; + KNX_addr.ga.line = GA_AREA; + KNX_addr.ga.member = GA_FDEF; + Settings.knx_GA_addr[Settings.knx_GA_registered] = KNX_addr.value; + + Settings.knx_GA_registered++; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_ADD " GA #%d: %s " D_TO " %d/%d/%d"), + Settings.knx_GA_registered, + device_param_ga[GAop-1], + GA_FNUM, GA_AREA, GA_FDEF ); +} + + +void KNX_DEL_GA( uint8_t GAnum ) +{ + + uint8_t dest_offset = 0; + uint8_t src_offset = 0; + uint8_t len = 0; + + + Settings.knx_GA_param[GAnum-1] = 0; + + if (GAnum == 1) + { + + src_offset = 1; + + + + len = (Settings.knx_GA_registered - 1); + } + else if (GAnum == Settings.knx_GA_registered) + { + + } + else + { + + + + + dest_offset = GAnum -1 ; + src_offset = dest_offset + 1; + len = (Settings.knx_GA_registered - GAnum); + } + + if (len > 0) + { + memmove(Settings.knx_GA_param + dest_offset, Settings.knx_GA_param + src_offset, len * sizeof(uint8_t)); + memmove(Settings.knx_GA_addr + dest_offset, Settings.knx_GA_addr + src_offset, len * sizeof(uint16_t)); + } + + Settings.knx_GA_registered--; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_DELETE " GA #%d"), + GAnum ); +} + + +void KNX_ADD_CB( uint8_t CBop, uint8_t CB_FNUM, uint8_t CB_AREA, uint8_t CB_FDEF ) +{ + + if ( Settings.knx_CB_registered >= MAX_KNX_CB ) { return; } + if ( CB_FNUM == 0 && CB_AREA == 0 && CB_FDEF == 0 ) { return; } + + + if ( device_param[CBop-1].CB_id == KNX_Empty ) + { + + device_param[CBop-1].CB_id = knx.callback_register("", KNX_CB_Action, &device_param[CBop-1]); + + + + + } + + Settings.knx_CB_param[Settings.knx_CB_registered] = CBop; + KNX_addr.ga.area = CB_FNUM; + KNX_addr.ga.line = CB_AREA; + KNX_addr.ga.member = CB_FDEF; + Settings.knx_CB_addr[Settings.knx_CB_registered] = KNX_addr.value; + + knx.callback_assign( device_param[CBop-1].CB_id, KNX_addr ); + + Settings.knx_CB_registered++; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_ADD " CB #%d: %d/%d/%d " D_TO " %s"), + Settings.knx_CB_registered, + CB_FNUM, CB_AREA, CB_FDEF, + device_param_cb[CBop-1] ); +} + + +void KNX_DEL_CB( uint8_t CBnum ) +{ + uint8_t oldparam = Settings.knx_CB_param[CBnum-1]; + uint8_t dest_offset = 0; + uint8_t src_offset = 0; + uint8_t len = 0; + + + knx.callback_unassign(CBnum-1); + Settings.knx_CB_param[CBnum-1] = 0; + + if (CBnum == 1) + { + + src_offset = 1; + + + + len = (Settings.knx_CB_registered - 1); + } + else if (CBnum == Settings.knx_CB_registered) + { + + } + else + { + + + + + dest_offset = CBnum -1 ; + src_offset = dest_offset + 1; + len = (Settings.knx_CB_registered - CBnum); + } + + if (len > 0) + { + memmove(Settings.knx_CB_param + dest_offset, Settings.knx_CB_param + src_offset, len * sizeof(uint8_t)); + memmove(Settings.knx_CB_addr + dest_offset, Settings.knx_CB_addr + src_offset, len * sizeof(uint16_t)); + } + + Settings.knx_CB_registered--; + + + if ( KNX_CB_Search( oldparam ) == KNX_Empty ) { + knx.callback_deregister( device_param[oldparam-1].CB_id ); + device_param[oldparam-1].CB_id = KNX_Empty; + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_DELETE " CB #%d"), CBnum ); +} + + +bool KNX_CONFIG_NOT_MATCH(void) +{ + + for (uint32_t i = 0; i < KNX_MAX_device_param; ++i) + { + if ( !device_param[i].show ) { + + + + if ( KNX_GA_Search(i+1) != KNX_Empty ) { return true; } + + if ( i < 8 ) + { + if ( KNX_CB_Search(i+1) != KNX_Empty ) { return true; } + if ( KNX_CB_Search(i+9) != KNX_Empty ) { return true; } + } + + if ( i > 15 ) + { + if ( KNX_CB_Search(i+1) != KNX_Empty ) { return true; } + } + } + } + + + for (uint32_t i = 0; i < Settings.knx_GA_registered; ++i) + { + if ( Settings.knx_GA_param[i] != 0 ) + { + if ( Settings.knx_GA_addr[i] == 0 ) + { + return true; + } + } + } + for (uint32_t i = 0; i < Settings.knx_CB_registered; ++i) + { + if ( Settings.knx_CB_param[i] != 0 ) + { + if ( Settings.knx_CB_addr[i] == 0 ) + { + return true; + } + } + } + + return false; +} + + +void KNXStart(void) +{ + knx.start(nullptr); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_START)); +} + + +void KNX_INIT(void) +{ + + if (Settings.knx_GA_registered > MAX_KNX_GA) { Settings.knx_GA_registered = MAX_KNX_GA; } + if (Settings.knx_CB_registered > MAX_KNX_CB) { Settings.knx_CB_registered = MAX_KNX_CB; } + + + KNX_physs_addr.value = Settings.knx_physsical_addr; + knx.physical_address_set( KNX_physs_addr ); +# 472 "S:/Development/Tasmota/tasmota/xdrv_11_knx.ino" + for (uint32_t i = 0; i < devices_present; ++i) + { + device_param[i].show = true; + } + for (uint32_t i = GPIO_SWT1; i < GPIO_SWT4 + 1; ++i) + { + if (GetUsedInModule(i, my_module.io)) { device_param[i - GPIO_SWT1 + 8].show = true; } + } + for (uint32_t i = GPIO_KEY1; i < GPIO_KEY4 + 1; ++i) + { + if (GetUsedInModule(i, my_module.io)) { device_param[i - GPIO_KEY1 + 8].show = true; } + } + for (uint32_t i = GPIO_SWT1_NP; i < GPIO_SWT4_NP + 1; ++i) + { + if (GetUsedInModule(i, my_module.io)) { device_param[i - GPIO_SWT1_NP + 8].show = true; } + } + for (uint32_t i = GPIO_KEY1_NP; i < GPIO_KEY4_NP + 1; ++i) + { + if (GetUsedInModule(i, my_module.io)) { device_param[i - GPIO_KEY1_NP + 8].show = true; } + } + if (GetUsedInModule(GPIO_DHT11, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; } + if (GetUsedInModule(GPIO_DHT22, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; } + if (GetUsedInModule(GPIO_SI7021, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; } +#ifdef USE_DS18x20 + if (GetUsedInModule(GPIO_DSB, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; } +#endif + if (GetUsedInModule(GPIO_DHT11, my_module.io)) { device_param[KNX_HUMIDITY-1].show = true; } + if (GetUsedInModule(GPIO_DHT22, my_module.io)) { device_param[KNX_HUMIDITY-1].show = true; } + if (GetUsedInModule(GPIO_SI7021, my_module.io)) { device_param[KNX_HUMIDITY-1].show = true; } + +#if defined(USE_ENERGY_SENSOR) + + if ( energy_flg != ENERGY_NONE ) { + device_param[KNX_ENERGY_POWER-1].show = true; + device_param[KNX_ENERGY_DAILY-1].show = true; + device_param[KNX_ENERGY_START-1].show = true; + device_param[KNX_ENERGY_TOTAL-1].show = true; + device_param[KNX_ENERGY_VOLTAGE-1].show = true; + device_param[KNX_ENERGY_CURRENT-1].show = true; + device_param[KNX_ENERGY_POWERFACTOR-1].show = true; + } +#endif + +#ifdef USE_RULES + device_param[KNX_SLOT1-1].show = true; + device_param[KNX_SLOT2-1].show = true; + device_param[KNX_SLOT3-1].show = true; + device_param[KNX_SLOT4-1].show = true; + device_param[KNX_SLOT5-1].show = true; +#endif + + + if (KNX_CONFIG_NOT_MATCH()) { + Settings.knx_GA_registered = 0; + Settings.knx_CB_registered = 0; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_DELETE " " D_KNX_PARAMETERS)); + } + + + + + uint8_t j; + for (uint32_t i = 0; i < Settings.knx_CB_registered; ++i) + { + j = Settings.knx_CB_param[i]; + if ( j > 0 ) + { + device_param[j-1].CB_id = knx.callback_register("", KNX_CB_Action, &device_param[j-1]); + + + + KNX_addr.value = Settings.knx_CB_addr[i]; + knx.callback_assign( device_param[j-1].CB_id, KNX_addr ); + } + } +} + + +void KNX_CB_Action(message_t const &msg, void *arg) +{ + device_parameters_t *chan = (device_parameters_t *)arg; + if (!(Settings.flag.knx_enabled)) { return; } + + char tempchar[33]; + + if (msg.data_len == 1) { + + tempchar[0] = msg.data[0]; + tempchar[1] = '\0'; + } else { + + float tempvar = knx.data_to_2byte_float(msg.data); + dtostrfd(tempvar,2,tempchar); + } + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX D_RECEIVED_FROM " %d.%d.%d " D_COMMAND " %s: %s " D_TO " %s"), + msg.received_on.ga.area, msg.received_on.ga.line, msg.received_on.ga.member, + (msg.ct == KNX_CT_WRITE) ? D_KNX_COMMAND_WRITE : (msg.ct == KNX_CT_READ) ? D_KNX_COMMAND_READ : D_KNX_COMMAND_OTHER, + tempchar, + device_param_cb[(chan->type)-1]); + + switch (msg.ct) + { + case KNX_CT_WRITE: + if (chan->type < 9) + { + ExecuteCommandPower(chan->type, msg.data[0], SRC_KNX); + } + else if (chan->type < 17) + { + if (!toggle_inhibit) { + ExecuteCommandPower((chan->type) -8, POWER_TOGGLE, SRC_KNX); + if (Settings.flag.knx_enable_enhancement) { + toggle_inhibit = TOGGLE_INHIBIT_TIME; + } + } + } +#ifdef USE_RULES + else if ((chan->type >= KNX_SLOT1) && (chan->type <= KNX_SLOT5)) + { + if (!toggle_inhibit) { + char command[25]; + if (msg.data_len == 1) { + + snprintf_P(command, sizeof(command), PSTR("event KNXRX_CMND%d=%d"), ((chan->type) - KNX_SLOT1 + 1 ), msg.data[0]); + } else { + + snprintf_P(command, sizeof(command), PSTR("event KNXRX_VAL%d=%s"), ((chan->type) - KNX_SLOT1 + 1 ), tempchar); + } + ExecuteCommand(command, SRC_KNX); + if (Settings.flag.knx_enable_enhancement) { + toggle_inhibit = TOGGLE_INHIBIT_TIME; + } + } + } +#endif + break; + + case KNX_CT_READ: + if (chan->type < 9) + { + knx.answer_1bit(msg.received_on, chan->last_state); + if (Settings.flag.knx_enable_enhancement) { + knx.answer_1bit(msg.received_on, chan->last_state); + knx.answer_1bit(msg.received_on, chan->last_state); + } + } + else if (chan->type == KNX_TEMPERATURE) + { + knx.answer_2byte_float(msg.received_on, last_temp); + if (Settings.flag.knx_enable_enhancement) { + knx.answer_2byte_float(msg.received_on, last_temp); + knx.answer_2byte_float(msg.received_on, last_temp); + } + } + else if (chan->type == KNX_HUMIDITY) + { + knx.answer_2byte_float(msg.received_on, last_hum); + if (Settings.flag.knx_enable_enhancement) { + knx.answer_2byte_float(msg.received_on, last_hum); + knx.answer_2byte_float(msg.received_on, last_hum); + } + } +#ifdef USE_RULES + else if ((chan->type >= KNX_SLOT1) && (chan->type <= KNX_SLOT5)) + { + if (!toggle_inhibit) { + char command[25]; + snprintf_P(command, sizeof(command), PSTR("event KNXRX_REQ%d"), ((chan->type) - KNX_SLOT1 + 1 ) ); + ExecuteCommand(command, SRC_KNX); + if (Settings.flag.knx_enable_enhancement) { + toggle_inhibit = TOGGLE_INHIBIT_TIME; + } + } + } +#endif + break; + } +} + + +void KnxUpdatePowerState(uint8_t device, power_t state) +{ + if (!(Settings.flag.knx_enabled)) { return; } + + device_param[device -1].last_state = bitRead(state, device -1); + + + uint8_t i = KNX_GA_Search(device); + while ( i != KNX_Empty ) { + KNX_addr.value = Settings.knx_GA_addr[i]; + knx.write_1bit(KNX_addr, device_param[device -1].last_state); + if (Settings.flag.knx_enable_enhancement) { + knx.write_1bit(KNX_addr, device_param[device -1].last_state); + knx.write_1bit(KNX_addr, device_param[device -1].last_state); + } + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s = %d " D_SENT_TO " %d.%d.%d"), + device_param_ga[device -1], device_param[device -1].last_state, + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member); + + i = KNX_GA_Search(device, i + 1); + } +} + + +void KnxSendButtonPower(void) +{ + if (!(Settings.flag.knx_enabled)) { return; } + + uint32_t key = (XdrvMailbox.payload >> 16) & 0xFF; + uint32_t device = XdrvMailbox.payload & 0xFF; + uint32_t state = (XdrvMailbox.payload >> 8) & 0xFF; +# 693 "S:/Development/Tasmota/tasmota/xdrv_11_knx.ino" + uint8_t i = KNX_GA_Search(device + 8); + while ( i != KNX_Empty ) { + KNX_addr.value = Settings.knx_GA_addr[i]; + knx.write_1bit(KNX_addr, !(state == 0)); + if (Settings.flag.knx_enable_enhancement) { + knx.write_1bit(KNX_addr, !(state == 0)); + knx.write_1bit(KNX_addr, !(state == 0)); + } + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s = %d " D_SENT_TO " %d.%d.%d"), + device_param_ga[device + 7], !(state == 0), + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member); + + i = KNX_GA_Search(device + 8, i + 1); + } + +} + + +void KnxSensor(uint8_t sensor_type, float value) +{ + if (sensor_type == KNX_TEMPERATURE) + { + last_temp = value; + } else if (sensor_type == KNX_HUMIDITY) + { + last_hum = value; + } + + if (!(Settings.flag.knx_enabled)) { return; } + + uint8_t i = KNX_GA_Search(sensor_type); + while ( i != KNX_Empty ) { + KNX_addr.value = Settings.knx_GA_addr[i]; + knx.write_2byte_float(KNX_addr, value); + if (Settings.flag.knx_enable_enhancement) { + knx.write_2byte_float(KNX_addr, value); + knx.write_2byte_float(KNX_addr, value); + } + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s " D_SENT_TO " %d.%d.%d "), + device_param_ga[sensor_type -1], + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member); + + i = KNX_GA_Search(sensor_type, i+1); + } +} + + + + + + +#ifdef USE_WEBSERVER +#ifdef USE_KNX_WEB_MENU +const char S_CONFIGURE_KNX[] PROGMEM = D_CONFIGURE_KNX; + +const char HTTP_BTN_MENU_KNX[] PROGMEM = + "

"; + +const char HTTP_FORM_KNX[] PROGMEM = + "
" + " " D_KNX_PARAMETERS " " + "
" + "
" + "" D_KNX_PHYSICAL_ADDRESS " " + " . " + " . " + "" + "

" D_KNX_PHYSICAL_ADDRESS_NOTE "

" + "" D_KNX_ENABLE "" D_KNX_ENHANCEMENT "

" + + "
" + "" D_KNX_GROUP_ADDRESS_TO_WRITE "
" + + " / " + " / " + " "; + +const char HTTP_FORM_KNX_ADD_BTN[] PROGMEM = + "

" + ""; + +const char HTTP_FORM_KNX_ADD_TABLE_ROW[] PROGMEM = + "" + ""; + +const char HTTP_FORM_KNX3[] PROGMEM = + "
%s -> %d / %d / %d

" + "
" + "" D_KNX_GROUP_ADDRESS_TO_READ "
"; + +const char HTTP_FORM_KNX4[] PROGMEM = + "-> -> ")); + WSContentSend_P(HTTP_FORM_KNX_GA, "GA_FNUM", "GA_AREA", "GA_FDEF"); + WSContentSend_P(HTTP_FORM_KNX_ADD_BTN, "GAwarning", (Settings.knx_GA_registered < MAX_KNX_GA) ? "" : "disabled", 1); + for (uint32_t i = 0; i < Settings.knx_GA_registered ; ++i) + { + if ( Settings.knx_GA_param[i] ) + { + KNX_addr.value = Settings.knx_GA_addr[i]; + WSContentSend_P(HTTP_FORM_KNX_ADD_TABLE_ROW, device_param_ga[Settings.knx_GA_param[i]-1], KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member, i +1); + } + } + + WSContentSend_P(HTTP_FORM_KNX3); + WSContentSend_P(HTTP_FORM_KNX_GA, "CB_FNUM", "CB_AREA", "CB_FDEF"); + WSContentSend_P(HTTP_FORM_KNX4); + + uint8_t j; + for (uint32_t i = 0; i < KNX_MAX_device_param ; i++) + { + + if ( (i > 8) && (i < 16) ) { j=i-8; } else { j=i; } + if ( i == 8 ) { j = 0; } + if ( device_param[j].show ) + { + WSContentSend_P(HTTP_FORM_KNX_OPT, device_param[i].type, device_param_cb[i]); + } + } + WSContentSend_P(PSTR(" ")); + WSContentSend_P(HTTP_FORM_KNX_ADD_BTN, "CBwarning", (Settings.knx_CB_registered < MAX_KNX_CB) ? "" : "disabled", 2); + + for (uint32_t i = 0; i < Settings.knx_CB_registered ; ++i) + { + if ( Settings.knx_CB_param[i] ) + { + KNX_addr.value = Settings.knx_CB_addr[i]; + WSContentSend_P(HTTP_FORM_KNX_ADD_TABLE_ROW2, KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member, device_param_cb[Settings.knx_CB_param[i]-1], i +1); + } + } + WSContentSend_P(PSTR("
")); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); + } + +} + + +void KNX_Save_Settings(void) +{ + String stmp; + address_t KNX_addr; + + Settings.flag.knx_enabled = WebServer->hasArg("b1"); + Settings.flag.knx_enable_enhancement = WebServer->hasArg("b2"); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_ENABLED ": %d, " D_KNX_ENHANCEMENT ": %d"), + Settings.flag.knx_enabled, Settings.flag.knx_enable_enhancement ); + + stmp = WebServer->arg("area"); + KNX_addr.pa.area = stmp.toInt(); + stmp = WebServer->arg("line"); + KNX_addr.pa.line = stmp.toInt(); + stmp = WebServer->arg("member"); + KNX_addr.pa.member = stmp.toInt(); + Settings.knx_physsical_addr = KNX_addr.value; + knx.physical_address_set( KNX_addr ); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_KNX_PHYSICAL_ADDRESS ": %d.%d.%d "), + KNX_addr.pa.area, KNX_addr.pa.line, KNX_addr.pa.member ); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX "GA: %d"), + Settings.knx_GA_registered ); + for (uint32_t i = 0; i < Settings.knx_GA_registered ; ++i) + { + KNX_addr.value = Settings.knx_GA_addr[i]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX "GA #%d: %s " D_TO " %d/%d/%d"), + i+1, device_param_ga[Settings.knx_GA_param[i]-1], + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member ); + + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX "CB: %d"), + Settings.knx_CB_registered ); + for (uint32_t i = 0; i < Settings.knx_CB_registered ; ++i) + { + KNX_addr.value = Settings.knx_CB_addr[i]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX "CB #%d: %d/%d/%d " D_TO " %s"), + i+1, + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member, + device_param_cb[Settings.knx_CB_param[i]-1] ); + } +} + +#endif +#endif + + + + + +void CmndKnxTxCmnd(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_KNXTX_CMNDS) && (XdrvMailbox.data_len > 0) && Settings.flag.knx_enabled) { + + + + uint8_t i = KNX_GA_Search(XdrvMailbox.index + KNX_SLOT1 -1); + while ( i != KNX_Empty ) { + KNX_addr.value = Settings.knx_GA_addr[i]; + knx.write_1bit(KNX_addr, !(XdrvMailbox.payload == 0)); + if (Settings.flag.knx_enable_enhancement) { + knx.write_1bit(KNX_addr, !(XdrvMailbox.payload == 0)); + knx.write_1bit(KNX_addr, !(XdrvMailbox.payload == 0)); + } + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s = %d " D_SENT_TO " %d.%d.%d"), + device_param_ga[XdrvMailbox.index + KNX_SLOT1 -2], !(XdrvMailbox.payload == 0), + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member); + + i = KNX_GA_Search(XdrvMailbox.index + KNX_SLOT1 -1, i + 1); + } + ResponseCmndIdxChar (XdrvMailbox.data ); + } +} + +void CmndKnxTxVal(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_KNXTX_CMNDS) && (XdrvMailbox.data_len > 0) && Settings.flag.knx_enabled) { + + + + uint8_t i = KNX_GA_Search(XdrvMailbox.index + KNX_SLOT1 -1); + while ( i != KNX_Empty ) { + KNX_addr.value = Settings.knx_GA_addr[i]; + + float tempvar = CharToFloat(XdrvMailbox.data); + dtostrfd(tempvar,2,XdrvMailbox.data); + + knx.write_2byte_float(KNX_addr, tempvar); + if (Settings.flag.knx_enable_enhancement) { + knx.write_2byte_float(KNX_addr, tempvar); + knx.write_2byte_float(KNX_addr, tempvar); + } + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s = %s " D_SENT_TO " %d.%d.%d"), + device_param_ga[XdrvMailbox.index + KNX_SLOT1 -2], XdrvMailbox.data, + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member); + + i = KNX_GA_Search(XdrvMailbox.index + KNX_SLOT1 -1, i + 1); + } + ResponseCmndIdxChar (XdrvMailbox.data ); + } +} + +void CmndKnxEnabled(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.flag.knx_enabled = XdrvMailbox.payload; + } + ResponseCmndChar (GetStateText(Settings.flag.knx_enabled) ); +} + +void CmndKnxEnhanced(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.flag.knx_enable_enhancement = XdrvMailbox.payload; + } + ResponseCmndChar (GetStateText(Settings.flag.knx_enable_enhancement) ); +} + +void CmndKnxPa(void) +{ + if (XdrvMailbox.data_len) { + if (strstr(XdrvMailbox.data, ".") != nullptr) { + char sub_string[XdrvMailbox.data_len]; + + int pa_area = atoi(subStr(sub_string, XdrvMailbox.data, ".", 1)); + int pa_line = atoi(subStr(sub_string, XdrvMailbox.data, ".", 2)); + int pa_member = atoi(subStr(sub_string, XdrvMailbox.data, ".", 3)); + + if ( ((pa_area == 0) && (pa_line == 0) && (pa_member == 0)) + || (pa_area > 15) || (pa_line > 15) || (pa_member > 255) ) { + Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command ); + return; + } + + KNX_addr.pa.area = pa_area; + KNX_addr.pa.line = pa_line; + KNX_addr.pa.member = pa_member; + Settings.knx_physsical_addr = KNX_addr.value; + } + } + KNX_addr.value = Settings.knx_physsical_addr; + Response_P (PSTR("{\"%s\":\"%d.%d.%d\"}"), + XdrvMailbox.command, KNX_addr.pa.area, KNX_addr.pa.line, KNX_addr.pa.member ); +} + +void CmndKnxGa(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_KNX_GA)) { + if (XdrvMailbox.data_len) { + if (strstr(XdrvMailbox.data, ",") != nullptr) { + char sub_string[XdrvMailbox.data_len]; + + int ga_option = atoi(subStr(sub_string, XdrvMailbox.data, ",", 1)); + int ga_area = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + int ga_line = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); + int ga_member = atoi(subStr(sub_string, XdrvMailbox.data, ",", 4)); + + if ( ((ga_area == 0) && (ga_line == 0) && (ga_member == 0)) + || (ga_area > 31) || (ga_line > 7) || (ga_member > 255) + || (ga_option < 0) || ((ga_option > KNX_MAX_device_param ) && (ga_option != KNX_Empty)) + || (!device_param[ga_option-1].show) ) { + Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command ); + return; + } + + KNX_addr.ga.area = ga_area; + KNX_addr.ga.line = ga_line; + KNX_addr.ga.member = ga_member; + + if ( XdrvMailbox.index > Settings.knx_GA_registered ) { + Settings.knx_GA_registered ++; + XdrvMailbox.index = Settings.knx_GA_registered; + } + + Settings.knx_GA_addr[XdrvMailbox.index -1] = KNX_addr.value; + Settings.knx_GA_param[XdrvMailbox.index -1] = ga_option; + } else { + if ( (XdrvMailbox.payload <= Settings.knx_GA_registered) && (XdrvMailbox.payload > 0) ) { + XdrvMailbox.index = XdrvMailbox.payload; + } else { + Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command ); + return; + } + } + if ( XdrvMailbox.index <= Settings.knx_GA_registered ) { + KNX_addr.value = Settings.knx_GA_addr[XdrvMailbox.index -1]; + Response_P (PSTR("{\"%s%d\":\"%s, %d/%d/%d\"}"), + XdrvMailbox.command, XdrvMailbox.index, device_param_ga[Settings.knx_GA_param[XdrvMailbox.index-1]-1], + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member ); + } + } else { + ResponseCmndNumber (Settings.knx_GA_registered ); + } + } +} + +void CmndKnxCb(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_KNX_CB)) { + if (XdrvMailbox.data_len) { + if (strstr(XdrvMailbox.data, ",") != nullptr) { + char sub_string[XdrvMailbox.data_len]; + + int cb_option = atoi(subStr(sub_string, XdrvMailbox.data, ",", 1)); + int cb_area = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + int cb_line = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); + int cb_member = atoi(subStr(sub_string, XdrvMailbox.data, ",", 4)); + + if ( ((cb_area == 0) && (cb_line == 0) && (cb_member == 0)) + || (cb_area > 31) || (cb_line > 7) || (cb_member > 255) + || (cb_option < 0) || ((cb_option > KNX_MAX_device_param ) && (cb_option != KNX_Empty)) + || (!device_param[cb_option-1].show) ) { + Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command ); + return; + } + + KNX_addr.ga.area = cb_area; + KNX_addr.ga.line = cb_line; + KNX_addr.ga.member = cb_member; + + if ( XdrvMailbox.index > Settings.knx_CB_registered ) { + Settings.knx_CB_registered ++; + XdrvMailbox.index = Settings.knx_CB_registered; + } + + Settings.knx_CB_addr[XdrvMailbox.index -1] = KNX_addr.value; + Settings.knx_CB_param[XdrvMailbox.index -1] = cb_option; + } else { + if ( (XdrvMailbox.payload <= Settings.knx_CB_registered) && (XdrvMailbox.payload > 0) ) { + XdrvMailbox.index = XdrvMailbox.payload; + } else { + Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command ); + return; + } + } + if ( XdrvMailbox.index <= Settings.knx_CB_registered ) { + KNX_addr.value = Settings.knx_CB_addr[XdrvMailbox.index -1]; + Response_P (PSTR("{\"%s%d\":\"%s, %d/%d/%d\"}"), + XdrvMailbox.command, XdrvMailbox.index, device_param_cb[Settings.knx_CB_param[XdrvMailbox.index-1]-1], + KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member ); + } + } else { + ResponseCmndNumber (Settings.knx_CB_registered ); + } + } +} + + + + + +bool Xdrv11(uint8_t function) +{ + bool result = false; + switch (function) { + case FUNC_LOOP: + if (!global_state.wifi_down) { knx.loop(); } + break; + case FUNC_EVERY_50_MSECOND: + if (toggle_inhibit) { + toggle_inhibit--; + } + break; + case FUNC_ANY_KEY: + KnxSendButtonPower(); + break; +#ifdef USE_WEBSERVER +#ifdef USE_KNX_WEB_MENU + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_KNX); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/kn", HandleKNXConfiguration); + break; +#endif +#endif + case FUNC_COMMAND: + result = DecodeCommand(kKnxCommands, KnxCommand); + break; + case FUNC_PRE_INIT: + KNX_INIT(); + break; + + + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_12_home_assistant.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_12_home_assistant.ino" +#ifdef USE_HOME_ASSISTANT + +#define XDRV_12 12 + + +const char kHAssJsonSensorTypes[] PROGMEM = + D_JSON_TEMPERATURE "|" D_JSON_PRESSURE "|" D_JSON_PRESSUREATSEALEVEL "|" + D_JSON_APPARENT_POWERUSAGE "|Battery|" D_JSON_CURRENT "|" D_JSON_DISTANCE "|" D_JSON_FREQUENCY "|" D_JSON_HUMIDITY "|" D_JSON_ILLUMINANCE "|" + D_JSON_MOISTURE "|PB0.3|PB0.5|PB1|PB2.5|PB5|PB10|PM1|PM2.5|PM10|" D_JSON_POWERFACTOR "|" D_JSON_POWERUSAGE "|" + D_JSON_REACTIVE_POWERUSAGE "|" D_JSON_TODAY "|" D_JSON_TOTAL "|" D_JSON_VOLTAGE "|" D_JSON_WEIGHT "|" D_JSON_YESTERDAY; +const char kHAssJsonSensorUnits[] PROGMEM = + "|||" + "W|%|A|Cm|Hz|%|LX|" + "%|ppd|ppd|ppd|ppd|ppd|ppd|µg/m³|µg/m³|µg/m³||W|" + "W|KWh|KWh|V|Kg|KWh"; +const char kHAssJsonSensorDevCla[] PROGMEM = + "dev_cla\":\"temperature|dev_cla\":\"pressure|dev_cla\":\"pressure|" + "dev_cla\":\"power|dev_cla\":\"battery|ic\":\"mdi:alpha-a-circle-outline|ic\":\"mdi:leak|ic\":\"mdi:current-ac|dev_cla\":\"humidity|dev_cla\":\"illuminance|" + "ic\":\"mdi:cup-water|ic\":\"mdi:flask|ic\":\"mdi:flask|ic\":\"mdi:flask|ic\":\"mdi:flask|ic\":\"mdi:flask|ic\":\"mdi:flask|" + "ic\":\"mdi:air-filter|ic\":\"mdi:air-filter|ic\":\"mdi:air-filter|ic\":\"mdi:alpha-f-circle-outline|dev_cla\":\"power|" + "dev_cla\":\"power|dev_cla\":\"power|dev_cla\":\"power|ic\":\"mdi:alpha-v-circle-outline|ic\":\"mdi:scale|dev_cla\":\"power"; + + +const char HASS_DISCOVER_SENSOR[] PROGMEM = + ",\"unit_of_meas\":\"%s\",\"%s\"," + "\"frc_upd\":true," + "\"val_tpl\":\"{{value_json['%s']['%s']"; + +const char HASS_DISCOVER_BASE[] PROGMEM = + "{\"name\":\"%s\"," + "\"stat_t\":\"%s\"," + "\"avty_t\":\"%s\"," + "\"pl_avail\":\"" D_ONLINE "\"," + "\"pl_not_avail\":\"" D_OFFLINE "\""; + +const char HASS_DISCOVER_RELAY[] PROGMEM = + ",\"cmd_t\":\"%s\"," + "\"val_tpl\":\"{{value_json.%s}}\"," + "\"pl_off\":\"%s\"," + "\"pl_on\":\"%s\""; + +const char HASS_DISCOVER_BUTTON_TOGGLE[] PROGMEM = + ",\"value_template\":\"{%%if is_state(entity_id,\\\"off\\\")-%%}ON{%%-endif%%}\"," + "\"off_delay\":1"; + +const char HASS_DISCOVER_BUTTON_SWITCH_ONOFF[] PROGMEM = + ",\"value_template\":\"{{value_json.%s}}\"," + "\"frc_upd\":true," + "\"pl_on\":\"%s\"," + "\"pl_off\":\"%s\""; + +const char HASS_DISCOVER_LIGHT_DIMMER[] PROGMEM = + ",\"bri_cmd_t\":\"%s\"," + "\"bri_stat_t\":\"%s\"," + "\"bri_scl\":100," + "\"on_cmd_type\":\"%s\"," + "\"bri_val_tpl\":\"{{value_json." D_CMND_DIMMER "}}\""; + +const char HASS_DISCOVER_LIGHT_COLOR[] PROGMEM = + ",\"rgb_cmd_t\":\"%s2\"," + "\"rgb_stat_t\":\"%s\"," + "\"rgb_val_tpl\":\"{{value_json." D_CMND_COLOR ".split(',')[0:3]|join(',')}}\""; + +const char HASS_DISCOVER_LIGHT_WHITE[] PROGMEM = + ",\"whit_val_cmd_t\":\"%s\"," + "\"whit_val_stat_t\":\"%s\"," + "\"white_value_scale\":100," + "\"whit_val_tpl\":\"{{value_json.Channel[3]}}\""; + +const char HASS_DISCOVER_LIGHT_CT[] PROGMEM = + ",\"clr_temp_cmd_t\":\"%s\"," + "\"clr_temp_stat_t\":\"%s\"," + "\"clr_temp_val_tpl\":\"{{value_json." D_CMND_COLORTEMPERATURE "}}\""; + +const char HASS_DISCOVER_LIGHT_SCHEME[] PROGMEM = + ",\"fx_cmd_t\":\"%s\"," + "\"fx_stat_t\":\"%s\"," + "\"fx_val_tpl\":\"{{value_json." D_CMND_SCHEME "}}\"," + "\"fx_list\":[\"0\",\"1\",\"2\",\"3\",\"4\"]"; + +const char HASS_DISCOVER_SENSOR_HASS_STATUS[] PROGMEM = + ",\"json_attributes_topic\":\"%s\"," + "\"unit_of_meas\":\" \"," + "\"val_tpl\":\"{{value_json['" D_JSON_RSSI "']}}\"," + "\"ic\":\"mdi:information-outline\""; + +const char HASS_DISCOVER_DEVICE_INFO[] PROGMEM = + ",\"uniq_id\":\"%s\"," + "\"device\":{\"identifiers\":[\"%06X\"]," + "\"connections\":[[\"mac\",\"%s\"]]," + "\"name\":\"%s\"," + "\"model\":\"%s\"," + "\"sw_version\":\"%s%s\"," + "\"manufacturer\":\"Tasmota\"}"; + +const char HASS_DISCOVER_DEVICE_INFO_SHORT[] PROGMEM = + ",\"uniq_id\":\"%s\"," + "\"device\":{\"identifiers\":[\"%06X\"]," + "\"connections\":[[\"mac\",\"%s\"]]}"; + +uint8_t hass_init_step = 0; +uint8_t hass_mode = 0; +int hass_tele_period = 0; + +void TryResponseAppend_P(const char *format, ...) +{ + va_list args; + va_start(args, format); + char dummy[2]; + int dlen = vsnprintf_P(dummy, 1, format, args); + + int mlen = strlen(mqtt_data); + int slen = sizeof(mqtt_data) - 1 - mlen; + if (dlen >= slen) + { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: MQTT discovery failed due to too long topic or friendly name. " + "Please shorten topic and friendly name. Failed to format(%u/%u):"), + dlen, slen); + va_start(args, format); + vsnprintf_P(log_data, sizeof(log_data), format, args); + AddLog(LOG_LEVEL_ERROR); + } + else + { + va_start(args, format); + vsnprintf_P(mqtt_data + mlen, slen, format, args); + } + va_end(args); +} + +void HAssAnnounceRelayLight(void) +{ + char stopic[TOPSZ]; + char stemp1[TOPSZ]; + char stemp2[TOPSZ]; + char stemp3[TOPSZ]; + char unique_id[30]; + bool is_light = false; + bool is_topic_light = false; + + for (uint32_t i = 1; i <= MAX_RELAYS; i++) + { + is_light = ((i == devices_present) && (light_type)); + is_topic_light = Settings.flag.hass_light || is_light; + + mqtt_data[0] = '\0'; + + + snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), (is_topic_light) ? "RL" : "LI", i); + snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s/config"), + (is_topic_light) ? "switch" : "light", unique_id); + MqttPublish(stopic, true); + + snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), (is_topic_light) ? "LI" : "RL", i); + snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s/config"), + (is_topic_light) ? "light" : "switch", unique_id); + + if (Settings.flag.hass_discovery && (i <= devices_present)) + { + char name[33 + 2]; + char value_template[33]; + char prefix[TOPSZ]; + char *command_topic = stemp1; + char *state_topic = stemp2; + char *availability_topic = stemp3; + + if (i > MAX_FRIENDLYNAMES) + { + snprintf_P(name, sizeof(name), PSTR("%s %d"), SettingsText(SET_FRIENDLYNAME1), i); + } + else + { + snprintf_P(name, sizeof(name), SettingsText(SET_FRIENDLYNAME1 + i - 1)); + } + GetPowerDevice(value_template, i, sizeof(value_template), Settings.flag.device_index_enable); + GetTopic_P(command_topic, CMND, mqtt_topic, value_template); + GetTopic_P(state_topic, TELE, mqtt_topic, D_RSLT_STATE); + GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT); + + Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic); + 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) + { + char *brightness_command_topic = stemp1; + + GetTopic_P(brightness_command_topic, CMND, mqtt_topic, D_CMND_DIMMER); + strncpy_P(stemp3, Settings.flag.not_power_linked ? PSTR("last") : PSTR("brightness"), sizeof(stemp3)); + TryResponseAppend_P(HASS_DISCOVER_LIGHT_DIMMER, brightness_command_topic, state_topic, stemp3); + + if (Light.subtype >= LST_RGB) + { + char *rgb_command_topic = stemp1; + + GetTopic_P(rgb_command_topic, CMND, mqtt_topic, D_CMND_COLOR); + TryResponseAppend_P(HASS_DISCOVER_LIGHT_COLOR, rgb_command_topic, state_topic); + + char *effect_command_topic = stemp1; + GetTopic_P(effect_command_topic, CMND, mqtt_topic, D_CMND_SCHEME); + TryResponseAppend_P(HASS_DISCOVER_LIGHT_SCHEME, effect_command_topic, state_topic); + } + if (LST_RGBW == Light.subtype) + { + char *white_temp_command_topic = stemp1; + + GetTopic_P(white_temp_command_topic, CMND, mqtt_topic, D_CMND_WHITE); + TryResponseAppend_P(HASS_DISCOVER_LIGHT_WHITE, white_temp_command_topic, state_topic); + } + if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) + { + char *color_temp_command_topic = stemp1; + + 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 + TryResponseAppend_P(PSTR("}")); + } + MqttPublish(stopic, true); + } +} + +void HAssAnnounceButtonSwitch(uint8_t device, char *topic, uint8_t present, uint8_t key, uint8_t toggle) +{ + + + char stopic[TOPSZ]; + char stemp1[TOPSZ]; + char stemp2[TOPSZ]; + char unique_id[30]; + + mqtt_data[0] = '\0'; + + + snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), key ? "SW" : "BTN", device + 1); + snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/binary_sensor/%s/config"), unique_id); + + if (Settings.flag.hass_discovery && present) + { + char name[33 + 6]; + char value_template[33]; + char prefix[TOPSZ]; + char *state_topic = stemp1; + char *availability_topic = stemp2; + char jsoname[8]; + + snprintf_P(name, sizeof(name), PSTR("%s %s%d"), SettingsText(SET_FRIENDLYNAME1), key ? "Switch" : "Button", device + 1); + snprintf_P(jsoname, sizeof(jsoname), PSTR("%s%d"), key ? "SWITCH" : "BUTTON", device + 1); + GetPowerDevice(value_template, device + 1, sizeof(value_template), + key + Settings.flag.device_index_enable); + GetTopic_P(state_topic, STAT, mqtt_topic, (PSTR("/'%s'"), jsoname)); + GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT); + + Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic); + TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str()); + + if (toggle) { + if (!key) { + TryResponseAppend_P(HASS_DISCOVER_BUTTON_TOGGLE); + } else { + TryResponseAppend_P(",\"value_template\":\"{%%if is_state(entity_id,\\\"on\\\")-%%}OFF{%%-else-%%}ON{%%-endif%%}\""); + } + } else { + TryResponseAppend_P(HASS_DISCOVER_BUTTON_SWITCH_ONOFF, PSTR(D_RSLT_STATE), SettingsText(SET_STATE_TXT2), SettingsText(SET_STATE_TXT1)); + } + TryResponseAppend_P(PSTR("}")); + } + MqttPublish(stopic, true); +} + +void HAssAnnounceSwitches(void) +{ + char sw_topic[TOPSZ]; + + + char *tmp = SettingsText(SET_MQTT_SWITCH_TOPIC); + Format(sw_topic, tmp, sizeof(sw_topic)); + if (!strcmp_P(sw_topic, "0") || strlen(sw_topic) == 0) + { + for (uint32_t switch_index = 0; switch_index < MAX_SWITCHES; switch_index++) + { + uint8_t switch_present = 0; + uint8_t toggle = 1; + + if (pin[GPIO_SWT1 + switch_index] < 99) + { + switch_present = 1; + } + + + if (Settings.switchmode[switch_index] == FOLLOW || Settings.switchmode[switch_index] == FOLLOW_INV || + Settings.flag3.button_switch_force_local || + !strcmp(mqtt_topic, sw_topic) || !strcmp(SettingsText(SET_MQTT_GRP_TOPIC), sw_topic)) + { + toggle = 0; + } + HAssAnnounceButtonSwitch(switch_index, sw_topic, switch_present, 1, toggle); + } + } +} + +void HAssAnnounceButtons(void) +{ + char key_topic[TOPSZ]; + + + char *tmp = SettingsText(SET_MQTT_BUTTON_TOPIC); + Format(key_topic, tmp, sizeof(key_topic)); + if (!strcmp_P(key_topic, "0") || strlen(key_topic) == 0) + { + for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) + { + uint8_t button_present = 0; + uint8_t toggle = 1; + + if (!button_index && ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type))) + { + button_present = 1; + } + else + { + if (pin[GPIO_KEY1 + button_index] < 99) + { + button_present = 1; + } + } + + + if (Settings.flag3.button_switch_force_local || + !strcmp(mqtt_topic, key_topic) || !strcmp(SettingsText(SET_MQTT_GRP_TOPIC), key_topic)) + { + toggle = 0; + } + HAssAnnounceButtonSwitch(button_index, key_topic, button_present, 0, toggle); + } + } +} + +void HAssAnnounceSensor(const char *sensorname, const char *subsensortype, const char *MultiSubName, uint8_t subqty, uint8_t subidx) +{ + char stopic[TOPSZ]; + char stemp1[TOPSZ]; + char stemp2[TOPSZ]; + char unique_id[30]; + + mqtt_data[0] = '\0'; + + char subname[20]; + NoAlNumToUnderscore(subname, MultiSubName); + snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%s"), ESP.getChipId(), sensorname, subname); + snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/sensor/%s/config"), unique_id);; + + if (Settings.flag.hass_discovery) + { + char name[33 + 42]; + char prefix[TOPSZ]; + char *state_topic = stemp1; + char *availability_topic = stemp2; + + snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/sensor/%s/config"), unique_id); + GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_SENSOR)); + snprintf_P(name, sizeof(name), PSTR("%s %s %s"), SettingsText(SET_FRIENDLYNAME1), sensorname, MultiSubName); + GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT); + + Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic); + TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str()); + + char jname[32]; + int sensor_index = GetCommandCode(jname, sizeof(jname), subsensortype, kHAssJsonSensorTypes); + if (sensor_index > -1) { + char param1[20]; + GetTextIndexed(param1, sizeof(param1), sensor_index, kHAssJsonSensorUnits); + switch (sensor_index) { + case 0: + snprintf_P(param1, sizeof(param1), PSTR("°%c"),TempUnit()); + break; + case 1: + case 2: + snprintf_P(param1, sizeof(param1), PSTR("%s"), PressureUnit().c_str()); + break; + } + char param2[50]; + GetTextIndexed(param2, sizeof(param2), sensor_index, kHAssJsonSensorDevCla); + TryResponseAppend_P(HASS_DISCOVER_SENSOR, param1, param2, sensorname, subsensortype); + if (subidx) { + TryResponseAppend_P(PSTR("[%d]"), subqty -1); + } + } else { + TryResponseAppend_P(HASS_DISCOVER_SENSOR, " ", "ic\":\"mdi:eye", sensorname, subsensortype); + } + TryResponseAppend_P(PSTR("}}\"}")); + } + MqttPublish(stopic, true); +} + +void HAssAnnounceSensors(void) +{ + uint8_t hass_xsns_index = 0; + bool is_sensor = true; + uint8_t subqty = 0; + do + { + mqtt_data[0] = '\0'; + int tele_period_save = tele_period; + tele_period = 2; + XsnsNextCall(FUNC_JSON_APPEND, hass_xsns_index); + tele_period = tele_period_save; + + char sensordata[512]; + strlcpy(sensordata, mqtt_data, sizeof(sensordata)); + + if (strlen(sensordata)) + { + sensordata[0] = '{'; + snprintf_P(sensordata, sizeof(sensordata), PSTR("%s}"), sensordata); + + + + StaticJsonBuffer<500> jsonBuffer; + JsonObject &root = jsonBuffer.parseObject(sensordata); + if (!root.success()) + { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: jsonBuffer failed to parse '%s'"), sensordata); + continue; + } + for (auto sensor : root) + { + const char *sensorname = sensor.key; + JsonObject &sensors = sensor.value.as(); + if (!sensors.success()) + { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: JsonObject failed to parse '%s'"), sensordata); + continue; + } + for (auto subsensor : sensors) + { + + if (subsensor.value.is()) { + JsonArray& subsensors = subsensor.value.as(); + subqty = subsensors.size(); + char MultiSubName[20]; + for (int i = 1; i <= subqty; i++) { + snprintf_P(MultiSubName, sizeof(MultiSubName), PSTR("%s %d"), subsensor.key, i); + HAssAnnounceSensor(sensorname, subsensor.key, MultiSubName, i, 1); + } + } else { HAssAnnounceSensor(sensorname, subsensor.key, subsensor.key, 0, 0);} + } + } + } + yield(); + } while (hass_xsns_index != 0); +} + +void HAssAnnounceStatusSensor(void) +{ + char stopic[TOPSZ]; + char stemp1[TOPSZ]; + char stemp2[TOPSZ]; + char unique_id[30]; + + + mqtt_data[0] = '\0'; + + + snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_status"), ESP.getChipId()); + snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/sensor/%s/config"), unique_id); + + if (Settings.flag.hass_discovery) + { + char name[33 + 7]; + char prefix[TOPSZ]; + char *state_topic = stemp1; + char *availability_topic = stemp2; + + snprintf_P(name, sizeof(name), PSTR("%s status"), SettingsText(SET_FRIENDLYNAME1)); + GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_HASS_STATE)); + GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT); + + Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic); + TryResponseAppend_P(HASS_DISCOVER_SENSOR_HASS_STATUS, state_topic); + TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO, unique_id, ESP.getChipId(), WiFi.macAddress().c_str(), + SettingsText(SET_FRIENDLYNAME1), ModuleName().c_str(), my_version, my_image); + + TryResponseAppend_P(PSTR("}")); + } + MqttPublish(stopic, true); +} + +void HAssPublishStatus(void) +{ + Response_P(PSTR("{\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_BUILDDATETIME "\":\"%s\"," + "\"" D_JSON_COREVERSION "\":\"" ARDUINO_ESP8266_RELEASE "\",\"" D_JSON_SDKVERSION "\":\"%s\"," + "\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_RESTARTREASON "\":\"%s\",\"" D_JSON_UPTIME "\":\"%s\"," + "\"WiFi " D_JSON_LINK_COUNT "\":%d,\"WiFi " D_JSON_DOWNTIME "\":\"%s\",\"" D_JSON_MQTT_COUNT "\":%d," + "\"" D_JSON_BOOTCOUNT "\":%d,\"" D_JSON_SAVECOUNT "\":%d,\"" D_CMND_IPADDRESS "\":\"%s\"," + "\"" D_JSON_RSSI "\":\"%d\",\"LoadAvg\":%lu}"), + my_version, my_image, GetBuildDateAndTime().c_str(), ESP.getSdkVersion(), ModuleName().c_str(), + GetResetReason().c_str(), GetUptime().c_str(), WifiLinkCount(), WifiDowntime().c_str(), MqttConnectCount(), + Settings.bootcount, Settings.save_flag, WiFi.localIP().toString().c_str(), + WifiGetRssiAsQuality(WiFi.RSSI()), loop_load_avg); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_HASS_STATE)); +} + +void HAssDiscovery(void) +{ + + if (Settings.flag.hass_discovery) + { + Settings.flag.mqtt_response = 0; + Settings.flag.decimal_text = 1; + Settings.flag3.hass_tele_on_power = 1; + Settings.light_scheme = 0; + } + + if (Settings.flag.hass_discovery || (1 == hass_mode)) + { + + HAssAnnounceRelayLight(); + + + HAssAnnounceButtons(); + + + HAssAnnounceSwitches(); + + + HAssAnnounceSensors(); + + + HAssAnnounceStatusSensor(); + } +} + +void HAssDiscover(void) +{ + hass_mode = 1; + hass_init_step = 1; +} + +void HAssAnyKey(void) +{ + if (!Settings.flag.hass_discovery) + { + return; + } + + uint32_t key = (XdrvMailbox.payload >> 16) & 0xFF; + uint32_t device = XdrvMailbox.payload & 0xFF; + uint32_t state = (XdrvMailbox.payload >> 8) & 0xFF; + + char scommand[CMDSZ]; + snprintf_P(scommand, sizeof(scommand), PSTR("%s%d"), (key) ? "SWITCH" : "BUTTON", device); + char stopic[TOPSZ]; + + GetTopic_P(stopic, STAT, mqtt_topic, scommand); + Response_P(S_JSON_COMMAND_SVALUE, PSTR(D_RSLT_STATE), GetStateText(state)); + MqttPublish(stopic); +} + + + + + +bool Xdrv12(uint8_t function) +{ + bool result = false; + + if (Settings.flag.mqtt_enabled) + { + switch (function) + { + case FUNC_EVERY_SECOND: + if (hass_init_step) + { + hass_init_step--; + if (!hass_init_step) + { + HAssDiscovery(); + } + } + else if (Settings.flag.hass_discovery && Settings.tele_period) + { + hass_tele_period++; + if (hass_tele_period >= Settings.tele_period) + { + hass_tele_period = 0; + + mqtt_data[0] = '\0'; + HAssPublishStatus(); + } + } + break; + case FUNC_ANY_KEY: + HAssAnyKey(); + break; + case FUNC_MQTT_INIT: + hass_mode = 0; + hass_init_step = 2; + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_13_display.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_13_display.ino" +#if defined(USE_I2C) || defined(USE_SPI) +#ifdef USE_DISPLAY + +#define XDRV_13 13 + +#include +#include + +Renderer *renderer; + +enum ColorType { COLOR_BW, COLOR_COLOR }; + +#ifndef MAXBUTTONS +#define MAXBUTTONS 16 +#endif + +#ifdef USE_TOUCH_BUTTONS +VButton *buttons[MAXBUTTONS]; +#endif + + + +uint16_t fg_color = 1; +uint16_t bg_color = 0; +uint8_t color_type = COLOR_BW; +uint8_t auto_draw=1; + +const uint8_t DISPLAY_MAX_DRIVERS = 16; +const uint8_t DISPLAY_MAX_COLS = 44; +const uint8_t DISPLAY_MAX_ROWS = 32; + +const uint8_t DISPLAY_LOG_ROWS = 32; + +#define D_PRFX_DISPLAY "Display" +#define D_CMND_DISP_ADDRESS "Address" +#define D_CMND_DISP_COLS "Cols" +#define D_CMND_DISP_DIMMER "Dimmer" +#define D_CMND_DISP_MODE "Mode" +#define D_CMND_DISP_MODEL "Model" +#define D_CMND_DISP_REFRESH "Refresh" +#define D_CMND_DISP_ROWS "Rows" +#define D_CMND_DISP_SIZE "Size" +#define D_CMND_DISP_FONT "Font" +#define D_CMND_DISP_ROTATE "Rotate" +#define D_CMND_DISP_TEXT "Text" +#define D_CMND_DISP_WIDTH "Width" +#define D_CMND_DISP_HEIGHT "Height" + +enum XdspFunctions { FUNC_DISPLAY_INIT_DRIVER, FUNC_DISPLAY_INIT, FUNC_DISPLAY_EVERY_50_MSECOND, FUNC_DISPLAY_EVERY_SECOND, + FUNC_DISPLAY_MODEL, FUNC_DISPLAY_MODE, FUNC_DISPLAY_POWER, + FUNC_DISPLAY_CLEAR, FUNC_DISPLAY_DRAW_FRAME, + FUNC_DISPLAY_DRAW_HLINE, FUNC_DISPLAY_DRAW_VLINE, FUNC_DISPLAY_DRAW_LINE, + FUNC_DISPLAY_DRAW_CIRCLE, FUNC_DISPLAY_FILL_CIRCLE, + FUNC_DISPLAY_DRAW_RECTANGLE, FUNC_DISPLAY_FILL_RECTANGLE, + FUNC_DISPLAY_TEXT_SIZE, FUNC_DISPLAY_FONT_SIZE, FUNC_DISPLAY_ROTATION, FUNC_DISPLAY_DRAW_STRING, FUNC_DISPLAY_ONOFF }; + +enum DisplayInitModes { DISPLAY_INIT_MODE, DISPLAY_INIT_PARTIAL, DISPLAY_INIT_FULL }; + +const char kDisplayCommands[] PROGMEM = D_PRFX_DISPLAY "|" + "|" D_CMND_DISP_MODEL "|" D_CMND_DISP_WIDTH "|" D_CMND_DISP_HEIGHT "|" D_CMND_DISP_MODE "|" D_CMND_DISP_REFRESH "|" + D_CMND_DISP_DIMMER "|" D_CMND_DISP_COLS "|" D_CMND_DISP_ROWS "|" D_CMND_DISP_SIZE "|" D_CMND_DISP_FONT "|" + D_CMND_DISP_ROTATE "|" D_CMND_DISP_TEXT "|" D_CMND_DISP_ADDRESS ; + +void (* const DisplayCommand[])(void) PROGMEM = { + &CmndDisplay, &CmndDisplayModel, &CmndDisplayWidth, &CmndDisplayHeight, &CmndDisplayMode, &CmndDisplayRefresh, + &CmndDisplayDimmer, &CmndDisplayColumns, &CmndDisplayRows, &CmndDisplaySize, &CmndDisplayFont, + &CmndDisplayRotate, &CmndDisplayText, &CmndDisplayAddress }; + +char *dsp_str; + +uint16_t dsp_x; +uint16_t dsp_y; +uint16_t dsp_x2; +uint16_t dsp_y2; +uint16_t dsp_rad; +uint16_t dsp_color; +int16_t dsp_len; +int16_t disp_xpos = 0; +int16_t disp_ypos = 0; + +uint8_t disp_power = 0; +uint8_t disp_device = 0; +uint8_t disp_refresh = 1; +uint8_t disp_autodraw = 1; +uint8_t dsp_init; +uint8_t dsp_font; +uint8_t dsp_flag; +uint8_t dsp_on; + +#ifdef USE_DISPLAY_MODES1TO5 + +char **disp_log_buffer; +char **disp_screen_buffer; +char disp_temp[2]; +char disp_pres[5]; + +uint8_t disp_log_buffer_cols = 0; +uint8_t disp_log_buffer_idx = 0; +uint8_t disp_log_buffer_ptr = 0; +uint8_t disp_screen_buffer_cols = 0; +uint8_t disp_screen_buffer_rows = 0; +bool disp_subscribed = false; + +#endif + + + +void DisplayInit(uint8_t mode) +{ + if (renderer) { + renderer->DisplayInit(mode, Settings.display_size, Settings.display_rotate, Settings.display_font); + } + else { + dsp_init = mode; + XdspCall(FUNC_DISPLAY_INIT); + } +} + +void DisplayClear(void) +{ + XdspCall(FUNC_DISPLAY_CLEAR); +} + +void DisplayDrawHLine(uint16_t x, uint16_t y, int16_t len, uint16_t color) +{ + dsp_x = x; + dsp_y = y; + dsp_len = len; + dsp_color = color; + XdspCall(FUNC_DISPLAY_DRAW_HLINE); +} + +void DisplayDrawVLine(uint16_t x, uint16_t y, int16_t len, uint16_t color) +{ + dsp_x = x; + dsp_y = y; + dsp_len = len; + dsp_color = color; + XdspCall(FUNC_DISPLAY_DRAW_VLINE); +} + +void DisplayDrawLine(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color) +{ + dsp_x = x; + dsp_y = y; + dsp_x2 = x2; + dsp_y2 = y2; + dsp_color = color; + XdspCall(FUNC_DISPLAY_DRAW_LINE); +} + +void DisplayDrawCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color) +{ + dsp_x = x; + dsp_y = y; + dsp_rad = rad; + dsp_color = color; + XdspCall(FUNC_DISPLAY_DRAW_CIRCLE); +} + +void DisplayDrawFilledCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color) +{ + dsp_x = x; + dsp_y = y; + dsp_rad = rad; + dsp_color = color; + XdspCall(FUNC_DISPLAY_FILL_CIRCLE); +} + +void DisplayDrawRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color) +{ + dsp_x = x; + dsp_y = y; + dsp_x2 = x2; + dsp_y2 = y2; + dsp_color = color; + XdspCall(FUNC_DISPLAY_DRAW_RECTANGLE); +} + +void DisplayDrawFilledRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color) +{ + dsp_x = x; + dsp_y = y; + dsp_x2 = x2; + dsp_y2 = y2; + dsp_color = color; + XdspCall(FUNC_DISPLAY_FILL_RECTANGLE); +} + +void DisplayDrawFrame(void) +{ + XdspCall(FUNC_DISPLAY_DRAW_FRAME); +} + +void DisplaySetSize(uint8_t size) +{ + Settings.display_size = size &3; + XdspCall(FUNC_DISPLAY_TEXT_SIZE); +} + +void DisplaySetFont(uint8_t font) +{ + Settings.display_font = font &3; + XdspCall(FUNC_DISPLAY_FONT_SIZE); +} + +void DisplaySetRotation(uint8_t rotation) +{ + Settings.display_rotate = rotation &3; + XdspCall(FUNC_DISPLAY_ROTATION); +} + +void DisplayDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag) +{ + dsp_x = x; + dsp_y = y; + dsp_str = str; + dsp_color = color; + dsp_flag = flag; + XdspCall(FUNC_DISPLAY_DRAW_STRING); +} + +void DisplayOnOff(uint8_t on) +{ + dsp_on = on; + XdspCall(FUNC_DISPLAY_ONOFF); +} + + + + +uint8_t fatoiv(char *cp,float *res) { + uint8_t index=0; + *res=CharToFloat(cp); + while (*cp) { + if ((*cp>='0' && *cp<='9') || (*cp=='-') || (*cp=='.')) { + cp++; + index++; + } else { + break; + } + } + return index; +} + + +uint8_t atoiv(char *cp, int16_t *res) +{ + uint8_t index = 0; + *res = atoi(cp); + while (*cp) { + if ((*cp>='0' && *cp<='9') || (*cp=='-')) { + cp++; + index++; + } else { + break; + } + } + return index; +} + + +uint8_t atoiV(char *cp, uint16_t *res) +{ + uint8_t index = 0; + *res = atoi(cp); + while (*cp) { + if (*cp>='0' && *cp<='9') { + cp++; + index++; + } else { + break; + } + } + return index; +} + + +void alignright(char *string) { + uint16_t slen=strlen(string); + uint16_t len=slen; + while (len) { + + if (string[len-1]!=' ') { + break; + } + len--; + } + uint16_t diff=slen-len; + if (diff>0) { + + memmove(&string[diff],string,len); + memset(string,' ',diff); + } +} + +char *get_string(char *buff,uint8_t len,char *cp) { +uint8_t index=0; + while (*cp!=':') { + buff[index]=*cp++; + index++; + if (index>=len) break; + } + buff[index]=0; + cp++; + return cp; +} + +#define ESCAPE_CHAR '~' + + +uint32_t decode_te(char *line) { + uint32_t skip = 0; + char sbuf[3],*cp; + while (*line) { + if (*line==ESCAPE_CHAR) { + cp=line+1; + if (*cp!=0 && *cp==ESCAPE_CHAR) { + + memmove(cp,cp+1,strlen(cp)); + skip++; + } else { + + if (strlen(cp)<2) { + + return skip; + } + + sbuf[0]=*(cp); + sbuf[1]=*(cp+1); + sbuf[2]=0; + *line=strtol(sbuf,0,16); + + memmove(cp,cp+2,strlen(cp)-1); + skip += 2; + } + } + line++; + } + return skip; +} + + + +#define DISPLAY_BUFFER_COLS 128 + +void DisplayText(void) +{ + uint8_t lpos; + uint8_t escape = 0; + uint8_t var; + int16_t lin = 0; + int16_t col = 0; + int16_t fill = 0; + int16_t temp; + int16_t temp1; + float ftemp; + + char linebuf[DISPLAY_BUFFER_COLS]; + char *dp = linebuf; + char *cp = XdrvMailbox.data; + + memset(linebuf, ' ', sizeof(linebuf)); + linebuf[sizeof(linebuf)-1] = 0; + *dp = 0; + + while (*cp) { + if (!escape) { + + if (*cp == '[') { + escape = 1; + cp++; + + if ((uint32_t)dp - (uint32_t)linebuf) { + if (!fill) { *dp = 0; } + if (col > 0 && lin > 0) { + + if (!renderer) DisplayDrawStringAt(col, lin, linebuf, fg_color, 1); + else renderer->DrawStringAt(col, lin, linebuf, fg_color, 1); + } else { + + if (!renderer) DisplayDrawStringAt(disp_xpos, disp_ypos, linebuf, fg_color, 0); + else renderer->DrawStringAt(disp_xpos, disp_ypos, linebuf, fg_color, 0); + } + memset(linebuf, ' ', sizeof(linebuf)); + linebuf[sizeof(linebuf)-1] = 0; + dp = linebuf; + } + } else { + + if (dp < (linebuf + DISPLAY_BUFFER_COLS)) { *dp++ = *cp++; } + } + } else { + + if (*cp == ']') { + escape = 0; + cp++; + } else { + + switch (*cp++) { + case 'z': + + if (!renderer) DisplayClear(); + else renderer->fillScreen(bg_color); + disp_xpos = 0; + disp_ypos = 0; + col = 0; + lin = 0; + break; + case 'i': + + DisplayInit(DISPLAY_INIT_PARTIAL); + break; + case 'I': + + DisplayInit(DISPLAY_INIT_FULL); + break; + case 'o': + if (!renderer) { + DisplayOnOff(0); + } else { + renderer->DisplayOnff(0); + } + break; + case 'O': + if (!renderer) { + DisplayOnOff(1); + } else { + renderer->DisplayOnff(1); + } + break; + case 'x': + + var = atoiv(cp, &disp_xpos); + cp += var; + break; + case 'y': + + var = atoiv(cp, &disp_ypos); + cp += var; + break; + case 'l': + + var = atoiv(cp, &lin); + cp += var; + + break; + case 'c': + + var = atoiv(cp, &col); + cp += var; + + break; + case 'C': + + if (*cp=='i') { + + cp++; + var = atoiv(cp, &temp); + if (renderer) ftemp=renderer->GetColorFromIndex(temp); + } else { + + var = fatoiv(cp,&ftemp); + } + fg_color=ftemp; + cp += var; + if (renderer) renderer->setTextColor(fg_color,bg_color); + break; + case 'B': + + if (*cp=='i') { + + cp++; + var = atoiv(cp, &temp); + if (renderer) ftemp=renderer->GetColorFromIndex(temp); + } else { + var = fatoiv(cp,&ftemp); + } + bg_color=ftemp; + cp += var; + if (renderer) renderer->setTextColor(fg_color,bg_color); + break; + case 'p': + + var = atoiv(cp, &fill); + cp += var; + linebuf[fill] = 0; + break; +#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) + case 'P': + { char *ep=strchr(cp,':'); + if (ep) { + *ep=0; + ep++; + Draw_RGB_Bitmap(cp,disp_xpos,disp_ypos); + cp=ep; + } + } + break; +#endif + case 'h': + + var = atoiv(cp, &temp); + cp += var; + if (temp < 0) { + if (renderer) renderer->writeFastHLine(disp_xpos + temp, disp_ypos, -temp, fg_color); + else DisplayDrawHLine(disp_xpos + temp, disp_ypos, -temp, fg_color); + } else { + if (renderer) renderer->writeFastHLine(disp_xpos, disp_ypos, temp, fg_color); + else DisplayDrawHLine(disp_xpos, disp_ypos, temp, fg_color); + } + disp_xpos += temp; + break; + case 'v': + + var = atoiv(cp, &temp); + cp += var; + if (temp < 0) { + if (renderer) renderer->writeFastVLine(disp_xpos, disp_ypos + temp, -temp, fg_color); + else DisplayDrawVLine(disp_xpos, disp_ypos + temp, -temp, fg_color); + } else { + if (renderer) renderer->writeFastVLine(disp_xpos, disp_ypos, temp, fg_color); + else DisplayDrawVLine(disp_xpos, disp_ypos, temp, fg_color); + } + disp_ypos += temp; + break; + case 'L': + + var = atoiv(cp, &temp); + cp += var; + cp++; + var = atoiv(cp, &temp1); + cp += var; + if (renderer) renderer->writeLine(disp_xpos, disp_ypos, temp, temp1, fg_color); + else DisplayDrawLine(disp_xpos, disp_ypos, temp, temp1, fg_color); + disp_xpos += temp; + disp_ypos += temp1; + break; + case 'k': + + var = atoiv(cp, &temp); + cp += var; + if (renderer) renderer->drawCircle(disp_xpos, disp_ypos, temp, fg_color); + else DisplayDrawCircle(disp_xpos, disp_ypos, temp, fg_color); + break; + case 'K': + + var = atoiv(cp, &temp); + cp += var; + if (renderer) renderer->fillCircle(disp_xpos, disp_ypos, temp, fg_color); + else DisplayDrawFilledCircle(disp_xpos, disp_ypos, temp, fg_color); + break; + case 'r': + + var = atoiv(cp, &temp); + cp += var; + cp++; + var = atoiv(cp, &temp1); + cp += var; + if (renderer) renderer->drawRect(disp_xpos, disp_ypos, temp, temp1, fg_color); + else DisplayDrawRectangle(disp_xpos, disp_ypos, temp, temp1, fg_color); + break; + case 'R': + + var = atoiv(cp, &temp); + cp += var; + cp++; + var = atoiv(cp, &temp1); + cp += var; + if (renderer) renderer->fillRect(disp_xpos, disp_ypos, temp, temp1, fg_color); + else DisplayDrawFilledRectangle(disp_xpos, disp_ypos, temp, temp1, fg_color); + break; + case 'u': + + { int16_t rad; + var = atoiv(cp, &temp); + cp += var; + cp++; + var = atoiv(cp, &temp1); + cp += var; + cp++; + var = atoiv(cp, &rad); + cp += var; + if (renderer) renderer->drawRoundRect(disp_xpos, disp_ypos, temp, temp1, rad, fg_color); + + } + break; + case 'U': + + { int16_t rad; + var = atoiv(cp, &temp); + cp += var; + cp++; + var = atoiv(cp, &temp1); + cp += var; + cp++; + var = atoiv(cp, &rad); + cp += var; + if (renderer) renderer->fillRoundRect(disp_xpos, disp_ypos, temp, temp1, rad, fg_color); + + } + break; + + case 't': + if (*cp=='S') { + cp++; + if (dp < (linebuf + DISPLAY_BUFFER_COLS) -8) { + snprintf_P(dp, 9, PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); + dp += 8; + } + } else { + if (dp < (linebuf + DISPLAY_BUFFER_COLS) -5) { + snprintf_P(dp, 6, PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute); + dp += 5; + } + } + break; + case 'T': + if (dp < (linebuf + DISPLAY_BUFFER_COLS) -8) { + snprintf_P(dp, 9, PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year%2000); + dp += 8; + } + break; + case 'd': + + if (renderer) renderer->Updateframe(); + else DisplayDrawFrame(); + break; + case 'D': + + auto_draw=*cp&3; + if (renderer) renderer->setDrawMode(auto_draw>>1); + cp += 1; + break; + case 's': + + if (renderer) renderer->setTextSize(*cp&7); + else DisplaySetSize(*cp&3); + cp += 1; + break; + case 'f': + + if (renderer) renderer->setTextFont(*cp&7); + else DisplaySetFont(*cp&7); + cp += 1; + break; + case 'a': + + if (renderer) renderer->setRotation(*cp&3); + else DisplaySetRotation(*cp&3); + cp+=1; + break; + +#ifdef USE_GRAPH + case 'G': + + if (*cp=='d') { + cp++; + var=atoiv(cp,&temp); + cp+=var; + cp++; + var=atoiv(cp,&temp1); + cp+=var; + RedrawGraph(temp,temp1); + break; + } +#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) + if (*cp=='s') { + cp++; + var=atoiv(cp,&temp); + cp+=var; + cp++; + + char bbuff[128]; + cp=get_string(bbuff,sizeof(bbuff),cp); + Save_graph(temp,bbuff); + break; + } + if (*cp=='r') { + cp++; + var=atoiv(cp,&temp); + cp+=var; + cp++; + + char bbuff[128]; + cp=get_string(bbuff,sizeof(bbuff),cp); + Restore_graph(temp,bbuff); + break; + } +#endif + { int16_t num,gxp,gyp,gxs,gys,dec,icol; + float ymin,ymax; + var=atoiv(cp,&num); + cp+=var; + cp++; + var=atoiv(cp,&gxp); + cp+=var; + cp++; + var=atoiv(cp,&gyp); + cp+=var; + cp++; + var=atoiv(cp,&gxs); + cp+=var; + cp++; + var=atoiv(cp,&gys); + cp+=var; + cp++; + var=atoiv(cp,&dec); + cp+=var; + cp++; + var=fatoiv(cp,&ymin); + cp+=var; + cp++; + var=fatoiv(cp,&ymax); + cp+=var; + if (color_type==COLOR_COLOR) { + + cp++; + var=atoiv(cp,&icol); + cp+=var; + } else { + icol=0; + } + DefineGraph(num,gxp,gyp,gxs,gys,dec,ymin,ymax,icol); + } + break; + case 'g': + { float temp; + int16_t num; + var=atoiv(cp,&num); + cp+=var; + cp++; + var=fatoiv(cp,&temp); + cp+=var; + AddValue(num,temp); + } + break; +#endif + +#ifdef USE_AWATCH + case 'w': + var = atoiv(cp, &temp); + cp += var; + DrawAClock(temp); + break; +#endif + +#ifdef USE_TOUCH_BUTTONS + case 'b': + { int16_t num,gxp,gyp,gxs,gys,outline,fill,textcolor,textsize; + var=atoiv(cp,&num); + cp+=var; + cp++; + uint8_t bflags=num>>8; + num=num%MAXBUTTONS; + var=atoiv(cp,&gxp); + cp+=var; + cp++; + var=atoiv(cp,&gyp); + cp+=var; + cp++; + var=atoiv(cp,&gxs); + cp+=var; + cp++; + var=atoiv(cp,&gys); + cp+=var; + cp++; + var=atoiv(cp,&outline); + cp+=var; + cp++; + var=atoiv(cp,&fill); + cp+=var; + cp++; + var=atoiv(cp,&textcolor); + cp+=var; + cp++; + var=atoiv(cp,&textsize); + cp+=var; + cp++; + + char bbuff[32]; + cp=get_string(bbuff,sizeof(bbuff),cp); + + if (buttons[num]) { + delete buttons[num]; + } + if (renderer) { + buttons[num]= new VButton(); + if (buttons[num]) { + buttons[num]->vpower=bflags; + buttons[num]->initButtonUL(renderer,gxp,gyp,gxs,gys,renderer->GetColorFromIndex(outline),\ + renderer->GetColorFromIndex(fill),renderer->GetColorFromIndex(textcolor),bbuff,textsize); + if (!bflags) { + + buttons[num]->xdrawButton(bitRead(power,num)); + } else { + + buttons[num]->vpower&=0x7f; + buttons[num]->xdrawButton(buttons[num]->vpower&0x80); + } + } + } + } + break; +#endif + default: + + Response_P(PSTR("Unknown Escape")); + goto exit; + break; + } + } + } + } + exit: + + dp -= decode_te(linebuf); + if ((uint32_t)dp - (uint32_t)linebuf) { + if (!fill) { + *dp = 0; + } else { + linebuf[abs(fill)] = 0; + } + if (fill<0) { + + alignright(linebuf); + } + if (col > 0 && lin > 0) { + + if (!renderer) DisplayDrawStringAt(col, lin, linebuf, fg_color, 1); + else renderer->DrawStringAt(col, lin, linebuf, fg_color, 1); + } else { + + if (!renderer) DisplayDrawStringAt(disp_xpos, disp_ypos, linebuf, fg_color, 0); + else renderer->DrawStringAt(disp_xpos, disp_ypos, linebuf, fg_color, 0); + } + } + + if (auto_draw&1) { + if (renderer) renderer->Updateframe(); + else DisplayDrawFrame(); + } +} + + + +#ifdef USE_DISPLAY_MODES1TO5 + +void DisplayClearScreenBuffer(void) +{ + if (disp_screen_buffer_cols) { + for (uint32_t i = 0; i < disp_screen_buffer_rows; i++) { + memset(disp_screen_buffer[i], 0, disp_screen_buffer_cols); + } + } +} + +void DisplayFreeScreenBuffer(void) +{ + if (disp_screen_buffer != nullptr) { + for (uint32_t i = 0; i < disp_screen_buffer_rows; i++) { + if (disp_screen_buffer[i] != nullptr) { free(disp_screen_buffer[i]); } + } + free(disp_screen_buffer); + disp_screen_buffer_cols = 0; + disp_screen_buffer_rows = 0; + } +} + +void DisplayAllocScreenBuffer(void) +{ + if (!disp_screen_buffer_cols) { + disp_screen_buffer_rows = Settings.display_rows; + disp_screen_buffer = (char**)malloc(sizeof(*disp_screen_buffer) * disp_screen_buffer_rows); + if (disp_screen_buffer != nullptr) { + for (uint32_t i = 0; i < disp_screen_buffer_rows; i++) { + disp_screen_buffer[i] = (char*)malloc(sizeof(*disp_screen_buffer[i]) * (Settings.display_cols[0] +1)); + if (disp_screen_buffer[i] == nullptr) { + DisplayFreeScreenBuffer(); + break; + } + } + } + if (disp_screen_buffer != nullptr) { + disp_screen_buffer_cols = Settings.display_cols[0] +1; + DisplayClearScreenBuffer(); + } + } +} + +void DisplayReAllocScreenBuffer(void) +{ + DisplayFreeScreenBuffer(); + DisplayAllocScreenBuffer(); +} + +void DisplayFillScreen(uint32_t line) +{ + uint32_t len = disp_screen_buffer_cols - strlen(disp_screen_buffer[line]); + if (len) { + memset(disp_screen_buffer[line] + strlen(disp_screen_buffer[line]), 0x20, len); + disp_screen_buffer[line][disp_screen_buffer_cols -1] = 0; + } +} + + + +void DisplayClearLogBuffer(void) +{ + if (disp_log_buffer_cols) { + for (uint32_t i = 0; i < DISPLAY_LOG_ROWS; i++) { + memset(disp_log_buffer[i], 0, disp_log_buffer_cols); + } + } +} + +void DisplayFreeLogBuffer(void) +{ + if (disp_log_buffer != nullptr) { + for (uint32_t i = 0; i < DISPLAY_LOG_ROWS; i++) { + if (disp_log_buffer[i] != nullptr) { free(disp_log_buffer[i]); } + } + free(disp_log_buffer); + disp_log_buffer_cols = 0; + } +} + +void DisplayAllocLogBuffer(void) +{ + if (!disp_log_buffer_cols) { + disp_log_buffer = (char**)malloc(sizeof(*disp_log_buffer) * DISPLAY_LOG_ROWS); + if (disp_log_buffer != nullptr) { + for (uint32_t i = 0; i < DISPLAY_LOG_ROWS; i++) { + disp_log_buffer[i] = (char*)malloc(sizeof(*disp_log_buffer[i]) * (Settings.display_cols[0] +1)); + if (disp_log_buffer[i] == nullptr) { + DisplayFreeLogBuffer(); + break; + } + } + } + if (disp_log_buffer != nullptr) { + disp_log_buffer_cols = Settings.display_cols[0] +1; + DisplayClearLogBuffer(); + } + } +} + +void DisplayReAllocLogBuffer(void) +{ + DisplayFreeLogBuffer(); + DisplayAllocLogBuffer(); +} + +void DisplayLogBufferAdd(char* txt) +{ + if (disp_log_buffer_cols) { + strlcpy(disp_log_buffer[disp_log_buffer_idx], txt, disp_log_buffer_cols); + disp_log_buffer_idx++; + if (DISPLAY_LOG_ROWS == disp_log_buffer_idx) { disp_log_buffer_idx = 0; } + } +} + +char* DisplayLogBuffer(char temp_code) +{ + char* result = nullptr; + if (disp_log_buffer_cols) { + if (disp_log_buffer_idx != disp_log_buffer_ptr) { + result = disp_log_buffer[disp_log_buffer_ptr]; + disp_log_buffer_ptr++; + if (DISPLAY_LOG_ROWS == disp_log_buffer_ptr) { disp_log_buffer_ptr = 0; } + + char *pch = strchr(result, '~'); + if (pch != nullptr) { result[pch - result] = temp_code; } + } + } + return result; +} + +void DisplayLogBufferInit(void) +{ + if (Settings.display_mode) { + disp_log_buffer_idx = 0; + disp_log_buffer_ptr = 0; + disp_refresh = Settings.display_refresh; + + snprintf_P(disp_temp, sizeof(disp_temp), PSTR("%c"), TempUnit()); + snprintf_P(disp_pres, sizeof(disp_pres), PressureUnit().c_str()); + + DisplayReAllocLogBuffer(); + + char buffer[40]; + snprintf_P(buffer, sizeof(buffer), PSTR(D_VERSION " %s%s"), my_version, my_image); + DisplayLogBufferAdd(buffer); + snprintf_P(buffer, sizeof(buffer), PSTR("Display mode %d"), Settings.display_mode); + DisplayLogBufferAdd(buffer); + + snprintf_P(buffer, sizeof(buffer), PSTR(D_CMND_HOSTNAME " %s"), my_hostname); + DisplayLogBufferAdd(buffer); + snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_SSID " %s"), SettingsText(SET_STASSID1 + Settings.sta_active)); + DisplayLogBufferAdd(buffer); + snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_MAC " %s"), WiFi.macAddress().c_str()); + DisplayLogBufferAdd(buffer); + if (!global_state.wifi_down) { + snprintf_P(buffer, sizeof(buffer), PSTR("IP %s"), WiFi.localIP().toString().c_str()); + DisplayLogBufferAdd(buffer); + snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_RSSI " %d%%"), WifiGetRssiAsQuality(WiFi.RSSI())); + DisplayLogBufferAdd(buffer); + } + } +} + + + + + +enum SensorQuantity { + JSON_TEMPERATURE, + JSON_HUMIDITY, JSON_LIGHT, JSON_NOISE, JSON_AIRQUALITY, + JSON_PRESSURE, JSON_PRESSUREATSEALEVEL, + JSON_ILLUMINANCE, + JSON_GAS, + JSON_YESTERDAY, JSON_TOTAL, JSON_TODAY, + JSON_PERIOD, + JSON_POWERFACTOR, JSON_COUNTER, JSON_ANALOG_INPUT, JSON_UV_LEVEL, + JSON_CURRENT, + JSON_VOLTAGE, + JSON_POWERUSAGE, + JSON_CO2, + JSON_FREQUENCY }; +const char kSensorQuantity[] PROGMEM = + D_JSON_TEMPERATURE "|" + D_JSON_HUMIDITY "|" D_JSON_LIGHT "|" D_JSON_NOISE "|" D_JSON_AIRQUALITY "|" + D_JSON_PRESSURE "|" D_JSON_PRESSUREATSEALEVEL "|" + D_JSON_ILLUMINANCE "|" + D_JSON_GAS "|" + D_JSON_YESTERDAY "|" D_JSON_TOTAL "|" D_JSON_TODAY "|" + D_JSON_PERIOD "|" + D_JSON_POWERFACTOR "|" D_JSON_COUNTER "|" D_JSON_ANALOG_INPUT "|" D_JSON_UV_LEVEL "|" + D_JSON_CURRENT "|" + D_JSON_VOLTAGE "|" + D_JSON_POWERUSAGE "|" + D_JSON_CO2 "|" + D_JSON_FREQUENCY ; + +void DisplayJsonValue(const char* topic, const char* device, const char* mkey, const char* value) +{ + char quantity[TOPSZ]; + char buffer[Settings.display_cols[0] +1]; + char spaces[Settings.display_cols[0]]; + char source[Settings.display_cols[0] - Settings.display_cols[1]]; + char svalue[Settings.display_cols[1] +1]; + +#ifdef USE_DEBUG_DRIVER + ShowFreeMem(PSTR("DisplayJsonValue")); +#endif + + memset(spaces, 0x20, sizeof(spaces)); + spaces[sizeof(spaces) -1] = '\0'; + snprintf_P(source, sizeof(source), PSTR("%s%s%s%s"), topic, (strlen(topic))?"/":"", mkey, spaces); + + int quantity_code = GetCommandCode(quantity, sizeof(quantity), mkey, kSensorQuantity); + if ((-1 == quantity_code) || !strcmp_P(mkey, S_RSLT_POWER)) { + return; + } + if (JSON_TEMPERATURE == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s~%s"), value, disp_temp); + } + else if ((quantity_code >= JSON_HUMIDITY) && (quantity_code <= JSON_AIRQUALITY)) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s%%"), value); + } + else if ((quantity_code >= JSON_PRESSURE) && (quantity_code <= JSON_PRESSUREATSEALEVEL)) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s%s"), value, disp_pres); + } + else if (JSON_ILLUMINANCE == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_LUX), value); + } + else if (JSON_GAS == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_KILOOHM), value); + } + else if ((quantity_code >= JSON_YESTERDAY) && (quantity_code <= JSON_TODAY)) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_KILOWATTHOUR), value); + } + else if (JSON_PERIOD == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_WATTHOUR), value); + } + else if ((quantity_code >= JSON_POWERFACTOR) && (quantity_code <= JSON_UV_LEVEL)) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), value); + } + else if (JSON_CURRENT == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_AMPERE), value); + } + else if (JSON_VOLTAGE == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_VOLT), value); + } + else if (JSON_POWERUSAGE == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_WATT), value); + } + else if (JSON_CO2 == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_PARTS_PER_MILLION), value); + } + else if (JSON_FREQUENCY == quantity_code) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_HERTZ), value); + } + snprintf_P(buffer, sizeof(buffer), PSTR("%s %s"), source, svalue); + + + + DisplayLogBufferAdd(buffer); +} + +void DisplayAnalyzeJson(char *topic, char *json) +{ +# 1145 "S:/Development/Tasmota/tasmota/xdrv_13_display.ino" + String jsonStr = json; + + StaticJsonBuffer<1024> jsonBuf; + JsonObject &root = jsonBuf.parseObject(jsonStr); + if (root.success()) { + + const char *unit; + unit = root[D_JSON_TEMPERATURE_UNIT]; + if (unit) { + snprintf_P(disp_temp, sizeof(disp_temp), PSTR("%s"), unit); + } + unit = root[D_JSON_PRESSURE_UNIT]; + if (unit) { + snprintf_P(disp_pres, sizeof(disp_pres), PSTR("%s"), unit); + } + + for (JsonObject::iterator it = root.begin(); it != root.end(); ++it) { + JsonVariant value = it->value; + if (value.is()) { + JsonObject& Object2 = value; + for (JsonObject::iterator it2 = Object2.begin(); it2 != Object2.end(); ++it2) { + JsonVariant value2 = it2->value; + if (value2.is()) { + JsonObject& Object3 = value2; + for (JsonObject::iterator it3 = Object3.begin(); it3 != Object3.end(); ++it3) { + const char* value = it3->value; + if (value != nullptr) { + DisplayJsonValue(topic, it->key, it3->key, value); + } + } + } else { + const char* value = it2->value; + if (value != nullptr) { + DisplayJsonValue(topic, it->key, it2->key, value); + } + } + } + } else { + const char* value = it->value; + if (value != nullptr) { + DisplayJsonValue(topic, it->key, it->key, value); + } + } + } + } +} + +void DisplayMqttSubscribe(void) +{ + + + + + + + if (Settings.display_model && (Settings.display_mode &0x04)) { + + char stopic[TOPSZ]; + char ntopic[TOPSZ]; + + ntopic[0] = '\0'; + strlcpy(stopic, SettingsText(SET_MQTT_FULLTOPIC), sizeof(stopic)); + char *tp = strtok(stopic, "/"); + while (tp != nullptr) { + if (!strcmp_P(tp, MQTT_TOKEN_PREFIX)) { + break; + } + strncat_P(ntopic, PSTR("+/"), sizeof(ntopic) - strlen(ntopic) -1); + tp = strtok(nullptr, "/"); + } + strncat(ntopic, SettingsText(SET_MQTTPREFIX3), sizeof(ntopic) - strlen(ntopic) -1); + strncat_P(ntopic, PSTR("/#"), sizeof(ntopic) - strlen(ntopic) -1); + MqttSubscribe(ntopic); + disp_subscribed = true; + } else { + disp_subscribed = false; + } +} + +bool DisplayMqttData(void) +{ + if (disp_subscribed) { + char stopic[TOPSZ]; + + snprintf_P(stopic, sizeof(stopic) , PSTR("%s/"), SettingsText(SET_MQTTPREFIX3)); + char *tp = strstr(XdrvMailbox.topic, stopic); + if (tp) { + if (Settings.display_mode &0x04) { + tp = tp + strlen(stopic); + char *topic = strtok(tp, "/"); + DisplayAnalyzeJson(topic, XdrvMailbox.data); + } + return true; + } + } + return false; +} + +void DisplayLocalSensor(void) +{ + if ((Settings.display_mode &0x02) && (0 == tele_period)) { + char no_topic[1] = { 0 }; + + DisplayAnalyzeJson(no_topic, mqtt_data); + } +} + +#endif + + + + + +void DisplayInitDriver(void) +{ + XdspCall(FUNC_DISPLAY_INIT_DRIVER); + + if (renderer) { + renderer->setTextFont(Settings.display_font); + renderer->setTextSize(Settings.display_size); + } + + + + + if (Settings.display_model) { + devices_present++; + disp_device = devices_present; + +#ifndef USE_DISPLAY_MODES1TO5 + Settings.display_mode = 0; +#else + DisplayLogBufferInit(); +#endif + } +} + +void DisplaySetPower(void) +{ + disp_power = bitRead(XdrvMailbox.index, disp_device -1); + + + + if (Settings.display_model) { + if (!renderer) { + XdspCall(FUNC_DISPLAY_POWER); + } else { + renderer->DisplayOnff(disp_power); + } + } +} + + + + + +void CmndDisplay(void) +{ + Response_P(PSTR("{\"" D_PRFX_DISPLAY "\":{\"" D_CMND_DISP_MODEL "\":%d,\"" D_CMND_DISP_WIDTH "\":%d,\"" D_CMND_DISP_HEIGHT "\":%d,\"" + D_CMND_DISP_MODE "\":%d,\"" D_CMND_DISP_DIMMER "\":%d,\"" D_CMND_DISP_SIZE "\":%d,\"" D_CMND_DISP_FONT "\":%d,\"" + D_CMND_DISP_ROTATE "\":%d,\"" D_CMND_DISP_REFRESH "\":%d,\"" D_CMND_DISP_COLS "\":[%d,%d],\"" D_CMND_DISP_ROWS "\":%d}}"), + Settings.display_model, Settings.display_width, Settings.display_height, + Settings.display_mode, Settings.display_dimmer, Settings.display_size, Settings.display_font, + Settings.display_rotate, Settings.display_refresh, Settings.display_cols[0], Settings.display_cols[1], Settings.display_rows); +} + +void CmndDisplayModel(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < DISPLAY_MAX_DRIVERS)) { + uint32_t last_display_model = Settings.display_model; + Settings.display_model = XdrvMailbox.payload; + if (XdspCall(FUNC_DISPLAY_MODEL)) { + restart_flag = 2; + } else { + Settings.display_model = last_display_model; + } + } + ResponseCmndNumber(Settings.display_model); +} + +void CmndDisplayWidth(void) +{ + if (XdrvMailbox.payload > 0) { + if (XdrvMailbox.payload != Settings.display_width) { + Settings.display_width = XdrvMailbox.payload; + restart_flag = 2; + } + } + ResponseCmndNumber(Settings.display_width); +} + +void CmndDisplayHeight(void) +{ + if (XdrvMailbox.payload > 0) { + if (XdrvMailbox.payload != Settings.display_height) { + Settings.display_height = XdrvMailbox.payload; + restart_flag = 2; + } + } + ResponseCmndNumber(Settings.display_height); +} + +void CmndDisplayMode(void) +{ +#ifdef USE_DISPLAY_MODES1TO5 + + + + + + + + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 5)) { + uint32_t last_display_mode = Settings.display_mode; + Settings.display_mode = XdrvMailbox.payload; + + if (disp_subscribed != (Settings.display_mode &0x04)) { + restart_flag = 2; + } else { + if (last_display_mode && !Settings.display_mode) { + DisplayInit(DISPLAY_INIT_MODE); + if (renderer) renderer->fillScreen(bg_color); + else DisplayClear(); + } else { + DisplayLogBufferInit(); + DisplayInit(DISPLAY_INIT_MODE); + } + } + } +#endif + ResponseCmndNumber(Settings.display_mode); +} + +void CmndDisplayDimmer(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { + Settings.display_dimmer = ((XdrvMailbox.payload +1) * 100) / 666; + if (Settings.display_dimmer && !(disp_power)) { + ExecuteCommandPower(disp_device, POWER_ON, SRC_DISPLAY); + } + else if (!Settings.display_dimmer && disp_power) { + ExecuteCommandPower(disp_device, POWER_OFF, SRC_DISPLAY); + } + if (renderer) renderer->dim(Settings.display_dimmer); + } + ResponseCmndNumber(Settings.display_dimmer); +} + +void CmndDisplaySize(void) +{ + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 4)) { + Settings.display_size = XdrvMailbox.payload; + if (renderer) renderer->setTextSize(Settings.display_size); + else DisplaySetSize(Settings.display_size); + } + ResponseCmndNumber(Settings.display_size); +} + +void CmndDisplayFont(void) +{ + if ((XdrvMailbox.payload >=0) && (XdrvMailbox.payload <= 4)) { + Settings.display_font = XdrvMailbox.payload; + if (renderer) renderer->setTextFont(Settings.display_font); + else DisplaySetFont(Settings.display_font); + } + ResponseCmndNumber(Settings.display_font); +} + +void CmndDisplayRotate(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 4)) { + if (Settings.display_rotate != XdrvMailbox.payload) { +# 1428 "S:/Development/Tasmota/tasmota/xdrv_13_display.ino" + Settings.display_rotate = XdrvMailbox.payload; + DisplayInit(DISPLAY_INIT_MODE); +#ifdef USE_DISPLAY_MODES1TO5 + DisplayLogBufferInit(); +#endif + } + } + ResponseCmndNumber(Settings.display_rotate); +} + +void CmndDisplayText(void) +{ + if (disp_device && XdrvMailbox.data_len > 0) { +#ifndef USE_DISPLAY_MODES1TO5 + DisplayText(); +#else + if (!Settings.display_mode) { + DisplayText(); + } else { + DisplayLogBufferAdd(XdrvMailbox.data); + } +#endif + ResponseCmndChar(XdrvMailbox.data); + } +} + +void CmndDisplayAddress(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 8)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) { + Settings.display_address[XdrvMailbox.index -1] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Settings.display_address[XdrvMailbox.index -1]); + } +} + +void CmndDisplayRefresh(void) +{ + if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 7)) { + Settings.display_refresh = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.display_refresh); +} + +void CmndDisplayColumns(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_MAX_COLS)) { + Settings.display_cols[XdrvMailbox.index -1] = XdrvMailbox.payload; +#ifdef USE_DISPLAY_MODES1TO5 + if (1 == XdrvMailbox.index) { + DisplayLogBufferInit(); + DisplayReAllocScreenBuffer(); + } +#endif + } + ResponseCmndIdxNumber(Settings.display_cols[XdrvMailbox.index -1]); + } +} + +void CmndDisplayRows(void) +{ + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_MAX_ROWS)) { + Settings.display_rows = XdrvMailbox.payload; +#ifdef USE_DISPLAY_MODES1TO5 + DisplayLogBufferInit(); + DisplayReAllocScreenBuffer(); +#endif + } + ResponseCmndNumber(Settings.display_rows); +} + + + + + +#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) +void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp) { + if (!renderer) return; + + + File fp; + fp=SD.open(file,FILE_READ); + if (!fp) return; + uint16_t xsize; + fp.read((uint8_t*)&xsize,2); + uint16_t ysize; + fp.read((uint8_t*)&ysize,2); + +#if 1 +#define XBUFF 128 + uint16_t xdiv=xsize/XBUFF; + renderer->setAddrWindow(xp,yp,xp+xsize,yp+ysize); + for(int16_t j=0; j=2) renderer->pushColors(rgb,len/2,true); + } + OsWatchLoop(); + } + renderer->setAddrWindow(0,0,0,0); +#else + for(int16_t j=0; jwritePixel(xp+i,yp,rgb); + } + delay(0); + OsWatchLoop(); + yp++; + } +#endif + fp.close(); +} +#endif + +#ifdef USE_AWATCH +#define MINUTE_REDUCT 4 + +#ifndef pi +#define pi 3.14159265359 +#endif + + +void DrawAClock(uint16_t rad) { + if (!renderer) return; + float frad=rad; + uint16_t hred=frad/3.0; + renderer->fillCircle(disp_xpos, disp_ypos, rad, bg_color); + renderer->drawCircle(disp_xpos, disp_ypos, rad, fg_color); + renderer->fillCircle(disp_xpos, disp_ypos, 4, fg_color); + for (uint8_t count=0; count<60; count+=5) { + float p1=((float)count*(pi/30)-(pi/2)); + uint8_t len; + if ((count%15)==0) { + len=4; + } else { + len=2; + } + renderer->writeLine(disp_xpos+((float)(rad-len)*cosf(p1)), disp_ypos+((float)(rad-len)*sinf(p1)), disp_xpos+(frad*cosf(p1)), disp_ypos+(frad*sinf(p1)), fg_color); + } + + + float hour=((float)RtcTime.hour*60.0+(float)RtcTime.minute)/60.0; + float temp=(hour*(pi/6.0)-(pi/2.0)); + renderer->writeLine(disp_xpos, disp_ypos,disp_xpos+(frad-hred)*cosf(temp),disp_ypos+(frad-hred)*sinf(temp), fg_color); + + + temp=((float)RtcTime.minute*(pi/30.0)-(pi/2.0)); + renderer->writeLine(disp_xpos, disp_ypos,disp_xpos+(frad-MINUTE_REDUCT)*cosf(temp),disp_ypos+(frad-MINUTE_REDUCT)*sinf(temp), fg_color); +} +#endif + + +#ifdef USE_GRAPH + +typedef union { + uint8_t data; + struct { + uint8_t overlay : 1; + uint8_t draw : 1; + uint8_t nu3 : 1; + uint8_t nu4 : 1; + uint8_t nu5 : 1; + uint8_t nu6 : 1; + uint8_t nu7 : 1; + uint8_t nu8 : 1; + }; +} GFLAGS; + +struct GRAPH { + uint16_t xp; + uint16_t yp; + uint16_t xs; + uint16_t ys; + float ymin; + float ymax; + float range; + uint32_t x_time; + uint32_t last_ms; + uint32_t last_ms_redrawn; + int16_t decimation; + uint16_t dcnt; + uint32_t summ; + uint16_t xcnt; + uint8_t *values; + uint8_t xticks; + uint8_t yticks; + uint8_t last_val; + uint8_t color_index; + GFLAGS flags; +}; + + +struct GRAPH *graph[NUM_GRAPHS]; + +#define TICKLEN 4 +void ClrGraph(uint16_t num) { + struct GRAPH *gp=graph[num]; + + uint16_t xticks=gp->xticks; + uint16_t yticks=gp->yticks; + uint16_t count; + + + if (gp->flags.overlay) return; + + renderer->fillRect(gp->xp+1,gp->yp+1,gp->xs-2,gp->ys-2,bg_color); + + if (xticks) { + float cxp=gp->xp,xd=(float)gp->xs/(float)xticks; + for (count=0; countwriteFastVLine(cxp,gp->yp+gp->ys-TICKLEN,TICKLEN,fg_color); + cxp+=xd; + } + } + if (yticks) { + if (gp->ymin<0 && gp->ymax>0) { + + float cxp=0; + float czp=gp->yp+(gp->ymax/gp->range); + while (cxpxs) { + renderer->writeFastHLine(gp->xp+cxp,czp,2,fg_color); + cxp+=6.0; + } + + float cyp=0,yd=gp->ys/yticks; + for (count=0; countgp->yp) { + renderer->writeFastHLine(gp->xp,czp-cyp,TICKLEN,fg_color); + renderer->writeFastHLine(gp->xp+gp->xs-TICKLEN,czp-cyp,TICKLEN,fg_color); + } + if ((czp+cyp)<(gp->yp+gp->ys)) { + renderer->writeFastHLine(gp->xp,czp+cyp,TICKLEN,fg_color); + renderer->writeFastHLine(gp->xp+gp->xs-TICKLEN,czp+cyp,TICKLEN,fg_color); + } + cyp+=yd; + } + } else { + float cyp=gp->yp,yd=gp->ys/yticks; + for (count=0; countwriteFastHLine(gp->xp,cyp,TICKLEN,fg_color); + renderer->writeFastHLine(gp->xp+gp->xs-TICKLEN,cyp,TICKLEN,fg_color); + cyp+=yd; + } + } + } +} + + +void DefineGraph(uint16_t num,uint16_t xp,uint16_t yp,int16_t xs,uint16_t ys,int16_t dec,float ymin, float ymax,uint8_t icol) { + if (!renderer) return; + uint8_t rflg=0; + if (xs<0) { + rflg=1; + xs=abs(xs); + } + struct GRAPH *gp; + uint16_t count; + uint16_t index=num%NUM_GRAPHS; + if (!graph[index]) { + gp=(struct GRAPH*)calloc(sizeof(struct GRAPH),1); + if (!gp) return; + graph[index]=gp; + } else { + gp=graph[index]; + if (rflg) { + RedrawGraph(index,1); + return; + } + } + + + gp->xticks=(num>>4)&0x3f; + gp->yticks=(num>>10)&0x3f; + gp->xp=xp; + gp->yp=yp; + gp->xs=xs; + gp->ys=ys; + if (!dec) dec=1; + gp->decimation=dec; + if (dec>0) { + + gp->x_time=((float)dec*60000.0)/(float)xs; + gp->last_ms=millis()+gp->x_time; + } + gp->ymin=ymin; + gp->ymax=ymax; + gp->range=(ymax-ymin)/ys; + gp->xcnt=0; + gp->dcnt=0; + gp->summ=0; + if (gp->values) free(gp->values); + gp->values=(uint8_t*) calloc(1,xs+2); + if (!gp->values) { + free(gp); + graph[index]=0; + return; + } + + gp->values[0]=0; + + gp->last_ms_redrawn=millis(); + + if (!icol) icol=1; + gp->color_index=icol; + gp->flags.overlay=0; + gp->flags.draw=1; + + + if (index>0) { + for (uint8_t count=0; countxp==gp1->xp) && (gp->yp==gp1->yp)) { + gp->flags.overlay=1; + break; + } + } + } + } + + + renderer->drawRect(xp,yp,xs,ys,fg_color); + + ClrGraph(index); + +} + + +void DisplayCheckGraph() { + int16_t count; + struct GRAPH *gp; + for (count=0;countdecimation>0) { + + while (millis()>gp->last_ms) { + gp->last_ms+=gp->x_time; + uint8_t val; + if (gp->dcnt) { + val=gp->summ/gp->dcnt; + gp->dcnt=0; + gp->summ=0; + gp->last_val=val; + } else { + val=gp->last_val; + } + AddGraph(count,val); + } + } + } + } +} + + +#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) +#include + +void Save_graph(uint8_t num, char *path) { + if (!renderer) return; + uint16_t index=num%NUM_GRAPHS; + struct GRAPH *gp=graph[index]; + if (!gp) return; + File fp; + SD.remove(path); + fp=SD.open(path,FILE_WRITE); + if (!fp) return; + char str[32]; + sprintf_P(str,PSTR("%d\t%d\t%d\t"),gp->xcnt,gp->xs,gp->ys); + fp.print(str); + dtostrfd(gp->ymin,2,str); + fp.print(str); + fp.print("\t"); + dtostrfd(gp->ymax,2,str); + fp.print(str); + fp.print("\t"); + for (uint32_t count=0;countxs;count++) { + dtostrfd(gp->values[count],0,str); + fp.print(str); + fp.print("\t"); + } + fp.print("\n"); + fp.close(); +} +void Restore_graph(uint8_t num, char *path) { + if (!renderer) return; + uint16_t index=num%NUM_GRAPHS; + struct GRAPH *gp=graph[index]; + if (!gp) return; + File fp; + fp=SD.open(path,FILE_READ); + if (!fp) return; + char vbuff[32]; + char *cp=vbuff; + uint8_t buf[2]; + uint8_t findex=0; + + for (uint32_t count=0;count<=gp->xs+4;count++) { + cp=vbuff; + findex=0; + while (fp.available()) { + fp.read(buf,1); + if (buf[0]=='\t' || buf[0]==',' || buf[0]=='\n' || buf[0]=='\r') { + break; + } else { + *cp++=buf[0]; + findex++; + if (findex>=sizeof(vbuff)-1) break; + } + } + *cp=0; + if (count<=4) { + if (count==0) gp->xcnt=atoi(vbuff); + } else { + gp->values[count-5]=atoi(vbuff); + } + } + fp.close(); + RedrawGraph(num,1); +} +#endif + +void RedrawGraph(uint8_t num, uint8_t flags) { + uint16_t index=num%NUM_GRAPHS; + struct GRAPH *gp=graph[index]; + if (!gp) return; + if (!flags) { + gp->flags.draw=0; + return; + } + if (!renderer) return; + + gp->flags.draw=1; + uint16_t linecol=fg_color; + + if (color_type==COLOR_COLOR) { + linecol=renderer->GetColorFromIndex(gp->color_index); + } + + if (!gp->flags.overlay) { + + renderer->drawRect(gp->xp,gp->yp,gp->xs,gp->ys,fg_color); + + ClrGraph(index); + } + + for (uint16_t count=0;countxs-1;count++) { + renderer->writeLine(gp->xp+count,gp->yp+gp->ys-gp->values[count]-1,gp->xp+count+1,gp->yp+gp->ys-gp->values[count+1]-1,linecol); + } +} + + +void AddGraph(uint8_t num,uint8_t val) { + struct GRAPH *gp=graph[num]; + if (!renderer) return; + + uint16_t linecol=fg_color; + if (color_type==COLOR_COLOR) { + linecol=renderer->GetColorFromIndex(gp->color_index); + } + gp->xcnt++; + if (gp->xcnt>gp->xs) { + gp->xcnt=gp->xs; + int16_t count; + + for (count=0;countxs-1;count++) { + gp->values[count]=gp->values[count+1]; + } + gp->values[gp->xcnt-1]=val; + + if (!gp->flags.draw) return; + + + if (millis()-gp->last_ms_redrawn>1000) { + gp->last_ms_redrawn=millis(); + + if (!gp->flags.overlay) { + + renderer->drawRect(gp->xp,gp->yp,gp->xs,gp->ys,fg_color); + + ClrGraph(num); + } + + for (count=0;countxs-1;count++) { + renderer->writeLine(gp->xp+count,gp->yp+gp->ys-gp->values[count]-1,gp->xp+count+1,gp->yp+gp->ys-gp->values[count+1]-1,linecol); + } + } + } else { + + gp->values[gp->xcnt]=val; + if (!gp->flags.draw) return; + renderer->writeLine(gp->xp+gp->xcnt-1,gp->yp+gp->ys-gp->values[gp->xcnt-1]-1,gp->xp+gp->xcnt,gp->yp+gp->ys-gp->values[gp->xcnt]-1,linecol); + } +} + + + +void AddValue(uint8_t num,float fval) { + + num=num%NUM_GRAPHS; + struct GRAPH *gp=graph[num]; + if (!gp) return; + + if (fval>gp->ymax) fval=gp->ymax; + if (fvalymin) fval=gp->ymin; + + int16_t val; + val=(fval-gp->ymin)/gp->range; + + if (val>gp->ys-1) val=gp->ys-1; + if (val<0) val=0; + + + gp->summ+=val; + gp->dcnt++; + + + if (gp->decimation<0) { + if (gp->dcnt>=-gp->decimation) { + gp->dcnt=0; + + val=gp->summ/-gp->decimation; + gp->summ=0; + + AddGraph(num,val); + } + } +} +#endif + + + + + +bool Xdrv13(uint8_t function) +{ + bool result = false; + + if ((i2c_flg || spi_flg || soft_spi_flg) && XdspPresent()) { + switch (function) { + case FUNC_PRE_INIT: + DisplayInitDriver(); +#ifdef USE_GRAPH + for (uint8_t count=0;count + +TasmotaSerial *MP3Player; + + + + + +#define D_CMND_MP3 "MP3" + +const char S_JSON_MP3_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_MP3 "%s\":%d}"; +const char S_JSON_MP3_COMMAND[] PROGMEM = "{\"" D_CMND_MP3 "%s\"}"; +const char kMP3_Commands[] PROGMEM = "Track|Play|Pause|Stop|Volume|EQ|Device|Reset|DAC"; + + + + + +enum MP3_Commands { + CMND_MP3_TRACK, + CMND_MP3_PLAY, + CMND_MP3_PAUSE, + CMND_MP3_STOP, + CMND_MP3_VOLUME, + CMND_MP3_EQ, + CMND_MP3_DEVICE, + CMND_MP3_RESET, + CMND_MP3_DAC }; + + + + + + +#define MP3_CMD_RESET_VALUE 0 + +#define MP3_CMD_TRACK 0x03 +#define MP3_CMD_PLAY 0x0d +#define MP3_CMD_PAUSE 0x0e +#define MP3_CMD_STOP 0x16 +#define MP3_CMD_VOLUME 0x06 +#define MP3_CMD_EQ 0x07 +#define MP3_CMD_DEVICE 0x09 +#define MP3_CMD_RESET 0x0C +#define MP3_CMD_DAC 0x1A + + + + + + +uint16_t MP3_Checksum(uint8_t *array) +{ + uint16_t checksum = 0; + for (uint32_t i = 0; i < 6; i++) { + checksum += array[i]; + } + checksum = checksum^0xffff; + return (checksum+1); +} + + + + + + +void MP3PlayerInit(void) { + MP3Player = new TasmotaSerial(-1, pin[GPIO_MP3_DFR562]); + + if (MP3Player->begin(9600)) { + MP3Player->flush(); + delay(1000); + MP3_CMD(MP3_CMD_RESET, MP3_CMD_RESET_VALUE); + delay(3000); + MP3_CMD(MP3_CMD_VOLUME, MP3_VOLUME); + } + return; +} +# 159 "S:/Development/Tasmota/tasmota/xdrv_14_mp3.ino" +void MP3_CMD(uint8_t mp3cmd,uint16_t val) { + uint8_t i = 0; + uint8_t cmd[10] = {0x7e,0xff,6,0,0,0,0,0,0,0xef}; + cmd[3] = mp3cmd; + cmd[4] = 0; + cmd[5] = val>>8; + cmd[6] = val; + uint16_t chks = MP3_Checksum(&cmd[1]); + cmd[7] = chks>>8; + cmd[8] = chks; + MP3Player->write(cmd, sizeof(cmd)); + delay(1000); + if (mp3cmd == MP3_CMD_RESET) { + MP3_CMD(MP3_CMD_VOLUME, MP3_VOLUME); + } + return; +} + + + + + +bool MP3PlayerCmd(void) { + char command[CMDSZ]; + bool serviced = true; + uint8_t disp_len = strlen(D_CMND_MP3); + + if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_MP3), disp_len)) { + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kMP3_Commands); + + switch (command_code) { + case CMND_MP3_TRACK: + case CMND_MP3_VOLUME: + case CMND_MP3_EQ: + case CMND_MP3_DEVICE: + case CMND_MP3_DAC: + + if (XdrvMailbox.data_len > 0) { + if (command_code == CMND_MP3_TRACK) { MP3_CMD(MP3_CMD_TRACK, XdrvMailbox.payload); } + if (command_code == CMND_MP3_VOLUME) { MP3_CMD(MP3_CMD_VOLUME, XdrvMailbox.payload * 30 / 100); } + if (command_code == CMND_MP3_EQ) { MP3_CMD(MP3_CMD_EQ, XdrvMailbox.payload); } + if (command_code == CMND_MP3_DEVICE) { MP3_CMD(MP3_CMD_DEVICE, XdrvMailbox.payload); } + if (command_code == CMND_MP3_DAC) { MP3_CMD(MP3_CMD_DAC, XdrvMailbox.payload); } + } + Response_P(S_JSON_MP3_COMMAND_NVALUE, command, XdrvMailbox.payload); + break; + case CMND_MP3_PLAY: + case CMND_MP3_PAUSE: + case CMND_MP3_STOP: + case CMND_MP3_RESET: + + if (command_code == CMND_MP3_PLAY) { MP3_CMD(MP3_CMD_PLAY, 0); } + if (command_code == CMND_MP3_PAUSE) { MP3_CMD(MP3_CMD_PAUSE, 0); } + if (command_code == CMND_MP3_STOP) { MP3_CMD(MP3_CMD_STOP, 0); } + if (command_code == CMND_MP3_RESET) { MP3_CMD(MP3_CMD_RESET, 0); } + Response_P(S_JSON_MP3_COMMAND, command, XdrvMailbox.payload); + break; + default: + + serviced = false; + break; + } + } else { + return false; + } + return serviced; +} + + + + + +bool Xdrv14(uint8_t function) +{ + bool result = false; + + if (pin[GPIO_MP3_DFR562] < 99) { + switch (function) { + case FUNC_PRE_INIT: + MP3PlayerInit(); + break; + case FUNC_COMMAND: + result = MP3PlayerCmd(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_15_pca9685.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_15_pca9685.ino" +#ifdef USE_I2C +#ifdef USE_PCA9685 + + + + + + +#define XDRV_15 15 +#define XI2C_01 1 + +#define PCA9685_REG_MODE1 0x00 +#define PCA9685_REG_LED0_ON_L 0x06 +#define PCA9685_REG_PRE_SCALE 0xFE + +#ifndef USE_PCA9685_ADDR + #define USE_PCA9685_ADDR 0x40 +#endif +#ifndef USE_PCA9685_FREQ + #define USE_PCA9685_FREQ 50 +#endif + +bool pca9685_detected = false; +uint16_t pca9685_freq = USE_PCA9685_FREQ; +uint16_t pca9685_pin_pwm_value[16]; + +void PCA9685_Detect(void) +{ + if (I2cActive(USE_PCA9685_ADDR)) { return; } + + uint8_t buffer; + if (I2cValidRead8(&buffer, USE_PCA9685_ADDR, PCA9685_REG_MODE1)) { + I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, 0x20); + if (I2cValidRead8(&buffer, USE_PCA9685_ADDR, PCA9685_REG_MODE1)) { + if (0x20 == buffer) { + pca9685_detected = true; + I2cSetActiveFound(USE_PCA9685_ADDR, "PCA9685"); + PCA9685_Reset(); + } + } + } +} + +void PCA9685_Reset(void) +{ + I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, 0x80); + PCA9685_SetPWMfreq(USE_PCA9685_FREQ); + for (uint32_t pin=0;pin<16;pin++) { + PCA9685_SetPWM(pin,0,false); + pca9685_pin_pwm_value[pin] = 0; + } + Response_P(PSTR("{\"PCA9685\":{\"RESET\":\"OK\"}}")); +} + +void PCA9685_SetPWMfreq(double freq) { + + + + + if (freq > 23 && freq < 1527) { + pca9685_freq=freq; + } else { + pca9685_freq=50; + } + uint8_t pre_scale_osc = round(25000000/(4096*pca9685_freq))-1; + if (1526 == pca9685_freq) pre_scale_osc=0xFF; + uint8_t current_mode1 = I2cRead8(USE_PCA9685_ADDR, PCA9685_REG_MODE1); + uint8_t sleep_mode1 = (current_mode1&0x7F) | 0x10; + I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, sleep_mode1); + I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_PRE_SCALE, pre_scale_osc); + I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, current_mode1 | 0xA0); +} + +void PCA9685_SetPWM_Reg(uint8_t pin, uint16_t on, uint16_t off) { + uint8_t led_reg = PCA9685_REG_LED0_ON_L + 4 * pin; + uint32_t led_data = 0; + I2cWrite8(USE_PCA9685_ADDR, led_reg, on); + I2cWrite8(USE_PCA9685_ADDR, led_reg+1, (on >> 8)); + I2cWrite8(USE_PCA9685_ADDR, led_reg+2, off); + I2cWrite8(USE_PCA9685_ADDR, led_reg+3, (off >> 8)); +} + +void PCA9685_SetPWM(uint8_t pin, uint16_t pwm, bool inverted) { + if (4096 == pwm) { + PCA9685_SetPWM_Reg(pin, 4096, 0); + } else { + PCA9685_SetPWM_Reg(pin, 0, pwm); + } + pca9685_pin_pwm_value[pin] = pwm; +} + +bool PCA9685_Command(void) +{ + bool serviced = true; + bool validpin = false; + uint8_t paramcount = 0; + if (XdrvMailbox.data_len > 0) { + paramcount=1; + } else { + serviced = false; + return serviced; + } + char sub_string[XdrvMailbox.data_len]; + for (uint32_t ca=0;ca 1) { + uint16_t new_freq = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + if ((new_freq >= 24) && (new_freq <= 1526)) { + PCA9685_SetPWMfreq(new_freq); + Response_P(PSTR("{\"PCA9685\":{\"PWMF\":%i, \"Result\":\"OK\"}}"),new_freq); + return serviced; + } + } else { + Response_P(PSTR("{\"PCA9685\":{\"PWMF\":%i}}"),pca9685_freq); + return serviced; + } + } + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"PWM")) { + if (paramcount > 1) { + uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + if (paramcount > 2) { + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 3), "ON")) { + PCA9685_SetPWM(pin, 4096, false); + Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,4096); + serviced = true; + return serviced; + } + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 3), "OFF")) { + PCA9685_SetPWM(pin, 0, false); + Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,0); + serviced = true; + return serviced; + } + uint16_t pwm = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); + if ((pin >= 0 && pin <= 15) && (pwm >= 0 && pwm <= 4096)) { + PCA9685_SetPWM(pin, pwm, false); + Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,pwm); + serviced = true; + return serviced; + } + } + } + } + return serviced; +} + +void PCA9685_OutputTelemetry(bool telemetry) +{ + ResponseTime_P(PSTR(",\"PCA9685\":{\"PWM_FREQ\":%i,"),pca9685_freq); + for (uint32_t pin=0;pin<16;pin++) { + ResponseAppend_P(PSTR("\"PWM%i\":%i,"),pin,pca9685_pin_pwm_value[pin]); + } + ResponseAppend_P(PSTR("\"END\":1}}")); + if (telemetry) { + MqttPublishTeleSensor(); + } +} + +bool Xdrv15(uint8_t function) +{ + if (!I2cEnabled(XI2C_01)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + PCA9685_Detect(); + } + else if (pca9685_detected) { + switch (function) { + case FUNC_EVERY_SECOND: + if (tele_period == 0) { + PCA9685_OutputTelemetry(true); + } + break; + case FUNC_COMMAND_DRIVER: + if (XDRV_15 == XdrvMailbox.index) { + result = PCA9685_Command(); + } + break; + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_16_tuyamcu.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_16_tuyamcu.ino" +#ifdef USE_LIGHT +#ifdef USE_TUYA_MCU + +#define XDRV_16 16 +#define XNRG_16 16 + +#ifndef TUYA_DIMMER_ID +#define TUYA_DIMMER_ID 0 +#endif + +#define TUYA_CMD_HEARTBEAT 0x00 +#define TUYA_CMD_QUERY_PRODUCT 0x01 +#define TUYA_CMD_MCU_CONF 0x02 +#define TUYA_CMD_WIFI_STATE 0x03 +#define TUYA_CMD_WIFI_RESET 0x04 +#define TUYA_CMD_WIFI_SELECT 0x05 +#define TUYA_CMD_SET_DP 0x06 +#define TUYA_CMD_STATE 0x07 +#define TUYA_CMD_QUERY_STATE 0x08 + +#define TUYA_LOW_POWER_CMD_WIFI_STATE 0x02 +#define TUYA_LOW_POWER_CMD_WIFI_RESET 0x03 +#define TUYA_LOW_POWER_CMD_WIFI_CONFIG 0x04 +#define TUYA_LOW_POWER_CMD_STATE 0x05 + +#define TUYA_TYPE_BOOL 0x01 +#define TUYA_TYPE_VALUE 0x02 +#define TUYA_TYPE_STRING 0x03 +#define TUYA_TYPE_ENUM 0x04 + +#define TUYA_BUFFER_SIZE 256 + +#include + +TasmotaSerial *TuyaSerial = nullptr; + +struct TUYA { + uint16_t new_dim = 0; + bool ignore_dim = false; + uint8_t cmd_status = 0; + uint8_t cmd_checksum = 0; + uint8_t data_len = 0; + uint8_t wifi_state = -2; + uint8_t heartbeat_timer = 0; +#ifdef USE_ENERGY_SENSOR + uint32_t lastPowerCheckTime = 0; +#endif + char *buffer = nullptr; + int byte_counter = 0; + bool low_power_mode = false; + bool send_success_next_second = false; +} Tuya; + + +enum TuyaSupportedFunctions { + TUYA_MCU_FUNC_NONE, + TUYA_MCU_FUNC_SWT1 = 1, + TUYA_MCU_FUNC_SWT2, + TUYA_MCU_FUNC_SWT3, + TUYA_MCU_FUNC_SWT4, + TUYA_MCU_FUNC_REL1 = 11, + TUYA_MCU_FUNC_REL2, + TUYA_MCU_FUNC_REL3, + TUYA_MCU_FUNC_REL4, + TUYA_MCU_FUNC_REL5, + TUYA_MCU_FUNC_REL6, + TUYA_MCU_FUNC_REL7, + TUYA_MCU_FUNC_REL8, + TUYA_MCU_FUNC_DIMMER = 21, + TUYA_MCU_FUNC_POWER = 31, + TUYA_MCU_FUNC_CURRENT, + TUYA_MCU_FUNC_VOLTAGE, + TUYA_MCU_FUNC_BATTERY_STATE, + TUYA_MCU_FUNC_BATTERY_PERCENTAGE, + TUYA_MCU_FUNC_REL1_INV = 41, + TUYA_MCU_FUNC_REL2_INV, + TUYA_MCU_FUNC_REL3_INV, + TUYA_MCU_FUNC_REL4_INV, + TUYA_MCU_FUNC_REL5_INV, + TUYA_MCU_FUNC_REL6_INV, + TUYA_MCU_FUNC_REL7_INV, + TUYA_MCU_FUNC_REL8_INV, + TUYA_MCU_FUNC_LOWPOWER_MODE = 51, + TUYA_MCU_FUNC_LAST = 255 +}; + +const char kTuyaCommand[] PROGMEM = "|" + D_CMND_TUYA_MCU "|" D_CMND_TUYA_MCU_SEND_STATE; + +void (* const TuyaCommand[])(void) PROGMEM = { + &CmndTuyaMcu, &CmndTuyaSend +}; +# 127 "S:/Development/Tasmota/tasmota/xdrv_16_tuyamcu.ino" +void CmndTuyaSend(void) { + if (XdrvMailbox.index > 4) { + return; + } + if (XdrvMailbox.index == 0) { + TuyaRequestState(); + } else { + if (XdrvMailbox.data_len > 0) { + char *p; + char *data; + uint8_t i = 0; + uint8_t dpId = 0; + for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 2; str = strtok_r(nullptr, ", ", &p)) { + if ( i == 0) { + dpId = strtoul(str, nullptr, 0); + } else { + data = str; + } + i++; + } + + if (1 == XdrvMailbox.index) { + TuyaSendBool(dpId, strtoul(data, nullptr, 0)); + } else if (2 == XdrvMailbox.index) { + TuyaSendValue(dpId, strtoull(data, nullptr, 0)); + } else if (3 == XdrvMailbox.index) { + TuyaSendString(dpId, data); + } else if (4 == XdrvMailbox.index) { + TuyaSendEnum(dpId, strtoul(data, nullptr, 0)); + } + } + } + ResponseCmndDone(); +} + + + + + + + +void CmndTuyaMcu(void) { + if (XdrvMailbox.data_len > 0) { + char *p; + uint8_t i = 0; + uint8_t parm[3] = { 0 }; + for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 2; str = strtok_r(nullptr, ", ", &p)) { + parm[i] = strtoul(str, nullptr, 0); + i++; + } + + if (TuyaFuncIdValid(parm[0])) { + TuyaAddMcuFunc(parm[0], parm[1]); + restart_flag = 2; + } else { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("TYA: TuyaMcu Invalid function id=%d"), parm[0]); + } + + } + + Response_P(PSTR("{\"" D_CMND_TUYA_MCU "\":[")); + bool added = false; + for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) { + if (Settings.tuya_fnid_map[i].fnid != 0) { + if (added) { + ResponseAppend_P(PSTR(",")); + } + ResponseAppend_P(PSTR("{\"fnId\":%d,\"dpId\":%d}" ), Settings.tuya_fnid_map[i].fnid, Settings.tuya_fnid_map[i].dpid); + added = true; + } + } + ResponseAppend_P(PSTR("]}")); +} + + + + + +void TuyaAddMcuFunc(uint8_t fnId, uint8_t dpId) { + bool added = false; + + if (fnId == 0 || dpId == 0) { + for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) { + if ((dpId > 0 && Settings.tuya_fnid_map[i].dpid == dpId) || (fnId > TUYA_MCU_FUNC_NONE && Settings.tuya_fnid_map[i].fnid == fnId)) { + Settings.tuya_fnid_map[i].fnid = TUYA_MCU_FUNC_NONE; + Settings.tuya_fnid_map[i].dpid = 0; + break; + } + } + } else { + for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) { + if (Settings.tuya_fnid_map[i].dpid == dpId || Settings.tuya_fnid_map[i].dpid == 0 || Settings.tuya_fnid_map[i].fnid == fnId || Settings.tuya_fnid_map[i].fnid == 0) { + if (!added) { + Settings.tuya_fnid_map[i].fnid = fnId; + Settings.tuya_fnid_map[i].dpid = dpId; + added = true; + } else if (Settings.tuya_fnid_map[i].dpid == dpId || Settings.tuya_fnid_map[i].fnid == fnId) { + Settings.tuya_fnid_map[i].fnid = TUYA_MCU_FUNC_NONE; + Settings.tuya_fnid_map[i].dpid = 0; + } + } + } + } + UpdateDevices(); +} + +void UpdateDevices() { + for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) { + uint8_t fnId = Settings.tuya_fnid_map[i].fnid; + if (fnId > TUYA_MCU_FUNC_NONE && Settings.tuya_fnid_map[i].dpid > 0) { + + if (fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) { + bitClear(rel_inverted, fnId - TUYA_MCU_FUNC_REL1); + } else if (fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) { + bitSet(rel_inverted, fnId - TUYA_MCU_FUNC_REL1_INV); + } + + } + } +} + +inline bool TuyaFuncIdValid(uint8_t fnId) { + return (fnId >= TUYA_MCU_FUNC_SWT1 && fnId <= TUYA_MCU_FUNC_SWT4) || + (fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) || + fnId == TUYA_MCU_FUNC_DIMMER || + (fnId >= TUYA_MCU_FUNC_POWER && fnId <= TUYA_MCU_FUNC_VOLTAGE) || + (fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) || + (fnId == TUYA_MCU_FUNC_LOWPOWER_MODE); +} + +uint8_t TuyaGetFuncId(uint8_t dpid) { + for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) { + if (Settings.tuya_fnid_map[i].dpid == dpid) { + return Settings.tuya_fnid_map[i].fnid; + } + } + return TUYA_MCU_FUNC_NONE; +} + +uint8_t TuyaGetDpId(uint8_t fnId) { + for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) { + if (Settings.tuya_fnid_map[i].fnid == fnId) { + return Settings.tuya_fnid_map[i].dpid; + } + } + return 0; +} + +void TuyaSendCmd(uint8_t cmd, uint8_t payload[] = nullptr, uint16_t payload_len = 0) +{ + uint8_t checksum = (0xFF + cmd + (payload_len >> 8) + (payload_len & 0xFF)); + TuyaSerial->write(0x55); + TuyaSerial->write(0xAA); + TuyaSerial->write((uint8_t)0x00); + TuyaSerial->write(cmd); + TuyaSerial->write(payload_len >> 8); + TuyaSerial->write(payload_len & 0xFF); + snprintf_P(log_data, sizeof(log_data), PSTR("TYA: Send \"55aa00%02x%02x%02x"), cmd, payload_len >> 8, payload_len & 0xFF); + for (uint32_t i = 0; i < payload_len; ++i) { + TuyaSerial->write(payload[i]); + checksum += payload[i]; + snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, payload[i]); + } + TuyaSerial->write(checksum); + TuyaSerial->flush(); + snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x\""), log_data, checksum); + AddLog(LOG_LEVEL_DEBUG); +} + +void TuyaSendState(uint8_t id, uint8_t type, uint8_t* value) +{ + uint16_t payload_len = 4; + uint8_t payload_buffer[8]; + payload_buffer[0] = id; + payload_buffer[1] = type; + switch (type) { + case TUYA_TYPE_BOOL: + case TUYA_TYPE_ENUM: + payload_len += 1; + payload_buffer[2] = 0x00; + payload_buffer[3] = 0x01; + payload_buffer[4] = value[0]; + break; + case TUYA_TYPE_VALUE: + payload_len += 4; + payload_buffer[2] = 0x00; + payload_buffer[3] = 0x04; + payload_buffer[4] = value[3]; + payload_buffer[5] = value[2]; + payload_buffer[6] = value[1]; + payload_buffer[7] = value[0]; + break; + + } + + TuyaSendCmd(TUYA_CMD_SET_DP, payload_buffer, payload_len); +} + +void TuyaSendBool(uint8_t id, bool value) +{ + TuyaSendState(id, TUYA_TYPE_BOOL, (uint8_t*)&value); +} + +void TuyaSendValue(uint8_t id, uint32_t value) +{ + TuyaSendState(id, TUYA_TYPE_VALUE, (uint8_t*)(&value)); +} + +void TuyaSendEnum(uint8_t id, uint32_t value) +{ + TuyaSendState(id, TUYA_TYPE_ENUM, (uint8_t*)(&value)); +} + +void TuyaSendString(uint8_t id, char data[]) { + + uint16_t len = strlen(data); + uint16_t payload_len = 4 + len; + uint8_t payload_buffer[payload_len]; + payload_buffer[0] = id; + payload_buffer[1] = TUYA_TYPE_STRING; + payload_buffer[2] = len >> 8; + payload_buffer[3] = len & 0xFF; + + for (uint16_t i = 0; i < len; i++) { + payload_buffer[4+i] = data[i]; + } + + TuyaSendCmd(TUYA_CMD_SET_DP, payload_buffer, payload_len); +} + +bool TuyaSetPower(void) +{ + bool status = false; + + uint8_t rpower = XdrvMailbox.index; + int16_t source = XdrvMailbox.payload; + + uint8_t dpid = TuyaGetDpId(TUYA_MCU_FUNC_REL1 + active_device - 1); + if (dpid == 0) dpid = TuyaGetDpId(TUYA_MCU_FUNC_REL1_INV + active_device - 1); + + if (source != SRC_SWITCH && TuyaSerial) { + TuyaSendBool(dpid, bitRead(rpower, active_device-1) ^ bitRead(rel_inverted, active_device-1)); + status = true; + } + return status; +} + +bool TuyaSetChannels(void) +{ + LightSerialDuty(((uint8_t*)XdrvMailbox.data)[0]); + delay(20); + return true; +} + +void LightSerialDuty(uint16_t duty) +{ + uint8_t dpid = TuyaGetDpId(TUYA_MCU_FUNC_DIMMER); + if (duty > 0 && !Tuya.ignore_dim && TuyaSerial && dpid > 0) { + duty = changeUIntScale(duty, 0, 255, 0, Settings.dimmer_hw_max); + if (duty < Settings.dimmer_hw_min) { duty = Settings.dimmer_hw_min; } + if (Tuya.new_dim != duty) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim value=%d (id=%d)"), duty, dpid); + TuyaSendValue(dpid, duty); + } + } else if (dpid > 0) { + Tuya.ignore_dim = false; + duty = changeUIntScale(duty, 0, 255, 0, Settings.dimmer_hw_max); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim skipped value=%d"), duty); + } else { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Cannot set dimmer. Dimmer Id unknown")); + } +} + +void TuyaRequestState(void) +{ + if (TuyaSerial) { + + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Read MCU state")); + + TuyaSendCmd(TUYA_CMD_QUERY_STATE); + } +} + +void TuyaResetWifi(void) +{ + if (!Settings.flag.button_restrict) { + char scmnd[20]; + snprintf_P(scmnd, sizeof(scmnd), D_CMND_WIFICONFIG " %d", 2); + ExecuteCommand(scmnd, SRC_BUTTON); + } +} + +void TuyaProcessStatePacket(void) { + char scmnd[20]; + uint8_t dpidStart = 6; + uint8_t fnId; + uint16_t dpDataLen; + + while (dpidStart + 4 < Tuya.byte_counter) { + dpDataLen = Tuya.buffer[dpidStart + 2] << 8 | Tuya.buffer[dpidStart + 3]; + fnId = TuyaGetFuncId(Tuya.buffer[dpidStart]); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: fnId=%d is set for dpId=%d"), fnId, Tuya.buffer[dpidStart]); + + if (Tuya.buffer[dpidStart + 1] == 1) { + + if (fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Relay-%d --> MCU State: %s Current State:%s"), fnId - TUYA_MCU_FUNC_REL1 + 1, Tuya.buffer[dpidStart + 4]?"On":"Off",bitRead(power, fnId - TUYA_MCU_FUNC_REL1)?"On":"Off"); + if ((power || Settings.light_dimmer > 0) && (Tuya.buffer[dpidStart + 4] != bitRead(power, fnId - TUYA_MCU_FUNC_REL1))) { + ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1 + 1, Tuya.buffer[dpidStart + 4], SRC_SWITCH); + } + } else if (fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Relay-%d-Inverted --> MCU State: %s Current State:%s"), fnId - TUYA_MCU_FUNC_REL1_INV + 1, Tuya.buffer[dpidStart + 4]?"Off":"On",bitRead(power, fnId - TUYA_MCU_FUNC_REL1_INV) ^ 1?"Off":"On"); + if (Tuya.buffer[dpidStart + 4] != bitRead(power, fnId - TUYA_MCU_FUNC_REL1_INV) ^ 1) { + ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1_INV + 1, Tuya.buffer[dpidStart + 4] ^ 1, SRC_SWITCH); + } + } else if (fnId >= TUYA_MCU_FUNC_SWT1 && fnId <= TUYA_MCU_FUNC_SWT4) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Switch-%d --> MCU State: %d Current State:%d"),fnId - TUYA_MCU_FUNC_SWT1 + 1,Tuya.buffer[dpidStart + 4], SwitchGetVirtual(fnId - TUYA_MCU_FUNC_SWT1)); + + if (SwitchGetVirtual(fnId - TUYA_MCU_FUNC_SWT1) != Tuya.buffer[dpidStart + 4]) { + SwitchSetVirtual(fnId - TUYA_MCU_FUNC_SWT1, Tuya.buffer[dpidStart + 4]); + SwitchHandler(1); + } + } + + } + else if (Tuya.buffer[dpidStart + 1] == 2) { + bool tuya_energy_enabled = (XNRG_16 == energy_flg); + uint16_t packetValue = Tuya.buffer[dpidStart + 6] << 8 | Tuya.buffer[dpidStart + 7]; + if (fnId == TUYA_MCU_FUNC_DIMMER) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Dim State=%d"), packetValue); + Tuya.new_dim = changeUIntScale(packetValue, 0, Settings.dimmer_hw_max, 0, 100); + if ((power || Settings.flag3.tuya_apply_o20) && + (Tuya.new_dim > 0) && (abs(Tuya.new_dim - Settings.light_dimmer) > 1)) { + Tuya.ignore_dim = true; + + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER " %d"), Tuya.new_dim ); + ExecuteCommand(scmnd, SRC_SWITCH); + } + } + + #ifdef USE_ENERGY_SENSOR + else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_VOLTAGE) { + Energy.voltage[0] = (float)packetValue / 10; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Voltage=%d"), Tuya.buffer[dpidStart], packetValue); + } else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_CURRENT) { + Energy.current[0] = (float)packetValue / 1000; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Current=%d"), Tuya.buffer[dpidStart], packetValue); + } else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_POWER) { + Energy.active_power[0] = (float)packetValue / 10; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Active_Power=%d"), Tuya.buffer[dpidStart], packetValue); + + if (Tuya.lastPowerCheckTime != 0 && Energy.active_power[0] > 0) { + Energy.kWhtoday += (float)Energy.active_power[0] * (Rtc.utc_time - Tuya.lastPowerCheckTime) / 36; + EnergyUpdateToday(); + } + Tuya.lastPowerCheckTime = Rtc.utc_time; + } + #endif + + } + + + dpidStart += dpDataLen + 4; + } +} + +void TuyaLowPowerModePacketProcess(void) { + switch (Tuya.buffer[3]) { + case TUYA_CMD_QUERY_PRODUCT: + TuyaHandleProductInfoPacket(); + TuyaSetWifiLed(); + break; + + case TUYA_LOW_POWER_CMD_STATE: + TuyaProcessStatePacket(); + Tuya.send_success_next_second = true; + break; + } + +} + +void TuyaHandleProductInfoPacket(void) { + uint16_t dataLength = Tuya.buffer[4] << 8 | Tuya.buffer[5]; + char *data = &Tuya.buffer[6]; + AddLog_P2(LOG_LEVEL_INFO, PSTR("TYA: MCU Product ID: %.*s"), dataLength, data); +} + +void TuyaSendLowPowerSuccessIfNeeded(void) { + uint8_t success = 1; + + if (Tuya.send_success_next_second) { + TuyaSendCmd(TUYA_LOW_POWER_CMD_STATE, &success, 1); + Tuya.send_success_next_second = false; + } +} + +void TuyaNormalPowerModePacketProcess(void) +{ + switch (Tuya.buffer[3]) { + case TUYA_CMD_QUERY_PRODUCT: + TuyaHandleProductInfoPacket(); + TuyaSendCmd(TUYA_CMD_MCU_CONF); + break; + + case TUYA_CMD_HEARTBEAT: + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Heartbeat")); + if (Tuya.buffer[6] == 0) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Detected MCU restart")); + Tuya.wifi_state = -2; + } + break; + + case TUYA_CMD_STATE: + TuyaProcessStatePacket(); + break; + + case TUYA_CMD_WIFI_RESET: + case TUYA_CMD_WIFI_SELECT: + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX WiFi Reset")); + TuyaResetWifi(); + break; + + case TUYA_CMD_WIFI_STATE: + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX WiFi LED set ACK")); + Tuya.wifi_state = TuyaGetTuyaWifiState(); + break; + + case TUYA_CMD_MCU_CONF: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX MCU configuration Mode=%d"), Tuya.buffer[5]); + + if (Tuya.buffer[5] == 2) { + uint8_t led1_gpio = Tuya.buffer[6]; + uint8_t key1_gpio = Tuya.buffer[7]; + bool key1_set = false; + bool led1_set = false; + for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { + if (Settings.my_gp.io[i] == GPIO_LED1) led1_set = true; + else if (Settings.my_gp.io[i] == GPIO_KEY1) key1_set = true; + } + if (!Settings.my_gp.io[led1_gpio] && !led1_set) { + Settings.my_gp.io[led1_gpio] = GPIO_LED1; + restart_flag = 2; + } + if (!Settings.my_gp.io[key1_gpio] && !key1_set) { + Settings.my_gp.io[key1_gpio] = GPIO_KEY1; + restart_flag = 2; + } + } + TuyaRequestState(); + break; + + default: + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX unknown command")); + } +} + + + + + +bool TuyaModuleSelected(void) +{ + if (!(pin[GPIO_TUYA_RX] < 99) || !(pin[GPIO_TUYA_TX] < 99)) { + pin[GPIO_TUYA_TX] = 1; + pin[GPIO_TUYA_RX] = 3; + Settings.my_gp.io[1] = GPIO_TUYA_TX; + Settings.my_gp.io[3] = GPIO_TUYA_RX; + restart_flag = 2; + } + + if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER) == 0 && TUYA_DIMMER_ID > 0) { + TuyaAddMcuFunc(TUYA_MCU_FUNC_DIMMER, TUYA_DIMMER_ID); + } + + bool relaySet = false; + + for (uint8_t i = 0 ; i < MAX_TUYA_FUNCTIONS; i++) { + if ((Settings.tuya_fnid_map[i].fnid >= TUYA_MCU_FUNC_REL1 && Settings.tuya_fnid_map[i].fnid <= TUYA_MCU_FUNC_REL8 ) || + (Settings.tuya_fnid_map[i].fnid >= TUYA_MCU_FUNC_REL1_INV && Settings.tuya_fnid_map[i].fnid <= TUYA_MCU_FUNC_REL8_INV )) { + relaySet = true; + devices_present++; + } + } + + if (!relaySet) { + TuyaAddMcuFunc(TUYA_MCU_FUNC_REL1, 1); + devices_present++; + SettingsSaveAll(); + } + + if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER) != 0) { + light_type = LT_SERIAL1; + } else { + light_type = LT_BASIC; + } + + if (TuyaGetDpId(TUYA_MCU_FUNC_LOWPOWER_MODE) != 0) { + Tuya.low_power_mode = true; + Settings.flag3.fast_power_cycle_disable = true; + } + + UpdateDevices(); + return true; +} + +void TuyaInit(void) +{ + Tuya.buffer = (char*)(malloc(TUYA_BUFFER_SIZE)); + if (Tuya.buffer != nullptr) { + TuyaSerial = new TasmotaSerial(pin[GPIO_TUYA_RX], pin[GPIO_TUYA_TX], 2); + if (TuyaSerial->begin(9600)) { + if (TuyaSerial->hardwareSerial()) { ClaimSerial(); } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Request MCU configuration")); + + TuyaSendCmd(TUYA_CMD_QUERY_PRODUCT); + } + } + Tuya.heartbeat_timer = 0; +} + +void TuyaSerialInput(void) +{ + while (TuyaSerial->available()) { + yield(); + uint8_t serial_in_byte = TuyaSerial->read(); + + if (serial_in_byte == 0x55) { + Tuya.cmd_status = 1; + Tuya.buffer[Tuya.byte_counter++] = serial_in_byte; + Tuya.cmd_checksum += serial_in_byte; + } + else if (Tuya.cmd_status == 1 && serial_in_byte == 0xAA) { + Tuya.cmd_status = 2; + + Tuya.byte_counter = 0; + Tuya.buffer[Tuya.byte_counter++] = 0x55; + Tuya.buffer[Tuya.byte_counter++] = 0xAA; + Tuya.cmd_checksum = 0xFF; + } + else if (Tuya.cmd_status == 2) { + if (Tuya.byte_counter == 5) { + Tuya.cmd_status = 3; + Tuya.data_len = serial_in_byte; + } + Tuya.cmd_checksum += serial_in_byte; + Tuya.buffer[Tuya.byte_counter++] = serial_in_byte; + } + else if ((Tuya.cmd_status == 3) && (Tuya.byte_counter == (6 + Tuya.data_len)) && (Tuya.cmd_checksum == serial_in_byte)) { + Tuya.buffer[Tuya.byte_counter++] = serial_in_byte; + + char hex_char[(Tuya.byte_counter * 2) + 2]; + uint16_t len = Tuya.buffer[4] << 8 | Tuya.buffer[5]; + Response_P(PSTR("{\"" D_JSON_TUYA_MCU_RECEIVED "\":{\"Data\":\"%s\",\"Cmnd\":%d"), ToHex_P((unsigned char*)Tuya.buffer, Tuya.byte_counter, hex_char, sizeof(hex_char)), Tuya.buffer[3]); + + if (len > 0) { + ResponseAppend_P(PSTR(",\"CmndData\":\"%s\""), ToHex_P((unsigned char*)&Tuya.buffer[6], len, hex_char, sizeof(hex_char))); + if (TUYA_CMD_STATE == Tuya.buffer[3]) { + + + uint8_t dpidStart = 6; + while (dpidStart + 4 < Tuya.byte_counter) { + uint8_t dpId = Tuya.buffer[dpidStart]; + uint8_t dpDataType = Tuya.buffer[dpidStart + 1]; + uint16_t dpDataLen = Tuya.buffer[dpidStart + 2] << 8 | Tuya.buffer[dpidStart + 3]; + const unsigned char *dpData = (unsigned char*)&Tuya.buffer[dpidStart + 4]; + const char *dpHexData = ToHex_P(dpData, dpDataLen, hex_char, sizeof(hex_char)); + + if (TUYA_CMD_STATE == Tuya.buffer[3]) { + ResponseAppend_P(PSTR(",\"DpType%uId%u\":"), dpDataType, dpId); + if (TUYA_TYPE_BOOL == dpDataType && dpDataLen == 1) { + ResponseAppend_P(PSTR("%u"), dpData[0]); + } else if (TUYA_TYPE_VALUE == dpDataType && dpDataLen == 4) { + uint32_t dpValue = (uint32_t)dpData[0] << 24 | (uint32_t)dpData[1] << 16 | (uint32_t)dpData[2] << 8 | (uint32_t)dpData[3] << 0; + ResponseAppend_P(PSTR("%u"), dpValue); + } else if (TUYA_TYPE_STRING == dpDataType) { + ResponseAppend_P(PSTR("\"%.*s\""), dpDataLen, dpData); + } else if (TUYA_TYPE_ENUM == dpDataType && dpDataLen == 1) { + ResponseAppend_P(PSTR("%u"), dpData[0]); + } else { + ResponseAppend_P(PSTR("\"0x%s\""), dpHexData); + } + } + + ResponseAppend_P(PSTR(",\"%d\":{\"DpId\":%d,\"DpIdType\":%d,\"DpIdData\":\"%s\""), dpId, dpId, dpDataType, dpHexData); + if (TUYA_TYPE_STRING == dpDataType) { + ResponseAppend_P(PSTR(",\"Type3Data\":\"%.*s\""), dpDataLen, dpData); + } + ResponseAppend_P(PSTR("}")); + dpidStart += dpDataLen + 4; + } + } + } + + ResponseAppend_P(PSTR("}}")); + + if (Settings.flag3.tuya_serial_mqtt_publish) { + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_TUYA_MCU_RECEIVED)); + } else { + AddLog_P(LOG_LEVEL_DEBUG, mqtt_data); + } + XdrvRulesProcess(); + + if (!Tuya.low_power_mode) { + TuyaNormalPowerModePacketProcess(); + } else { + TuyaLowPowerModePacketProcess(); + } + + Tuya.byte_counter = 0; + Tuya.cmd_status = 0; + Tuya.cmd_checksum = 0; + Tuya.data_len = 0; + } + else if (Tuya.byte_counter < TUYA_BUFFER_SIZE -1) { + Tuya.buffer[Tuya.byte_counter++] = serial_in_byte; + Tuya.cmd_checksum += serial_in_byte; + } else { + Tuya.byte_counter = 0; + Tuya.cmd_status = 0; + Tuya.cmd_checksum = 0; + Tuya.data_len = 0; + } + } +} + +bool TuyaButtonPressed(void) +{ + if (!XdrvMailbox.index && ((PRESSED == XdrvMailbox.payload) && (NOT_PRESSED == Button.last_state[XdrvMailbox.index]))) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Reset GPIO triggered")); + TuyaResetWifi(); + return true; + } + return false; +} + +uint8_t TuyaGetTuyaWifiState(void) { + + uint8_t wifi_state = 0x02; + switch(WifiState()){ + case WIFI_MANAGER: + wifi_state = 0x01; + break; + case WIFI_RESTART: + wifi_state = 0x03; + break; + } + + if (MqttIsConnected()) { + wifi_state = 0x04; + } + + return wifi_state; +} + +void TuyaSetWifiLed(void) +{ + Tuya.wifi_state = TuyaGetTuyaWifiState(); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Set WiFi LED %d (%d)"), Tuya.wifi_state, WifiState()); + + if (Tuya.low_power_mode) { + TuyaSendCmd(TUYA_LOW_POWER_CMD_WIFI_STATE, &Tuya.wifi_state, 1); + } else { + TuyaSendCmd(TUYA_CMD_WIFI_STATE, &Tuya.wifi_state, 1); + } +} + +#ifdef USE_ENERGY_SENSOR + + + + +bool Xnrg16(uint8_t function) +{ + bool result = false; + + if (TUYA_DIMMER == my_module_type) { + if (FUNC_PRE_INIT == function) { + if (TuyaGetDpId(TUYA_MCU_FUNC_POWER) != 0) { + if (TuyaGetDpId(TUYA_MCU_FUNC_CURRENT) == 0) { + Energy.current_available = false; + } + if (TuyaGetDpId(TUYA_MCU_FUNC_VOLTAGE) == 0) { + Energy.voltage_available = false; + } + energy_flg = XNRG_16; + } + } + } + return result; +} +#endif + + + + + +bool Xdrv16(uint8_t function) +{ + bool result = false; + + if (TUYA_DIMMER == my_module_type) { + switch (function) { + case FUNC_LOOP: + if (TuyaSerial) { TuyaSerialInput(); } + break; + case FUNC_MODULE_INIT: + result = TuyaModuleSelected(); + break; + case FUNC_PRE_INIT: + TuyaInit(); + break; + case FUNC_SET_DEVICE_POWER: + result = TuyaSetPower(); + break; + case FUNC_BUTTON_PRESSED: + result = TuyaButtonPressed(); + break; + case FUNC_EVERY_SECOND: + if (TuyaSerial && Tuya.wifi_state != TuyaGetTuyaWifiState()) { TuyaSetWifiLed(); } + if (!Tuya.low_power_mode) { + Tuya.heartbeat_timer++; + if (Tuya.heartbeat_timer > 10) { + Tuya.heartbeat_timer = 0; + TuyaSendCmd(TUYA_CMD_HEARTBEAT); + } + } else { + TuyaSendLowPowerSuccessIfNeeded(); + } + break; + case FUNC_SET_CHANNELS: + result = TuyaSetChannels(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kTuyaCommand, TuyaCommand); + break; + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_17_rcswitch.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_17_rcswitch.ino" +#ifdef USE_RC_SWITCH + + + + +#define XDRV_17 17 + +#define D_JSON_RF_PROTOCOL "Protocol" +#define D_JSON_RF_BITS "Bits" +#define D_JSON_RF_DATA "Data" + +#define D_CMND_RFSEND "RFSend" +#define D_JSON_RF_PULSE "Pulse" +#define D_JSON_RF_REPEAT "Repeat" + +const char kRfSendCommands[] PROGMEM = "|" + D_CMND_RFSEND; + +void (* const RfSendCommand[])(void) PROGMEM = { + &CmndRfSend }; + +#include + +RCSwitch mySwitch = RCSwitch(); + +#define RF_TIME_AVOID_DUPLICATE 1000 + +uint32_t rf_lasttime = 0; + +void RfReceiveCheck(void) +{ + if (mySwitch.available()) { + + unsigned long data = mySwitch.getReceivedValue(); + unsigned int bits = mySwitch.getReceivedBitlength(); + int protocol = mySwitch.getReceivedProtocol(); + int delay = mySwitch.getReceivedDelay(); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RFR: Data 0x%lX (%u), Bits %d, Protocol %d, Delay %d"), data, data, bits, protocol, delay); + + uint32_t now = millis(); + if ((now - rf_lasttime > RF_TIME_AVOID_DUPLICATE) && (data > 0)) { + rf_lasttime = now; + + char stemp[16]; + if (Settings.flag.rf_receive_decimal) { + snprintf_P(stemp, sizeof(stemp), PSTR("%u"), (uint32_t)data); + } else { + snprintf_P(stemp, sizeof(stemp), PSTR("\"0x%lX\""), (uint32_t)data); + } + ResponseTime_P(PSTR(",\"" D_JSON_RFRECEIVED "\":{\"" D_JSON_RF_DATA "\":%s,\"" D_JSON_RF_BITS "\":%d,\"" D_JSON_RF_PROTOCOL "\":%d,\"" D_JSON_RF_PULSE "\":%d}}"), + stemp, bits, protocol, delay); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_RFRECEIVED)); + XdrvRulesProcess(); +#ifdef USE_DOMOTICZ + DomoticzSensor(DZ_COUNT, data); +#endif + } + mySwitch.resetAvailable(); + } +} + +void RfInit(void) +{ + if (pin[GPIO_RFSEND] < 99) { + mySwitch.enableTransmit(pin[GPIO_RFSEND]); + } + if (pin[GPIO_RFRECV] < 99) { + pinMode( pin[GPIO_RFRECV], INPUT); + mySwitch.enableReceive(pin[GPIO_RFRECV]); + } +} + + + + + +void CmndRfSend(void) +{ + bool error = false; + + if (XdrvMailbox.data_len) { + unsigned long data = 0; + unsigned int bits = 24; + int protocol = 1; + int repeat = 10; + int pulse = 350; + + char dataBufUc[XdrvMailbox.data_len + 1]; + UpperCase(dataBufUc, XdrvMailbox.data); + StaticJsonBuffer<150> jsonBuf; + JsonObject &root = jsonBuf.parseObject(dataBufUc); + if (root.success()) { + + char parm_uc[10]; + data = strtoul(root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_DATA))], nullptr, 0); + bits = root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_BITS))]; + protocol = root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_PROTOCOL))]; + repeat = root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_REPEAT))]; + pulse = root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_PULSE))]; + } else { + + char *p; + uint8_t i = 0; + for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 5; str = strtok_r(nullptr, ", ", &p)) { + switch (i++) { + case 0: + data = strtoul(str, nullptr, 0); + break; + case 1: + bits = atoi(str); + break; + case 2: + protocol = atoi(str); + break; + case 3: + repeat = atoi(str); + break; + case 4: + pulse = atoi(str); + } + } + } + + if (!protocol) { protocol = 1; } + mySwitch.setProtocol(protocol); + if (!pulse) { pulse = 350; } + mySwitch.setPulseLength(pulse); + if (!repeat) { repeat = 10; } + mySwitch.setRepeatTransmit(repeat); + if (!bits) { bits = 24; } + if (data) { + mySwitch.send(data, bits); + ResponseCmndDone(); + } else { + error = true; + } + } else { + error = true; + } + if (error) { + Response_P(PSTR("{\"" D_CMND_RFSEND "\":\"" D_JSON_NO " " D_JSON_RF_DATA ", " D_JSON_RF_BITS ", " D_JSON_RF_PROTOCOL ", " D_JSON_RF_REPEAT " " D_JSON_OR " " D_JSON_RF_PULSE "\"}")); + } +} + + + + + +bool Xdrv17(uint8_t function) +{ + bool result = false; + + if ((pin[GPIO_RFSEND] < 99) || (pin[GPIO_RFRECV] < 99)) { + switch (function) { + case FUNC_EVERY_50_MSECOND: + if (pin[GPIO_RFRECV] < 99) { + RfReceiveCheck(); + } + break; + case FUNC_COMMAND: + if (pin[GPIO_RFSEND] < 99) { + result = DecodeCommand(kRfSendCommands, RfSendCommand); + } + break; + case FUNC_INIT: + RfInit(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_18_armtronix_dimmers.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_18_armtronix_dimmers.ino" +#ifdef USE_LIGHT +#ifdef USE_ARMTRONIX_DIMMERS + + + + + + + +#define XDRV_18 18 + +#include + +TasmotaSerial *ArmtronixSerial = nullptr; + +struct ARMTRONIX { + bool ignore_dim = false; + int8_t wifi_state = -2; + int8_t dim_state[2]; + int8_t knob_state[2]; +} Armtronix; + + + + + +bool ArmtronixSetChannels(void) +{ + LightSerial2Duty(((uint8_t*)XdrvMailbox.data)[0], ((uint8_t*)XdrvMailbox.data)[1]); + return true; +} + +void LightSerial2Duty(uint8_t duty1, uint8_t duty2) +{ + if (ArmtronixSerial && !Armtronix.ignore_dim) { + duty1 = ((float)duty1)/2.575757; + duty2 = ((float)duty2)/2.575757; + Armtronix.dim_state[0] = duty1; + Armtronix.dim_state[1] = duty2; + ArmtronixSerial->print("Dimmer1:"); + ArmtronixSerial->print(duty1); + ArmtronixSerial->print("\nDimmer2:"); + ArmtronixSerial->println(duty2); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ARM: Send Serial Packet Dim Values=%d,%d"), Armtronix.dim_state[0],Armtronix.dim_state[1]); + + } else { + Armtronix.ignore_dim = false; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ARM: Send Dim Level skipped due to already set. Value=%d,%d"), Armtronix.dim_state[0],Armtronix.dim_state[1]); + + } +} + +void ArmtronixRequestState(void) +{ + if (ArmtronixSerial) { + + AddLog_P(LOG_LEVEL_DEBUG, PSTR("ARM: Request MCU state")); + ArmtronixSerial->println("Status"); + + } +} + + + + + +bool ArmtronixModuleSelected(void) +{ + devices_present++; + light_type = LT_SERIAL2; + return true; +} + +void ArmtronixInit(void) +{ + Armtronix.dim_state[0] = -1; + Armtronix.dim_state[1] = -1; + Armtronix.knob_state[0] = -1; + Armtronix.knob_state[1] = -1; + ArmtronixSerial = new TasmotaSerial(pin[GPIO_RXD], pin[GPIO_TXD], 2); + if (ArmtronixSerial->begin(115200)) { + if (ArmtronixSerial->hardwareSerial()) { ClaimSerial(); } + ArmtronixSerial->println("Status"); + } +} + +void ArmtronixSerialInput(void) +{ + String answer; + int8_t newDimState[2]; + uint8_t temp; + int commaIndex; + char scmnd[20]; + if (ArmtronixSerial->available()) { + yield(); + answer = ArmtronixSerial->readStringUntil('\n'); + if (answer.substring(0,7) == "Status:") { + commaIndex = 6; + for (uint32_t i =0; i<2; i++) { + newDimState[i] = answer.substring(commaIndex+1,answer.indexOf(',',commaIndex+1)).toInt(); + if (newDimState[i] != Armtronix.dim_state[i]) { + temp = ((float)newDimState[i])*1.01010101010101; + Armtronix.dim_state[i] = newDimState[i]; + Armtronix.ignore_dim = true; + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_CHANNEL "%d %d"),i+1, temp); + ExecuteCommand(scmnd,SRC_SWITCH); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ARM: Send CMND_CHANNEL=%s"), scmnd ); + } + commaIndex = answer.indexOf(',',commaIndex+1); + } + Armtronix.knob_state[0] = answer.substring(commaIndex+1,answer.indexOf(',',commaIndex+1)).toInt(); + commaIndex = answer.indexOf(',',commaIndex+1); + Armtronix.knob_state[1] = answer.substring(commaIndex+1,answer.indexOf(',',commaIndex+1)).toInt(); + } + } +} + +void ArmtronixSetWifiLed(void) +{ + uint8_t wifi_state = 0x02; + + switch (WifiState()) { + case WIFI_MANAGER: + wifi_state = 0x01; + break; + case WIFI_RESTART: + wifi_state = 0x03; + break; + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ARM: Set WiFi LED to state %d (%d)"), wifi_state, WifiState()); + + char state = '0' + ((wifi_state & 1) > 0); + ArmtronixSerial->print("Setled:"); + ArmtronixSerial->write(state); + ArmtronixSerial->write(','); + state = '0' + ((wifi_state & 2) > 0); + ArmtronixSerial->write(state); + ArmtronixSerial->write(10); + Armtronix.wifi_state = WifiState(); +} + + + + + +bool Xdrv18(uint8_t function) +{ + bool result = false; + + if (ARMTRONIX_DIMMERS == my_module_type) { + switch (function) { + case FUNC_LOOP: + if (ArmtronixSerial) { ArmtronixSerialInput(); } + break; + case FUNC_MODULE_INIT: + result = ArmtronixModuleSelected(); + break; + case FUNC_INIT: + ArmtronixInit(); + break; + case FUNC_EVERY_SECOND: + if (ArmtronixSerial) { + if (Armtronix.wifi_state!=WifiState()) { ArmtronixSetWifiLed(); } + if (uptime &1) { + ArmtronixSerial->println("Status"); + } + } + break; + case FUNC_SET_CHANNELS: + result = ArmtronixSetChannels(); + break; + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_19_ps16dz_dimmer.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_19_ps16dz_dimmer.ino" +#ifdef USE_LIGHT +#ifdef USE_PS_16_DZ + + + + +#define XDRV_19 19 + +#define PS16DZ_BUFFER_SIZE 80 + +#include + +TasmotaSerial *PS16DZSerial = nullptr; + +struct PS16DZ { + char *rx_buffer = nullptr; + int byte_counter = 0; + uint8_t dimmer = 0; +} Ps16dz; + + + + + +void PS16DZSerialSend(const char *tx_buffer) +{ + + + PS16DZSerial->print(tx_buffer); + PS16DZSerial->write(0x1B); + PS16DZSerial->flush(); +} + +void PS16DZSerialSendOk(void) +{ + char tx_buffer[16]; + snprintf_P(tx_buffer, sizeof(tx_buffer), PSTR("AT+SEND=ok")); + PS16DZSerialSend(tx_buffer); +} + + + + +void PS16DZSerialSendUpdateCommand(void) +{ + uint8_t light_state_dimmer = light_state.getDimmer(); + + light_state_dimmer = (light_state_dimmer < Settings.dimmer_hw_min) ? Settings.dimmer_hw_min : light_state_dimmer; + light_state_dimmer = (light_state_dimmer > Settings.dimmer_hw_max) ? Settings.dimmer_hw_max : light_state_dimmer; + + char tx_buffer[80]; + snprintf_P(tx_buffer, sizeof(tx_buffer), PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"bright\":%d"), + LocalTime(), millis()%1000, power?"on":"off", light_state_dimmer); + + PS16DZSerialSend(tx_buffer); +} + + + + + +void PS16DZSerialInput(void) +{ + char scmnd[20]; + while (PS16DZSerial->available()) { + yield(); + uint8_t serial_in_byte = PS16DZSerial->read(); + if (serial_in_byte != 0x1B) { + if (Ps16dz.byte_counter >= PS16DZ_BUFFER_SIZE - 1) { + memset(Ps16dz.rx_buffer, 0, PS16DZ_BUFFER_SIZE); + Ps16dz.byte_counter = 0; + } + if (Ps16dz.byte_counter || (!Ps16dz.byte_counter && ('A' == serial_in_byte))) { + Ps16dz.rx_buffer[Ps16dz.byte_counter++] = serial_in_byte; + } + } else { + Ps16dz.rx_buffer[Ps16dz.byte_counter++] = 0x00; + + + + + if (!strncmp(Ps16dz.rx_buffer+3, "RESULT", 6)) { + + } + else if (!strncmp(Ps16dz.rx_buffer+3, "UPDATE", 6)) { + + char *end_str; + char *string = Ps16dz.rx_buffer+10; + char *token = strtok_r(string, ",", &end_str); + + bool is_switch_change = false; + bool is_brightness_change = false; + + while (token != nullptr) { + char* end_token; + char* token2 = strtok_r(token, ":", &end_token); + char* token3 = strtok_r(nullptr, ":", &end_token); + + if (!strncmp(token2, "\"switch\"", 8)) { + bool switch_state = !strncmp(token3, "\"on\"", 4) ? true : false; + + + + is_switch_change = (switch_state != power); + if (is_switch_change) { + ExecuteCommandPower(1, switch_state, SRC_SWITCH); + } + } + else if (!strncmp(token2, "\"bright\"", 8)) { + Ps16dz.dimmer = atoi(token3); + + + + is_brightness_change = Ps16dz.dimmer != Settings.light_dimmer; + if (power && (Ps16dz.dimmer > 0) && is_brightness_change) { + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER " %d"), Ps16dz.dimmer); + ExecuteCommand(scmnd, SRC_SWITCH); + } + } + else if (!strncmp(token2, "\"sequence\"", 10)) { + + + + } + token = strtok_r(nullptr, ",", &end_str); + } + + if (!is_brightness_change) { + + + + PS16DZSerialSendOk(); + } + } + else if (!strncmp(Ps16dz.rx_buffer+3, "SETTING", 7)) { + + + if (!Settings.flag.button_restrict) { + int state = WIFI_MANAGER; + if (!strncmp(Ps16dz.rx_buffer+10, "=exit", 5)) { state = WIFI_RETRY; } + if (state != Settings.sta_config) { + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_WIFICONFIG " %d"), state); + ExecuteCommand(scmnd, SRC_BUTTON); + } + } + } + memset(Ps16dz.rx_buffer, 0, PS16DZ_BUFFER_SIZE); + Ps16dz.byte_counter = 0; + } + } +} + +bool PS16DZSerialSendUpdateCommandIfRequired(void) +{ + if (!PS16DZSerial) { return true; } + + bool is_switch_change = (XdrvMailbox.payload != SRC_SWITCH); + bool is_brightness_change = (light_state.getDimmer() != Ps16dz.dimmer); + + if (is_switch_change || is_brightness_change) { + PS16DZSerialSendUpdateCommand(); + } + + return true; +} + +void PS16DZInit(void) +{ + Ps16dz.rx_buffer = (char*)(malloc(PS16DZ_BUFFER_SIZE)); + if (Ps16dz.rx_buffer != nullptr) { + PS16DZSerial = new TasmotaSerial(pin[GPIO_RXD], pin[GPIO_TXD], 2); + if (PS16DZSerial->begin(19200)) { + if (PS16DZSerial->hardwareSerial()) { ClaimSerial(); } + } + } +} + +bool PS16DZModuleSelected(void) +{ + devices_present++; + light_type = LT_SERIAL1; + + return true; +} + + + + + +bool Xdrv19(uint8_t function) +{ + bool result = false; + + if (PS_16_DZ == my_module_type) { + switch (function) { + case FUNC_LOOP: + if (PS16DZSerial) { PS16DZSerialInput(); } + break; + case FUNC_SET_DEVICE_POWER: + case FUNC_SET_CHANNELS: + result = PS16DZSerialSendUpdateCommandIfRequired(); + break; + case FUNC_INIT: + PS16DZInit(); + break; + case FUNC_MODULE_INIT: + result = PS16DZModuleSelected(); + break; + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_20_hue.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_20_hue.ino" +#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT) +# 31 "S:/Development/Tasmota/tasmota/xdrv_20_hue.ino" +#define XDRV_20 20 + +const char HUE_RESPONSE[] PROGMEM = + "HTTP/1.1 200 OK\r\n" + "HOST: 239.255.255.250:1900\r\n" + "CACHE-CONTROL: max-age=100\r\n" + "EXT:\r\n" + "LOCATION: http://%s:80/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" + "hue-bridgeid: %s\r\n"; +const char HUE_ST1[] PROGMEM = + "ST: upnp:rootdevice\r\n" + "USN: uuid:%s::upnp:rootdevice\r\n" + "\r\n"; +const char HUE_ST2[] PROGMEM = + "ST: uuid:%s\r\n" + "USN: uuid:%s\r\n" + "\r\n"; +const char HUE_ST3[] PROGMEM = + "ST: urn:schemas-upnp-org:device:basic:1\r\n" + "USN: uuid:%s\r\n" + "\r\n"; + +String HueBridgeId(void) +{ + String temp = WiFi.macAddress(); + temp.replace(":", ""); + String bridgeid = temp.substring(0, 6) + "FFFE" + temp.substring(6); + return bridgeid; +} + +String HueSerialnumber(void) +{ + String serial = WiFi.macAddress(); + serial.replace(":", ""); + serial.toLowerCase(); + return serial; +} + +String HueUuid(void) +{ + String uuid = F("f6543a06-da50-11ba-8d8f-"); + uuid += HueSerialnumber(); + return uuid; +} + +void HueRespondToMSearch(void) +{ + char message[TOPSZ]; + + TickerMSearch.detach(); + if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) { + char response[320]; + snprintf_P(response, sizeof(response), HUE_RESPONSE, WiFi.localIP().toString().c_str(), HueBridgeId().c_str()); + int len = strlen(response); + + snprintf_P(response + len, sizeof(response) - len, HUE_ST1, HueUuid().c_str()); + PortUdp.write(response); + PortUdp.endPacket(); + + snprintf_P(response + len, sizeof(response) - len, HUE_ST2, HueUuid().c_str(), HueUuid().c_str()); + PortUdp.write(response); + PortUdp.endPacket(); + + snprintf_P(response + len, sizeof(response) - len, HUE_ST3, HueUuid().c_str()); + PortUdp.write(response); + PortUdp.endPacket(); + + snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT)); + } else { + snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE)); + } + + PrepLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"), + message, udp_remote_ip.toString().c_str(), udp_remote_port); + + udp_response_mutex = false; +} + + + + + +const char HUE_DESCRIPTION_XML[] PROGMEM = + "" + "" + "" + "1" + "0" + "" + + "http://{x1:80/" + "" + "urn:schemas-upnp-org:device:Basic:1" + "Amazon-Echo-HA-Bridge ({x1)" + + "Royal Philips Electronics" + "http://www.philips.com" + "Philips hue Personal Wireless Lighting" + "Philips hue bridge 2012" + "929000226503" + "{x3" + "uuid:{x2" + "" + "\r\n" + "\r\n"; +const char HUE_LIGHTS_STATUS_JSON1[] PROGMEM = + "{\"on\":{state}," + "{light_status}" + "\"alert\":\"none\"," + "\"effect\":\"none\"," + "\"reachable\":true}"; +const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM = + ",\"type\":\"Extended color light\"," + "\"name\":\"{j1\"," + "\"modelid\":\"LCT007\"," + "\"uniqueid\":\"{j2\"," + "\"swversion\":\"5.50.1.19085\"}"; +const char HUE_GROUP0_STATUS_JSON[] PROGMEM = + "{\"name\":\"Group 0\"," + "\"lights\":[{l1]," + "\"type\":\"LightGroup\"," + "\"action\":"; + +const char HueConfigResponse_JSON[] PROGMEM = + "{\"name\":\"Philips hue\"," + "\"mac\":\"{ma\"," + "\"dhcp\":true," + "\"ipaddress\":\"{ip\"," + "\"netmask\":\"{ms\"," + "\"gateway\":\"{gw\"," + "\"proxyaddress\":\"none\"," + "\"proxyport\":0," + "\"bridgeid\":\"{br\"," + "\"UTC\":\"{dt\"," + "\"whitelist\":{\"{id\":{" + "\"last use date\":\"{dt\"," + "\"create date\":\"{dt\"," + "\"name\":\"Remote\"}}," + "\"swversion\":\"01041302\"," + "\"apiversion\":\"1.17.0\"," + "\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false}," + "\"linkbutton\":false," + "\"portalservices\":false" + "}"; +const char HUE_LIGHT_RESPONSE_JSON[] PROGMEM = + "{\"success\":{\"/lights/{id/state/{cm\":{re}}"; +const char HUE_ERROR_JSON[] PROGMEM = + "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"; + + + +String GetHueDeviceId(uint8_t id) +{ + String deviceid = WiFi.macAddress() + F(":00:11-") + String(id); + deviceid.toLowerCase(); + return deviceid; +} + +String GetHueUserId(void) +{ + char userid[7]; + + snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP.getChipId()); + return String(userid); +} + +void HandleUpnpSetupHue(void) +{ + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_HUE_BRIDGE_SETUP)); + String description_xml = FPSTR(HUE_DESCRIPTION_XML); + description_xml.replace("{x1", WiFi.localIP().toString()); + description_xml.replace("{x2", HueUuid()); + description_xml.replace("{x3", HueSerialnumber()); + WSSend(200, CT_XML, description_xml); +} + +void HueNotImplemented(String *path) +{ + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str()); + + WSSend(200, CT_JSON, "{}"); +} + +void HueConfigResponse(String *response) +{ + *response += FPSTR(HueConfigResponse_JSON); + response->replace("{ma", WiFi.macAddress()); + response->replace("{ip", WiFi.localIP().toString()); + response->replace("{ms", WiFi.subnetMask().toString()); + response->replace("{gw", WiFi.gatewayIP().toString()); + response->replace("{br", HueBridgeId()); + response->replace("{dt", GetDateAndTime(DT_UTC)); + response->replace("{id", GetHueUserId()); +} + +void HueConfig(String *path) +{ + String response = ""; + HueConfigResponse(&response); + WSSend(200, CT_JSON, response); +} + + + +bool g_gotct = false; + + + + +uint16_t prev_hue = 0; +uint8_t prev_sat = 0; +uint8_t prev_bri = 254; +uint16_t prev_ct = 254; +char prev_x_str[24] = "\0"; +char prev_y_str[24] = "\0"; + +uint8_t getLocalLightSubtype(uint8_t device) { + if (light_type) { + if (device >= Light.device) { + if (Settings.flag3.pwm_multi_channels) { + return LST_SINGLE; + } else { + return Light.subtype; + } + } else { + return LST_NONE; + } + } else { + return LST_NONE; + } +} + +void HueLightStatus1(uint8_t device, String *response) +{ + uint16_t ct = 0; + uint8_t color_mode; + String light_status = ""; + uint16_t hue = 0; + uint8_t sat = 0; + uint8_t bri = 254; + uint32_t echo_gen = findEchoGeneration(); + + + uint8_t local_light_subtype = getLocalLightSubtype(device); + + bri = LightGetBri(device); + if (bri > 254) bri = 254; + if (bri < 1) bri = 1; + +#ifdef USE_SHUTTER + if (ShutterState(device)) { + bri = (float)((Settings.shutter_options[device-1] & 1) ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100; + } +#endif + + if (light_type) { + light_state.getHSB(&hue, &sat, nullptr); + + if ((bri > prev_bri ? bri - prev_bri : prev_bri - bri) < 1) + bri = prev_bri; + + if (sat > 254) sat = 254; + if ((sat > prev_sat ? sat - prev_sat : prev_sat - sat) < 1) { + sat = prev_sat; + } else { + prev_x_str[0] = prev_y_str[0] = 0; + } + + hue = changeUIntScale(hue, 0, 359, 0, 65535); + if ((hue > prev_hue ? hue - prev_hue : prev_hue - hue) < 400) { + hue = prev_hue; + } else { + prev_x_str[0] = prev_y_str[0] = 0; + } + + color_mode = light_state.getColorMode(); + ct = light_state.getCT(); + if (LCM_RGB == color_mode) { g_gotct = false; } + if (LCM_CT == color_mode) { g_gotct = true; } + + + + if ((ct > prev_ct ? ct - prev_ct : prev_ct - ct) < 1) + ct = prev_ct; + + + + } + + *response += FPSTR(HUE_LIGHTS_STATUS_JSON1); + response->replace("{state}", (power & (1 << (device-1))) ? "true" : "false"); + + if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { + light_status += "\"bri\":"; + light_status += String(bri); + light_status += ","; + } + if (LST_COLDWARM <= local_light_subtype) { + light_status += F("\"colormode\":\""); + light_status += (g_gotct ? "ct" : "hs"); + light_status += "\","; + } + if (LST_RGB <= local_light_subtype) { + if (prev_x_str[0] && prev_y_str[0]) { + light_status += "\"xy\":["; + light_status += prev_x_str; + light_status += ","; + light_status += prev_y_str; + light_status += "],"; + } else { + float x, y; + light_state.getXY(&x, &y); + light_status += "\"xy\":["; + light_status += String(x, 5); + light_status += ","; + light_status += String(y, 5); + light_status += "],"; + } + light_status += "\"hue\":"; + light_status += String(hue); + light_status += ","; + + light_status += "\"sat\":"; + light_status += String(sat); + light_status += ","; + } + if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { + light_status += "\"ct\":"; + light_status += String(ct > 0 ? ct : 284); + light_status += ","; + } + response->replace("{light_status}", light_status); +} + + + +bool HueActive(uint8_t device) { + if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; } + return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1); +} + +void HueLightStatus2(uint8_t device, String *response) +{ + *response += FPSTR(HUE_LIGHTS_STATUS_JSON2); + if (device <= MAX_FRIENDLYNAMES) { + response->replace("{j1", SettingsText(SET_FRIENDLYNAME1 +device -1)); + } else { + char fname[33]; + strcpy(fname, SettingsText(SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1)); + uint32_t fname_len = strlen(fname); + if (fname_len > 30) { fname_len = 30; } + fname[fname_len++] = '-'; + if (device - MAX_FRIENDLYNAMES < 10) { + fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES; + } else { + fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10; + } + fname[fname_len] = 0x00; + + response->replace("{j1", fname); + } + response->replace("{j2", GetHueDeviceId(device)); +} + + + + +uint32_t EncodeLightId(uint8_t relay_id) +{ + uint8_t mac[6]; + WiFi.macAddress(mac); + uint32_t id = 0; + + if (relay_id >= 32) { + relay_id = 0; + } + if (relay_id > 15) { + id = (1 << 28); + } + + id |= (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4) | (relay_id & 0xF); + return id; +} + + + +uint32_t DecodeLightId(uint32_t hue_id) { + uint8_t relay_id = hue_id & 0xF; + if (hue_id & (1 << 28)) { + relay_id += 16; + } + if (0 == relay_id) { + relay_id = 32; + } + return relay_id; +} + +static const char * FIRST_GEN_UA[] = { + "AEOBC", +}; + + +uint32_t findEchoGeneration(void) { + + String user_agent = WebServer->header("User-Agent"); + uint32_t gen = 2; + + for (uint32_t i = 0; i < sizeof(FIRST_GEN_UA)/sizeof(char*); i++) { + if (user_agent.indexOf(FIRST_GEN_UA[i]) >= 0) { + gen = 1; + break; + } + } + if (0 == user_agent.length()) { + gen = 1; + } + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, D_LOG_HTTP D_HUE " User-Agent: %s, gen=%d", user_agent.c_str(), gen); + + return gen; +} + +void HueGlobalConfig(String *path) { + String response; + uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present; + + path->remove(0,1); + response = F("{\"lights\":{"); + bool appending = false; + for (uint32_t i = 1; i <= maxhue; i++) { + if (HueActive(i)) { + if (appending) { response += ","; } + response += "\""; + response += EncodeLightId(i); + response += F("\":{\"state\":"); + HueLightStatus1(i, &response); + HueLightStatus2(i, &response); + appending = true; + } + } + response += F("},\"groups\":{},\"schedules\":{},\"config\":"); + HueConfigResponse(&response); + response += "}"; + WSSend(200, CT_JSON, response); +} + +void HueAuthentication(String *path) +{ + char response[38]; + + snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str()); + WSSend(200, CT_JSON, response); +} + +void HueLights(String *path) +{ + + + + String response; + int code = 200; + uint16_t tmp = 0; + uint16_t hue = 0; + uint8_t sat = 0; + uint8_t bri = 254; + uint16_t ct = 0; + bool resp = false; + bool on = false; + bool change = false; + uint8_t device = 1; + uint8_t local_light_subtype = Light.subtype; + uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present; + + path->remove(0,path->indexOf("/lights")); + if (path->endsWith("/lights")) { + response = "{"; + bool appending = false; + for (uint32_t i = 1; i <= maxhue; i++) { + if (HueActive(i)) { + if (appending) { response += ","; } + response += "\""; + response += EncodeLightId(i); + response += F("\":{\"state\":"); + HueLightStatus1(i, &response); + HueLightStatus2(i, &response); + appending = true; + } + } +#ifdef USE_SCRIPT_HUE + Script_Check_Hue(&response); +#endif + response += "}"; + } + else if (path->endsWith("/state")) { + path->remove(0,8); + path->remove(path->indexOf("/state")); + device = DecodeLightId(atoi(path->c_str())); + +#ifdef USE_SCRIPT_HUE + if (device>devices_present) { + return Script_Handle_Hue(path); + } +#endif + + if ((device < 1) || (device > maxhue)) { + device = 1; + } + local_light_subtype = getLocalLightSubtype(device); + + if (WebServer->args()) { + response = "["; + + StaticJsonBuffer<400> jsonBuffer; + JsonObject &hue_json = jsonBuffer.parseObject(WebServer->arg((WebServer->args())-1)); + if (hue_json.containsKey("on")) { + + response += FPSTR(HUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(EncodeLightId(device))); + response.replace("{cm", "on"); + +#ifdef USE_SHUTTER + if (ShutterState(device)) { + if (!change) { + on = hue_json["on"]; + bri = on ? 1.0f : 0.0f; + change = true; + } + response.replace("{re", on ? "true" : "false"); + } else { +#endif + on = hue_json["on"]; + switch(on) + { + case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE); + response.replace("{re", "false"); + break; + case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE); + response.replace("{re", "true"); + break; + default : response.replace("{re", (power & (1 << (device-1))) ? "true" : "false"); + break; + } + resp = true; +#ifdef USE_SHUTTER + } +#endif + } + + if (light_type && (local_light_subtype >= LST_SINGLE)) { + if (!Settings.flag3.pwm_multi_channels) { + light_state.getHSB(&hue, &sat, nullptr); + bri = light_state.getBri(); + ct = light_state.getCT(); + uint8_t color_mode = light_state.getColorMode(); + if (LCM_RGB == color_mode) { g_gotct = false; } + if (LCM_CT == color_mode) { g_gotct = true; } + + } else { + bri = LightGetBri(device); + } + } + prev_x_str[0] = prev_y_str[0] = 0; + + if (hue_json.containsKey("bri")) { + tmp = hue_json["bri"]; + prev_bri = bri = tmp; + + if (254 <= bri) { bri = 255; } + if (resp) { response += ","; } + response += FPSTR(HUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(device)); + response.replace("{cm", "bri"); + response.replace("{re", String(tmp)); + if (LST_SINGLE <= Light.subtype) { + change = true; + } + resp = true; + } + + + if (hue_json.containsKey("xy")) { + float x, y; + x = hue_json["xy"][0]; + y = hue_json["xy"][1]; + const String &x_str = hue_json["xy"][0]; + const String &y_str = hue_json["xy"][1]; + x_str.toCharArray(prev_x_str, sizeof(prev_x_str)); + y_str.toCharArray(prev_y_str, sizeof(prev_y_str)); + + uint8_t rr,gg,bb; + LightStateClass::XyToRgb(x, y, &rr, &gg, &bb); + LightStateClass::RgbToHsb(rr, gg, bb, &hue, &sat, nullptr); + prev_hue = changeUIntScale(hue, 0, 359, 0, 65535); + prev_sat = (sat > 254 ? 254 : sat); + + if (resp) { response += ","; } + response += FPSTR(HUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(device)); + response.replace("{cm", "xy"); + response.replace("{re", "[" + x_str + "," + y_str + "]"); + g_gotct = false; + resp = true; + change = true; + } + if (hue_json.containsKey("hue")) { + tmp = hue_json["hue"]; + prev_hue = tmp; + + hue = changeUIntScale(tmp, 0, 65535, 0, 359); + if (resp) { response += ","; } + response += FPSTR(HUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(device)); + response.replace("{cm", "hue"); + response.replace("{re", String(tmp)); + if (LST_RGB <= Light.subtype) { + g_gotct = false; + change = true; + } + resp = true; + } + if (hue_json.containsKey("sat")) { + tmp = hue_json["sat"]; + prev_sat = sat = tmp; + + if (254 <= sat) { sat = 255; } + if (resp) { response += ","; } + response += FPSTR(HUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(device)); + response.replace("{cm", "sat"); + response.replace("{re", String(tmp)); + if (LST_RGB <= Light.subtype) { + g_gotct = false; + change = true; + } + resp = true; + } + if (hue_json.containsKey("ct")) { + ct = hue_json["ct"]; + prev_ct = ct; + if (resp) { response += ","; } + response += FPSTR(HUE_LIGHT_RESPONSE_JSON); + response.replace("{id", String(device)); + response.replace("{cm", "ct"); + response.replace("{re", String(ct)); + if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) { + g_gotct = true; + change = true; + } + resp = true; + } + if (change) { +#ifdef USE_SHUTTER + if (ShutterState(device)) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_options[device-1] & 1); + ShutterSetPosition(device, bri * 100.0f ); + } else +#endif + if (light_type && (local_light_subtype > LST_NONE)) { + if (!Settings.flag3.pwm_multi_channels) { + if (g_gotct) { + light_controller.changeCTB(ct, bri); + } else { + light_controller.changeHSB(hue, sat, bri); + } + LightPreparePower(); + } else { + LightSetBri(device, bri); + } + if (LST_COLDWARM <= local_light_subtype) { + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR)); + } else { + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER)); + } + XdrvRulesProcess(); + } + change = false; + } + response += "]"; + if (2 == response.length()) { + response = FPSTR(HUE_ERROR_JSON); + } + } + else { + response = FPSTR(HUE_ERROR_JSON); + } + } + else if(path->indexOf("/lights/") >= 0) { + AddLog_P2(LOG_LEVEL_DEBUG_MORE, "/lights path=%s", path->c_str()); + path->remove(0,8); + device = DecodeLightId(atoi(path->c_str())); + +#ifdef USE_SCRIPT_HUE + if (device>devices_present) { + Script_HueStatus(&response,device-devices_present-1); + goto exit; +} +#endif + + if ((device < 1) || (device > maxhue)) { + device = 1; + } + response += F("{\"state\":"); + HueLightStatus1(device, &response); + HueLightStatus2(device, &response); + } + else { + response = "{}"; + code = 406; + } + exit: + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str()); + WSSend(code, CT_JSON, response); +} + +void HueGroups(String *path) +{ + + + + String response = "{}"; + uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present; + + if (path->endsWith("/0")) { + response = FPSTR(HUE_GROUP0_STATUS_JSON); + String lights = F("\"1\""); + for (uint32_t i = 2; i <= maxhue; i++) { + lights += ",\""; + lights += EncodeLightId(i); + lights += "\""; + } + response.replace("{l1", lights); + HueLightStatus1(1, &response); + response += F("}"); + } + + WSSend(200, CT_JSON, response); +} + +void HandleHueApi(String *path) +{ +# 784 "S:/Development/Tasmota/tasmota/xdrv_20_hue.ino" + uint8_t args = 0; + + path->remove(0, 4); + uint16_t apilen = path->length(); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API " (%s)"), path->c_str()); + for (args = 0; args < WebServer->args(); args++) { + String json = WebServer->arg(args); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str()); + } + + if (path->endsWith("/invalid/")) {} + else if (!apilen) HueAuthentication(path); + else if (path->endsWith("/")) HueAuthentication(path); + else if (path->endsWith("/config")) HueConfig(path); + else if (path->indexOf("/lights") >= 0) HueLights(path); + else if (path->indexOf("/groups") >= 0) HueGroups(path); + else if (path->endsWith("/schedules")) HueNotImplemented(path); + else if (path->endsWith("/sensors")) HueNotImplemented(path); + else if (path->endsWith("/scenes")) HueNotImplemented(path); + else if (path->endsWith("/rules")) HueNotImplemented(path); + else if (path->endsWith("/resourcelinks")) HueNotImplemented(path); + else HueGlobalConfig(path); +} + + + + + +bool Xdrv20(uint8_t function) +{ + bool result = false; + +#ifdef USE_SCRIPT_HUE + if ((EMUL_HUE == Settings.flag2.emulation)) { +#else + if (devices_present && (EMUL_HUE == Settings.flag2.emulation)) { +#endif + switch (function) { + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/description.xml", HandleUpnpSetupHue); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_21_wemo.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_21_wemo.ino" +#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined (USE_EMULATION_WEMO) + + + + +#define XDRV_21 21 + +const char WEMO_MSEARCH[] PROGMEM = + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=86400\r\n" + "DATE: Fri, 15 Apr 2016 04:56:29 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://%s:80/setup.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n" + "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" + "ST: %s\r\n" + "USN: uuid:%s::%s\r\n" + "X-User-Agent: redsonic\r\n" + "\r\n"; + +String WemoSerialnumber(void) +{ + char serial[16]; + + snprintf_P(serial, sizeof(serial), PSTR("201612K%08X"), ESP.getChipId()); + return String(serial); +} + +String WemoUuid(void) +{ + char uuid[27]; + + snprintf_P(uuid, sizeof(uuid), PSTR("Socket-1_0-%s"), WemoSerialnumber().c_str()); + return String(uuid); +} + +void WemoRespondToMSearch(int echo_type) +{ + char message[TOPSZ]; + + TickerMSearch.detach(); + if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) { + char type[24]; + if (1 == echo_type) { + strcpy_P(type, URN_BELKIN_DEVICE_CAP); + } else { + strcpy_P(type, UPNP_ROOTDEVICE); + } + char response[400]; + snprintf_P(response, sizeof(response), WEMO_MSEARCH, WiFi.localIP().toString().c_str(), type, WemoUuid().c_str(), type); + PortUdp.write(response); + PortUdp.endPacket(); + snprintf_P(message, sizeof(message), PSTR(D_RESPONSE_SENT)); + } else { + snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE)); + } + + PrepLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_WEMO " " D_JSON_TYPE " %d, %s " D_TO " %s:%d"), + echo_type, message, udp_remote_ip.toString().c_str(), udp_remote_port); + + udp_response_mutex = false; +} + + + + + +const char WEMO_EVENTSERVICE_XML[] PROGMEM = + "" + "" + "" + "SetBinaryState" + "" + "" + "" + "BinaryState" + "BinaryState" + "in" + "" + "" + "" + "" + "GetBinaryState" + "" + "" + "" + "BinaryState" + "BinaryState" + "out" + "" + "" + "" + "" + "" + "" + "BinaryState" + "bool" + "0" + "" + "" + "level" + "string" + "0" + "" + "" + "\r\n\r\n"; + +const char WEMO_METASERVICE_XML[] PROGMEM = + "" + "" + "1" + "0" + "" + "" + "" + "GetMetaInfo" + "" + "" + "GetMetaInfo" + "MetaInfo" + "in" + "" + "" + "" + "" + "" + "MetaInfo" + "string" + "0" + "" + "" + "\r\n\r\n"; + +const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM = + "" + "" + "" + "%d" + "" + "" + "\r\n"; + +const char WEMO_SETUP_XML[] PROGMEM = + "" + "" + "" + "urn:Belkin:device:controllee:1" + "{x1" + "Belkin International Inc." + "Socket" + "3.1415" + "uuid:{x2" + "{x3" + "0" + "" + "" + "urn:Belkin:service:basicevent:1" + "urn:Belkin:serviceId:basicevent1" + "/upnp/control/basicevent1" + "/upnp/event/basicevent1" + "/eventservice.xml" + "" + "" + "urn:Belkin:service:metainfo:1" + "urn:Belkin:serviceId:metainfo1" + "/upnp/control/metainfo1" + "/upnp/event/metainfo1" + "/metainfoservice.xml" + "" + "" + "" + "\r\n"; + + + +void HandleUpnpEvent(void) +{ + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_BASIC_EVENT)); + + char event[500]; + strlcpy(event, WebServer->arg(0).c_str(), sizeof(event)); + + + + + char state = 'G'; + if (strstr_P(event, PSTR("SetBinaryState")) != nullptr) { + state = 'S'; + uint8_t power = POWER_TOGGLE; + if (strstr_P(event, PSTR("State>10on("/upnp/control/basicevent1", HTTP_POST, HandleUpnpEvent); + WebServer->on("/eventservice.xml", HandleUpnpService); + WebServer->on("/metainfoservice.xml", HandleUpnpMetaService); + WebServer->on("/setup.xml", HandleUpnpSetupWemo); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_22_sonoff_ifan.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_22_sonoff_ifan.ino" +#ifdef USE_SONOFF_IFAN + + + + +#define XDRV_22 22 + +const uint8_t MAX_FAN_SPEED = 4; + +const uint8_t kIFan02Speed[MAX_FAN_SPEED] = { 0x00, 0x01, 0x03, 0x05 }; +const uint8_t kIFan03Speed[MAX_FAN_SPEED +2] = { 0x00, 0x01, 0x03, 0x04, 0x05, 0x06 }; +const uint8_t kIFan03Sequence[MAX_FAN_SPEED][MAX_FAN_SPEED] = {{0, 2, 2, 2}, {0, 1, 2, 4}, {1, 1, 2, 5}, {4, 4, 5, 3}}; + +const char kSonoffIfanCommands[] PROGMEM = "|" + D_CMND_FANSPEED; + +void (* const SonoffIfanCommand[])(void) PROGMEM = { + &CmndFanspeed }; + +uint8_t ifan_fanspeed_timer = 0; +uint8_t ifan_fanspeed_goal = 0; +bool ifan_receive_flag = false; +bool ifan_restart_flag = true; + + + +bool IsModuleIfan(void) +{ + return ((SONOFF_IFAN02 == my_module_type) || (SONOFF_IFAN03 == my_module_type)); +} + +uint8_t MaxFanspeed(void) +{ + return MAX_FAN_SPEED; +} + +uint8_t GetFanspeed(void) +{ + if (ifan_fanspeed_timer) { + return ifan_fanspeed_goal; + } else { + + + + + + + uint8_t fanspeed = (uint8_t)(power &0xF) >> 1; + if (fanspeed) { fanspeed = (fanspeed >> 1) +1; } + return fanspeed; + } +} + + + +void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence) +{ + ifan_fanspeed_timer = 0; + ifan_fanspeed_goal = fanspeed; + + uint8_t fanspeed_now = GetFanspeed(); + + if (fanspeed == fanspeed_now) { return; } + + uint8_t fans = kIFan02Speed[fanspeed]; + if (SONOFF_IFAN03 == my_module_type) { + if (sequence) { + fanspeed = kIFan03Sequence[fanspeed_now][ifan_fanspeed_goal]; + if (fanspeed != ifan_fanspeed_goal) { + if (0 == fanspeed_now) { + ifan_fanspeed_timer = 20; + } else { + ifan_fanspeed_timer = 2; + } + } + } + fans = kIFan03Speed[fanspeed]; + } + for (uint32_t i = 2; i < 5; i++) { + uint8_t state = (fans &1) + POWER_OFF_NO_STATE; + ExecuteCommandPower(i, state, SRC_IGNORE); + fans >>= 1; + } + +#ifdef USE_DOMOTICZ + if (sequence) { DomoticzUpdateFanState(); } +#endif +} + + + +void SonoffIfanReceived(void) +{ + char svalue[32]; + + uint8_t mode = serial_in_buffer[3]; + uint8_t action = serial_in_buffer[6]; + + if (4 == mode) { + if (action < 4) { + + + + + if (action != GetFanspeed()) { + snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_FANSPEED " %d"), action); + ExecuteCommand(svalue, SRC_REMOTE); +#ifdef USE_BUZZER + BuzzerEnabledBeep((action) ? action : 1, (action) ? 1 : 4); +#endif + } + } else { + + ExecuteCommandPower(1, POWER_TOGGLE, SRC_REMOTE); + } + } + if (6 == mode) { + + Settings.flag3.buzzer_enable = !Settings.flag3.buzzer_enable; + } + if (7 == mode) { + +#ifdef USE_BUZZER + BuzzerEnabledBeep(4, 1); +#endif + } + + + + serial_in_buffer[5] = 0; + serial_in_buffer[6] = 0; + for (uint32_t i = 0; i < 7; i++) { + if ((i > 1) && (i < 6)) { serial_in_buffer[6] += serial_in_buffer[i]; } + Serial.write(serial_in_buffer[i]); + } +} + +bool SonoffIfanSerialInput(void) +{ + if (SONOFF_IFAN03 == my_module_type) { + if (0xAA == serial_in_byte) { + serial_in_byte_counter = 0; + ifan_receive_flag = true; + } + if (ifan_receive_flag) { + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + if (serial_in_byte_counter == 8) { +# 176 "S:/Development/Tasmota/tasmota/xdrv_22_sonoff_ifan.ino" + AddLogSerial(LOG_LEVEL_DEBUG); + uint8_t crc = 0; + for (uint32_t i = 2; i < 7; i++) { + crc += serial_in_buffer[i]; + } + if (crc == serial_in_buffer[7]) { + SonoffIfanReceived(); + ifan_receive_flag = false; + return true; + } + } + serial_in_byte = 0; + } + return false; + } +} + + + + + +void CmndFanspeed(void) +{ + if (XdrvMailbox.data_len > 0) { + if ('-' == XdrvMailbox.data[0]) { + XdrvMailbox.payload = (int16_t)GetFanspeed() -1; + if (XdrvMailbox.payload < 0) { XdrvMailbox.payload = MAX_FAN_SPEED -1; } + } + else if ('+' == XdrvMailbox.data[0]) { + XdrvMailbox.payload = GetFanspeed() +1; + if (XdrvMailbox.payload > MAX_FAN_SPEED -1) { XdrvMailbox.payload = 0; } + } + } + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_FAN_SPEED)) { + SonoffIFanSetFanspeed(XdrvMailbox.payload, true); + } + ResponseCmndNumber(GetFanspeed()); +} + + + +bool SonoffIfanInit(void) +{ + if (SONOFF_IFAN03 == my_module_type) { + SetSerial(9600, TS_SERIAL_8N1); + } + return false; +} + +void SonoffIfanUpdate(void) +{ + if (SONOFF_IFAN03 == my_module_type) { + if (ifan_fanspeed_timer) { + ifan_fanspeed_timer--; + if (!ifan_fanspeed_timer) { + SonoffIFanSetFanspeed(ifan_fanspeed_goal, false); + } + } + } + + if (ifan_restart_flag && (4 == uptime) && (SONOFF_IFAN02 == my_module_type)) { + ifan_restart_flag = false; + SetDevicePower(1, SRC_RETRY); + SetDevicePower(power, SRC_RETRY); + } +} + + + + + +bool Xdrv22(uint8_t function) +{ + bool result = false; + + if (IsModuleIfan()) { + switch (function) { + case FUNC_EVERY_250_MSECOND: + SonoffIfanUpdate(); + break; + case FUNC_SERIAL: + result = SonoffIfanSerialInput(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kSonoffIfanCommands, SonoffIfanCommand); + break; + case FUNC_MODULE_INIT: + result = SonoffIfanInit(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino" +#ifdef USE_ZIGBEE + +#define OCCUPANCY "Occupancy" + +typedef uint64_t Z_IEEEAddress; +typedef uint16_t Z_ShortAddress; + +enum ZnpCommandType { + Z_POLL = 0x00, + Z_SREQ = 0x20, + Z_AREQ = 0x40, + Z_SRSP = 0x60 +}; + +enum ZnpSubsystem { + Z_RPC_Error = 0x00, + Z_SYS = 0x01, + Z_MAC = 0x02, + Z_NWK = 0x03, + Z_AF = 0x04, + Z_ZDO = 0x05, + Z_SAPI = 0x06, + Z_UTIL = 0x07, + Z_DEBUG = 0x08, + Z_APP = 0x09 +}; + + +enum SysCommand { + SYS_RESET = 0x00, + SYS_PING = 0x01, + SYS_VERSION = 0x02, + SYS_SET_EXTADDR = 0x03, + SYS_GET_EXTADDR = 0x04, + SYS_RAM_READ = 0x05, + SYS_RAM_WRITE = 0x06, + SYS_OSAL_NV_ITEM_INIT = 0x07, + SYS_OSAL_NV_READ = 0x08, + SYS_OSAL_NV_WRITE = 0x09, + SYS_OSAL_START_TIMER = 0x0A, + SYS_OSAL_STOP_TIMER = 0x0B, + SYS_RANDOM = 0x0C, + SYS_ADC_READ = 0x0D, + SYS_GPIO = 0x0E, + SYS_STACK_TUNE = 0x0F, + SYS_SET_TIME = 0x10, + SYS_GET_TIME = 0x11, + SYS_OSAL_NV_DELETE = 0x12, + SYS_OSAL_NV_LENGTH = 0x13, + SYS_TEST_RF = 0x40, + SYS_TEST_LOOPBACK = 0x41, + SYS_RESET_IND = 0x80, + SYS_OSAL_TIMER_EXPIRED = 0x81, +}; + +enum SapiCommand { + SAPI_START_REQUEST = 0x00, + SAPI_BIND_DEVICE = 0x01, + SAPI_ALLOW_BIND = 0x02, + SAPI_SEND_DATA_REQUEST = 0x03, + SAPI_READ_CONFIGURATION = 0x04, + SAPI_WRITE_CONFIGURATION = 0x05, + SAPI_GET_DEVICE_INFO = 0x06, + SAPI_FIND_DEVICE_REQUEST = 0x07, + SAPI_PERMIT_JOINING_REQUEST = 0x08, + SAPI_SYSTEM_RESET = 0x09, + SAPI_START_CONFIRM = 0x80, + SAPI_BIND_CONFIRM = 0x81, + SAPI_ALLOW_BIND_CONFIRM = 0x82, + SAPI_SEND_DATA_CONFIRM = 0x83, + SAPI_FIND_DEVICE_CONFIRM = 0x85, + SAPI_RECEIVE_DATA_INDICATION = 0x87, +}; +enum Z_configuration { + CONF_EXTADDR = 0x01, + CONF_BOOTCOUNTER = 0x02, + CONF_STARTUP_OPTION = 0x03, + CONF_START_DELAY = 0x04, + CONF_NIB = 0x21, + CONF_DEVICE_LIST = 0x22, + CONF_ADDRMGR = 0x23, + CONF_POLL_RATE = 0x24, + CONF_QUEUED_POLL_RATE = 0x25, + CONF_RESPONSE_POLL_RATE = 0x26, + CONF_REJOIN_POLL_RATE = 0x27, + CONF_DATA_RETRIES = 0x28, + CONF_POLL_FAILURE_RETRIES = 0x29, + CONF_STACK_PROFILE = 0x2A, + CONF_INDIRECT_MSG_TIMEOUT = 0x2B, + CONF_ROUTE_EXPIRY_TIME = 0x2C, + CONF_EXTENDED_PAN_ID = 0x2D, + CONF_BCAST_RETRIES = 0x2E, + CONF_PASSIVE_ACK_TIMEOUT = 0x2F, + CONF_BCAST_DELIVERY_TIME = 0x30, + CONF_NWK_MODE = 0x31, + CONF_CONCENTRATOR_ENABLE = 0x32, + CONF_CONCENTRATOR_DISCOVERY = 0x33, + CONF_CONCENTRATOR_RADIUS = 0x34, + CONF_CONCENTRATOR_RC = 0x36, + CONF_NWK_MGR_MODE = 0x37, + CONF_SRC_RTG_EXPIRY_TIME = 0x38, + CONF_ROUTE_DISCOVERY_TIME = 0x39, + CONF_NWK_ACTIVE_KEY_INFO = 0x3A, + CONF_NWK_ALTERN_KEY_INFO = 0x3B, + CONF_ROUTER_OFF_ASSOC_CLEANUP = 0x3C, + CONF_NWK_LEAVE_REQ_ALLOWED = 0x3D, + CONF_NWK_CHILD_AGE_ENABLE = 0x3E, + CONF_DEVICE_LIST_KA_TIMEOUT = 0x3F, + CONF_BINDING_TABLE = 0x41, + CONF_GROUP_TABLE = 0x42, + CONF_APS_FRAME_RETRIES = 0x43, + CONF_APS_ACK_WAIT_DURATION = 0x44, + CONF_APS_ACK_WAIT_MULTIPLIER = 0x45, + CONF_BINDING_TIME = 0x46, + CONF_APS_USE_EXT_PANID = 0x47, + CONF_APS_USE_INSECURE_JOIN = 0x48, + CONF_COMMISSIONED_NWK_ADDR = 0x49, + CONF_APS_NONMEMBER_RADIUS = 0x4B, + CONF_APS_LINK_KEY_TABLE = 0x4C, + CONF_APS_DUPREJ_TIMEOUT_INC = 0x4D, + CONF_APS_DUPREJ_TIMEOUT_COUNT = 0x4E, + CONF_APS_DUPREJ_TABLE_SIZE = 0x4F, + CONF_DIAGNOSTIC_STATS = 0x50, + CONF_SECURITY_LEVEL = 0x61, + CONF_PRECFGKEY = 0x62, + CONF_PRECFGKEYS_ENABLE = 0x63, + CONF_SECURITY_MODE = 0x64, + CONF_SECURE_PERMIT_JOIN = 0x65, + CONF_APS_LINK_KEY_TYPE = 0x66, + CONF_APS_ALLOW_R19_SECURITY = 0x67, + CONF_IMPLICIT_CERTIFICATE = 0x69, + CONF_DEVICE_PRIVATE_KEY = 0x6A, + CONF_CA_PUBLIC_KEY = 0x6B, + CONF_KE_MAX_DEVICES = 0x6C, + CONF_USE_DEFAULT_TCLK = 0x6D, + CONF_RNG_COUNTER = 0x6F, + CONF_RANDOM_SEED = 0x70, + CONF_TRUSTCENTER_ADDR = 0x71, + CONF_USERDESC = 0x81, + CONF_NWKKEY = 0x82, + CONF_PANID = 0x83, + CONF_CHANLIST = 0x84, + CONF_LEAVE_CTRL = 0x85, + CONF_SCAN_DURATION = 0x86, + CONF_LOGICAL_TYPE = 0x87, + CONF_NWKMGR_MIN_TX = 0x88, + CONF_NWKMGR_ADDR = 0x89, + CONF_ZDO_DIRECT_CB = 0x8F, + CONF_TCLK_TABLE_START = 0x0101, + ZNP_HAS_CONFIGURED = 0xF00 +}; +# 210 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino" +enum Z_Status { + Z_Success = 0x00, + Z_Failure = 0x01, + Z_InvalidParameter = 0x02, + Z_MemError = 0x03, + Z_Created = 0x09, + Z_BufferFull = 0x11 +}; + +enum Z_App_Profiles { + Z_PROF_IPM = 0x0101, + Z_PROF_HA = 0x0104, + Z_PROF_CBA = 0x0105, + Z_PROF_TA = 0x0107, + Z_PROF_PHHC = 0x0108, + Z_PROF_AMI = 0x0109, +}; + +enum Z_Device_Ids { + Z_DEVID_CONF_TOOL = 0x0005, +# 262 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino" +}; +# 275 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino" +enum AfCommand : uint8_t { + AF_REGISTER = 0x00, + AF_DATA_REQUEST = 0x01, + AF_DATA_REQUEST_EXT = 0x02, + AF_DATA_REQUEST_SRC_RTG = 0x03, + AF_INTER_PAN_CTL = 0x10, + AF_DATA_STORE = 0x11, + AF_DATA_RETRIEVE = 0x12, + AF_APSF_CONFIG_SET = 0x13, + AF_DATA_CONFIRM = 0x80, + AF_REFLECT_ERROR = 0x83, + AF_INCOMING_MSG = 0x81, + AF_INCOMING_MSG_EXT = 0x82 +}; + + +enum : uint8_t { + ZDO_NWK_ADDR_REQ = 0x00, + ZDO_IEEE_ADDR_REQ = 0x01, + ZDO_NODE_DESC_REQ = 0x02, + ZDO_POWER_DESC_REQ = 0x03, + ZDO_SIMPLE_DESC_REQ = 0x04, + ZDO_ACTIVE_EP_REQ = 0x05, + ZDO_MATCH_DESC_REQ = 0x06, + ZDO_COMPLEX_DESC_REQ = 0x07, + ZDO_USER_DESC_REQ = 0x08, + ZDO_DEVICE_ANNCE = 0x0A, + ZDO_USER_DESC_SET = 0x0B, + ZDO_SERVER_DISC_REQ = 0x0C, + ZDO_END_DEVICE_BIND_REQ = 0x20, + ZDO_BIND_REQ = 0x21, + ZDO_UNBIND_REQ = 0x22, + ZDO_SET_LINK_KEY = 0x23, + ZDO_REMOVE_LINK_KEY = 0x24, + ZDO_GET_LINK_KEY = 0x25, + ZDO_MGMT_NWK_DISC_REQ = 0x30, + ZDO_MGMT_LQI_REQ = 0x31, + ZDO_MGMT_RTQ_REQ = 0x32, + ZDO_MGMT_BIND_REQ = 0x33, + ZDO_MGMT_LEAVE_REQ = 0x34, + ZDO_MGMT_DIRECT_JOIN_REQ = 0x35, + ZDO_MGMT_PERMIT_JOIN_REQ = 0x36, + ZDO_MGMT_NWK_UPDATE_REQ = 0x37, + ZDO_MSG_CB_REGISTER = 0x3E, + ZDO_MGS_CB_REMOVE = 0x3F, + ZDO_STARTUP_FROM_APP = 0x40, + ZDO_AUTO_FIND_DESTINATION = 0x41, + ZDO_EXT_REMOVE_GROUP = 0x47, + ZDO_EXT_REMOVE_ALL_GROUP = 0x48, + ZDO_EXT_FIND_ALL_GROUPS_ENDPOINT = 0x49, + ZDO_EXT_FIND_GROUP = 0x4A, + ZDO_EXT_ADD_GROUP = 0x4B, + ZDO_EXT_COUNT_ALL_GROUPS = 0x4C, + ZDO_NWK_ADDR_RSP = 0x80, + ZDO_IEEE_ADDR_RSP = 0x81, + ZDO_NODE_DESC_RSP = 0x82, + ZDO_POWER_DESC_RSP = 0x83, + ZDO_SIMPLE_DESC_RSP = 0x84, + ZDO_ACTIVE_EP_RSP = 0x85, + ZDO_MATCH_DESC_RSP = 0x86, + ZDO_COMPLEX_DESC_RSP = 0x87, + ZDO_USER_DESC_RSP = 0x88, + ZDO_USER_DESC_CONF = 0x89, + ZDO_SERVER_DISC_RSP = 0x8A, + ZDO_END_DEVICE_BIND_RSP = 0xA0, + ZDO_BIND_RSP = 0xA1, + ZDO_UNBIND_RSP = 0xA2, + ZDO_MGMT_NWK_DISC_RSP = 0xB0, + ZDO_MGMT_LQI_RSP = 0xB1, + ZDO_MGMT_RTG_RSP = 0xB2, + ZDO_MGMT_BIND_RSP = 0xB3, + ZDO_MGMT_LEAVE_RSP = 0xB4, + ZDO_MGMT_DIRECT_JOIN_RSP = 0xB5, + ZDO_MGMT_PERMIT_JOIN_RSP = 0xB6, + ZDO_STATE_CHANGE_IND = 0xC0, + ZDO_END_DEVICE_ANNCE_IND = 0xC1, + ZDO_MATCH_DESC_RSP_SENT = 0xC2, + ZDO_STATUS_ERROR_RSP = 0xC3, + ZDO_SRC_RTG_IND = 0xC4, + ZDO_LEAVE_IND = 0xC9, + ZDO_TC_DEV_IND = 0xCA, + ZDO_PERMIT_JOIN_IND = 0xCB, + ZDO_MSG_CB_INCOMING = 0xFF +}; + + +enum ZdoStates { + ZDO_DEV_HOLD = 0x00, + ZDO_DEV_INIT = 0x01, + ZDO_DEV_NWK_DISC = 0x02, + ZDO_DEV_NWK_JOINING = 0x03, + ZDO_DEV_NWK_REJOIN = 0x04, + ZDO_DEV_END_DEVICE_UNAUTH = 0x05, + ZDO_DEV_END_DEVICE = 0x06, + ZDO_DEV_ROUTER = 0x07, + ZDO_DEV_COORD_STARTING = 0x08, + ZDO_DEV_ZB_COORD = 0x09, + ZDO_DEV_NWK_ORPHAN = 0x0A, +}; + + +enum Z_Util { + Z_UTIL_GET_DEVICE_INFO = 0x00, + Z_UTIL_GET_NV_INFO = 0x01, + Z_UTIL_SET_PANID = 0x02, + Z_UTIL_SET_CHANNELS = 0x03, + Z_UTIL_SET_SECLEVEL = 0x04, + Z_UTIL_SET_PRECFGKEY = 0x05, + Z_UTIL_CALLBACK_SUB_CMD = 0x06, + Z_UTIL_KEY_EVENT = 0x07, + Z_UTIL_TIME_ALIVE = 0x09, + Z_UTIL_LED_CONTROL = 0x0A, + Z_UTIL_TEST_LOOPBACK = 0x10, + Z_UTIL_DATA_REQ = 0x11, + Z_UTIL_SRC_MATCH_ENABLE = 0x20, + Z_UTIL_SRC_MATCH_ADD_ENTRY = 0x21, + Z_UTIL_SRC_MATCH_DEL_ENTRY = 0x22, + Z_UTIL_SRC_MATCH_CHECK_SRC_ADDR = 0x23, + Z_UTIL_SRC_MATCH_ACK_ALL_PENDING = 0x24, + Z_UTIL_SRC_MATCH_CHECK_ALL_PENDING = 0x25, + Z_UTIL_ADDRMGR_EXT_ADDR_LOOKUP = 0x40, + Z_UTIL_ADDRMGR_NWK_ADDR_LOOKUP = 0x41, + Z_UTIL_APSME_LINK_KEY_DATA_GET = 0x44, + Z_UTIL_APSME_LINK_KEY_NV_ID_GET = 0x45, + Z_UTIL_ASSOC_COUNT = 0x48, + Z_UTIL_ASSOC_FIND_DEVICE = 0x49, + Z_UTIL_ASSOC_GET_WITH_ADDRESS = 0x4A, + Z_UTIL_APSME_REQUEST_KEY_CMD = 0x4B, + Z_UTIL_ZCL_KEY_EST_INIT_EST = 0x80, + Z_UTIL_ZCL_KEY_EST_SIGN = 0x81, + Z_UTIL_UTIL_SYNC_REQ = 0xE0, + Z_UTIL_ZCL_KEY_ESTABLISH_IND = 0xE1 +}; + +enum ZCL_Global_Commands { + ZCL_READ_ATTRIBUTES = 0x00, + ZCL_READ_ATTRIBUTES_RESPONSE = 0x01, + ZCL_WRITE_ATTRIBUTES = 0x02, + ZCL_WRITE_ATTRIBUTES_UNDIVIDED = 0x03, + ZCL_WRITE_ATTRIBUTES_RESPONSE = 0x04, + ZCL_WRITE_ATTRIBUTES_NORESPONSE = 0x05, + ZCL_CONFIGURE_REPORTING = 0x06, + ZCL_CONFIGURE_REPORTING_RESPONSE = 0x07, + ZCL_READ_REPORTING_CONFIGURATION = 0x08, + ZCL_READ_REPORTING_CONFIGURATION_RESPONSE = 0x09, + ZCL_REPORT_ATTRIBUTES = 0x0a, + ZCL_DEFAULT_RESPONSE = 0x0b, + ZCL_DISCOVER_ATTRIBUTES = 0x0c, + ZCL_DISCOVER_ATTRIBUTES_RESPONSE = 0x0d + +}; + +const uint16_t Z_ProfileIds[] PROGMEM = { 0x0104, 0x0109, 0xA10E, 0xC05E }; +const char Z_ProfileNames[] PROGMEM = "ZigBee Home Automation|ZigBee Smart Energy|ZigBee Green Power|ZigBee Light Link"; + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_1_headers.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_1_headers.ino" +#ifdef USE_ZIGBEE + + + +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); + + + +JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) { + + if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) { + return *(JsonVariant*)nullptr; + } + + for (auto kv : json) { + const char *key = kv.key; + JsonVariant &value = kv.value; + + if (0 == strcasecmp_P(key, needle)) { + return value; + } + } + + return *(JsonVariant*)nullptr; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino" +#ifdef USE_ZIGBEE + +#ifndef ZIGBEERECEIVED +#define ZIGBEERECEIVED 1 +#endif + +#include +#include + +#ifndef ZIGBEE_SAVE_DELAY_SECONDS +#define ZIGBEE_SAVE_DELAY_SECONDS 10; +#endif +const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; + +typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); + +typedef struct Z_Device { + uint16_t shortaddr; + uint64_t longaddr; + uint32_t firstSeen; + uint32_t lastSeen; + String manufacturerId; + String modelId; + String friendlyName; + std::vector endpoints; + std::vector clusters_in; + std::vector clusters_out; + + uint32_t timer; + uint16_t cluster; + uint16_t endpoint; + uint32_t value; + Z_DeviceTimer func; + + DynamicJsonBuffer *json_buffer; + JsonObject *json; + + uint8_t seqNumber; +} Z_Device; + + + + + + + +class Z_Devices { +public: + Z_Devices() {}; + + + + + + + uint16_t isKnownShortAddr(uint16_t shortaddr) const; + uint16_t isKnownLongAddr(uint64_t longaddr) const; + uint16_t isKnownIndex(uint32_t index) const; + uint16_t isKnownFriendlyName(const char * name) const; + + uint64_t getDeviceLongAddr(uint16_t shortaddr) const; + + + + void updateDevice(uint16_t shortaddr, uint64_t longaddr = 0); + + + void addEndoint(uint16_t shortaddr, uint8_t endpoint); + + + void addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t profileId); + + + void addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out); + + uint8_t findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster); + + void setManufId(uint16_t shortaddr, const char * str); + void setModelId(uint16_t shortaddr, const char * str); + void setFriendlyName(uint16_t shortaddr, const char * str); + const String * getFriendlyName(uint16_t) const; + + + void updateLastSeen(uint16_t shortaddr); + + + uint8_t getNextSeqNumber(uint16_t shortaddr); + + + String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const; + + + void resetTimer(uint32_t shortaddr); + void setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func); + void runTimer(void); + + + void jsonClear(uint16_t shortaddr); + void jsonAppend(uint16_t shortaddr, const JsonObject &values); + const JsonObject *jsonGet(uint16_t shortaddr); + void jsonPublishFlush(uint16_t shortaddr); + bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values); + void jsonPublishNow(uint16_t shortaddr, JsonObject &values); + + + size_t devicesSize(void) const { + return _devices.size(); + } + const Z_Device &devicesAt(size_t i) const { + return _devices.at(i); + } + + + bool removeDevice(uint16_t shortaddr); + + + void dirty(void); + void clean(void); + + + uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const; + +private: + std::vector _devices = {}; + uint32_t _saveTimer = 0; + uint8_t _seqNumber = 0; + + template < typename T> + static bool findInVector(const std::vector & vecOfElements, const T & element); + + template < typename T> + static int32_t findEndpointInVector(const std::vector & vecOfElements, uint8_t element); + + + static int32_t findClusterEndpoint(const std::vector & vecOfElements, uint16_t element); + + Z_Device & getShortAddr(uint16_t shortaddr); + const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; + Z_Device & getLongAddr(uint64_t longaddr); + + int32_t findShortAddr(uint16_t shortaddr) const; + int32_t findLongAddr(uint64_t longaddr) const; + int32_t findFriendlyName(const char * name) const; + + void _updateLastSeen(Z_Device &device) { + if (&device != nullptr) { + device.lastSeen = Rtc.utc_time; + } + }; + + + Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0); +}; + +Z_Devices zigbee_devices = Z_Devices(); + + +uint64_t localIEEEAddr = 0; + + +template < typename T> +bool Z_Devices::findInVector(const std::vector & vecOfElements, const T & element) { + + auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element); + + if (it != vecOfElements.end()) { + return true; + } else { + return false; + } +} + +template < typename T> +int32_t Z_Devices::findEndpointInVector(const std::vector & vecOfElements, uint8_t element) { + + + int32_t found = 0; + for (auto &elem : vecOfElements) { + if ( ((elem >> 16) & 0xFF) == element) { return found; } + found++; + } + + return -1; +} +# 214 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino" +int32_t Z_Devices::findClusterEndpoint(const std::vector & vecOfElements, uint16_t cluster) { + int32_t found = 0; + for (auto &elem : vecOfElements) { + if ((elem & 0xFFFF) == cluster) { return found; } + found++; + } + return -1; +} + + + + + +Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { + if (!shortaddr && !longaddr) { return *(Z_Device*) nullptr; } + Z_Device device = { shortaddr, longaddr, + Rtc.utc_time, Rtc.utc_time, + String(), + String(), + String(), + std::vector(), + std::vector(), + std::vector(), + 0,0,0,0, + nullptr, + nullptr, nullptr, + 0, + }; + device.json_buffer = new DynamicJsonBuffer(); + _devices.push_back(device); + dirty(); + return _devices.back(); +} +# 256 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino" +int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const { + if (!shortaddr) { return -1; } + int32_t found = 0; + if (shortaddr) { + for (auto &elem : _devices) { + if (elem.shortaddr == shortaddr) { return found; } + found++; + } + } + return -1; +} +# 275 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino" +int32_t Z_Devices::findLongAddr(uint64_t longaddr) const { + if (!longaddr) { return -1; } + int32_t found = 0; + if (longaddr) { + for (auto &elem : _devices) { + if (elem.longaddr == longaddr) { return found; } + found++; + } + } + return -1; +} +# 294 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino" +int32_t Z_Devices::findFriendlyName(const char * name) const { + if (!name) { return -1; } + size_t name_len = strlen(name); + int32_t found = 0; + if (name_len) { + for (auto &elem : _devices) { + if (elem.friendlyName == name) { return found; } + found++; + } + } + return -1; +} + + +uint16_t Z_Devices::isKnownShortAddr(uint16_t shortaddr) const { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + return shortaddr; + } else { + return 0; + } +} + +uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const { + int32_t found = findLongAddr(longaddr); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + return device.shortaddr; + } else { + return 0; + } +} + +uint16_t Z_Devices::isKnownIndex(uint32_t index) const { + if (index < devicesSize()) { + const Z_Device & device = devicesAt(index); + return device.shortaddr; + } else { + return 0; + } +} + +uint16_t Z_Devices::isKnownFriendlyName(const char * name) const { + if ((!name) || (0 == strlen(name))) { return 0xFFFF; } + int32_t found = findFriendlyName(name); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + return device.shortaddr; + } else { + return 0; + } +} + +uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const { + const Z_Device & device = getShortAddrConst(shortaddr); + return device.longaddr; +} + + + + +Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) { + if (!shortaddr) { return *(Z_Device*) nullptr; } + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + return _devices[found]; + } + + return createDeviceEntry(shortaddr, 0); +} + +const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const { + if (!shortaddr) { return *(Z_Device*) nullptr; } + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + return _devices[found]; + } + return *((Z_Device*)nullptr); +} + + +Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) { + if (!longaddr) { return *(Z_Device*) nullptr; } + int32_t found = findLongAddr(longaddr); + if (found > 0) { + return _devices[found]; + } + return createDeviceEntry(0, longaddr); +} + + +bool Z_Devices::removeDevice(uint16_t shortaddr) { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + _devices.erase(_devices.begin() + found); + dirty(); + return true; + } + return false; +} + + + + + + +void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) { + int32_t s_found = findShortAddr(shortaddr); + int32_t l_found = findLongAddr(longaddr); + + if ((s_found >= 0) && (l_found >= 0)) { + if (s_found == l_found) { + updateLastSeen(shortaddr); + } else { + + _devices[l_found].shortaddr = shortaddr; + + _devices.erase(_devices.begin() + s_found); + updateLastSeen(shortaddr); + dirty(); + } + } else if (s_found >= 0) { + + + _devices[s_found].longaddr = longaddr; + updateLastSeen(shortaddr); + dirty(); + } else if (l_found >= 0) { + + _devices[l_found].shortaddr = shortaddr; + dirty(); + } else { + + if (shortaddr || longaddr) { + createDeviceEntry(shortaddr, longaddr); + } + } +} + + + + +void Z_Devices::addEndoint(uint16_t shortaddr, uint8_t endpoint) { + if (!shortaddr) { return; } + uint32_t ep_profile = (endpoint << 16); + Z_Device &device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + _updateLastSeen(device); + if (findEndpointInVector(device.endpoints, endpoint) < 0) { + device.endpoints.push_back(ep_profile); + dirty(); + } +} + +void Z_Devices::addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t profileId) { + if (!shortaddr) { return; } + uint32_t ep_profile = (endpoint << 16) | profileId; + Z_Device &device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + _updateLastSeen(device); + int32_t found = findEndpointInVector(device.endpoints, endpoint); + if (found < 0) { + device.endpoints.push_back(ep_profile); + dirty(); + } else { + if (device.endpoints[found] != ep_profile) { + device.endpoints[found] = ep_profile; + dirty(); + } + } +} + +void Z_Devices::addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out) { + if (!shortaddr) { return; } + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + _updateLastSeen(device); + uint32_t ep_cluster = (endpoint << 16) | cluster; + if (!out) { + if (!findInVector(device.clusters_in, ep_cluster)) { + device.clusters_in.push_back(ep_cluster); + dirty(); + } + } else { + if (!findInVector(device.clusters_out, ep_cluster)) { + device.clusters_out.push_back(ep_cluster); + dirty(); + } + } +} + + + +uint8_t Z_Devices::findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster){ + int32_t short_found = findShortAddr(shortaddr); + if (short_found < 0) return 0; + Z_Device &device = getShortAddr(shortaddr); + if (&device == nullptr) { return 0; } + int32_t found = findClusterEndpoint(device.clusters_in, cluster); + if (found >= 0) { + return (device.clusters_in[found] >> 16) & 0xFF; + } else { + return 0; + } +} + + +void Z_Devices::setManufId(uint16_t shortaddr, const char * str) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + _updateLastSeen(device); + if (!device.manufacturerId.equals(str)) { + dirty(); + } + device.manufacturerId = str; +} +void Z_Devices::setModelId(uint16_t shortaddr, const char * str) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + _updateLastSeen(device); + if (!device.modelId.equals(str)) { + dirty(); + } + device.modelId = str; +} +void Z_Devices::setFriendlyName(uint16_t shortaddr, const char * str) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + _updateLastSeen(device); + if (!device.friendlyName.equals(str)) { + dirty(); + } + device.friendlyName = str; +} + +const String * Z_Devices::getFriendlyName(uint16_t shortaddr) const { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + if (device.friendlyName.length() > 0) { + return &device.friendlyName; + } + } + return nullptr; +} + + +void Z_Devices::updateLastSeen(uint16_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + _updateLastSeen(device); +} + + +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; + } +} + + + + +void Z_Devices::resetTimer(uint32_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + device.timer = 0; + device.func = nullptr; +} + + +void Z_Devices::setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + + device.cluster = cluster; + device.endpoint = endpoint; + device.value = value; + device.func = func; + device.timer = wait_ms + millis(); +} + + +void Z_Devices::runTimer(void) { + for (std::vector::iterator it = _devices.begin(); it != _devices.end(); ++it) { + Z_Device &device = *it; + uint16_t shortaddr = device.shortaddr; + + uint32_t timer = device.timer; + if ((timer) && TimeReached(timer)) { + device.timer = 0; + + (*device.func)(device.shortaddr, device.cluster, device.endpoint, device.value); + } + } + + if ((_saveTimer) && TimeReached(_saveTimer)) { + saveZigbeeDevices(); + _saveTimer = 0; + } +} + +void Z_Devices::jsonClear(uint16_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + + device.json = nullptr; + device.json_buffer->clear(); +} + +void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) { + to.remove(key); + + if (val.is()) { + String sval = val.as(); + to.set(key, sval); + } else if (val.is()) { + JsonArray &nested_arr = to.createNestedArray(key); + CopyJsonArray(nested_arr, val.as()); + } else if (val.is()) { + JsonObject &nested_obj = to.createNestedObject(key); + CopyJsonObject(nested_obj, val.as()); + } else { + to.set(key, val); + } +} + +void CopyJsonArray(JsonArray &to, const JsonArray &arr) { + for (auto v : arr) { + if (v.is()) { + String sval = v.as(); + to.add(sval); + } else if (v.is()) { + } else if (v.is()) { + } else { + to.add(v); + } + } +} + +void CopyJsonObject(JsonObject &to, const JsonObject &from) { + for (auto kv : from) { + String key_string = kv.key; + JsonVariant &val = kv.value; + + CopyJsonVariant(to, key_string, val); + } +} + + +bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return false; } + if (&values == nullptr) { return false; } + + if (nullptr == device.json) { + return false; + } + + for (auto kv : values) { + String key_string = kv.key; + + if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { + if (device.json->containsKey(kv.key)) { + return true; + } + } + } + return false; +} + +void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + if (&values == nullptr) { return; } + + if (nullptr == device.json) { + device.json = &(device.json_buffer->createObject()); + } + + char sa[8]; + snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr); + device.json->set(F(D_JSON_ZIGBEE_DEVICE), sa); + + const String * fname = zigbee_devices.getFriendlyName(shortaddr); + if (fname) { + device.json->set(F(D_JSON_ZIGBEE_NAME), (char*)fname->c_str()); + } + + + CopyJsonObject(*device.json, values); +} + +const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return nullptr; } + return device.json; +} + +void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } + JsonObject * json = device.json; + if (json == nullptr) { return; } + + const String * fname = zigbee_devices.getFriendlyName(shortaddr); + bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); +# 718 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino" + if (use_fname) { + json->remove(F(D_JSON_ZIGBEE_NAME)); + } else { + json->remove(F(D_JSON_ZIGBEE_DEVICE)); + } + + String msg = ""; + json->printTo(msg); + zigbee_devices.jsonClear(shortaddr); + + if (use_fname) { + 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 + + 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 + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); +#endif + } + + +} + +void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) { + jsonPublishFlush(shortaddr); + jsonAppend(shortaddr, values); + jsonPublishFlush(shortaddr); +} + +void Z_Devices::dirty(void) { + _saveTimer = kZigbeeSaveDelaySeconds * 1000 + millis(); +} +void Z_Devices::clean(void) { + _saveTimer = 0; +} + + + + + + +uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_known) const { + if (nullptr == param) { return 0; } + size_t param_len = strlen(param); + char dataBuf[param_len + 1]; + strcpy(dataBuf, param); + RemoveSpace(dataBuf); + uint16_t shortaddr = 0; + + if (strlen(dataBuf) < 4) { + + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 99)) { + shortaddr = zigbee_devices.isKnownIndex(XdrvMailbox.payload - 1); + } + } else if ((dataBuf[0] == '0') && (dataBuf[1] == 'x')) { + + if (strlen(dataBuf) < 18) { + + shortaddr = strtoull(dataBuf, nullptr, 0); + if (short_must_be_known) { + shortaddr = zigbee_devices.isKnownShortAddr(shortaddr); + } + + } else { + + uint64_t longaddr = strtoull(dataBuf, nullptr, 0); + shortaddr = zigbee_devices.isKnownLongAddr(longaddr); + } + } else { + + shortaddr = zigbee_devices.isKnownFriendlyName(dataBuf); + } + + return shortaddr; +} + + + + + +String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { + DynamicJsonBuffer jsonBuffer; + JsonArray& json = jsonBuffer.createArray(); + JsonArray& devices = json; + + for (std::vector::const_iterator it = _devices.begin(); it != _devices.end(); ++it) { + const Z_Device& device = *it; + uint16_t shortaddr = device.shortaddr; + char hex[22]; + + + if ((status_shortaddr) && (status_shortaddr != shortaddr)) { continue; } + + JsonObject& dev = devices.createNestedObject(); + + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); + dev[F(D_JSON_ZIGBEE_DEVICE)] = hex; + + if (device.friendlyName.length() > 0) { + dev[F(D_JSON_ZIGBEE_NAME)] = device.friendlyName; + } + + if (2 <= dump_mode) { + hex[0] = '0'; + hex[1] = 'x'; + Uint64toHex(device.longaddr, &hex[2], 64); + dev[F("IEEEAddr")] = hex; + if (device.modelId.length() > 0) { + dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId; + } + if (device.manufacturerId.length() > 0) { + dev[F("Manufacturer")] = device.manufacturerId; + } + } + + + if (3 <= dump_mode) { + JsonObject& dev_endpoints = dev.createNestedObject(F("Endpoints")); + for (std::vector::const_iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) { + uint32_t ep_profile = *ite; + uint8_t endpoint = (ep_profile >> 16) & 0xFF; + uint16_t profileId = ep_profile & 0xFFFF; + + snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); + JsonObject& ep = dev_endpoints.createNestedObject(hex); + + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), profileId); + ep[F("ProfileId")] = hex; + + int32_t found = -1; + for (uint32_t i = 0; i < sizeof(Z_ProfileIds) / sizeof(Z_ProfileIds[0]); i++) { + if (pgm_read_word(&Z_ProfileIds[i]) == profileId) { + found = i; + break; + } + } + if (found > 0) { + GetTextIndexed(hex, sizeof(hex), found, Z_ProfileNames); + ep[F("ProfileIdName")] = hex; + } + + ep.createNestedArray(F("ClustersIn")); + ep.createNestedArray(F("ClustersOut")); + } + + for (std::vector::const_iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t endpoint = (*itc >> 16) & 0xFF; + + snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); + JsonArray &cluster_arr = dev_endpoints[hex][F("ClustersIn")]; + + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster); + cluster_arr.add(hex); + } + + for (std::vector::const_iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t endpoint = (*itc >> 16) & 0xFF; + + snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); + JsonArray &cluster_arr = dev_endpoints[hex][F("ClustersOut")]; + + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster); + cluster_arr.add(hex); + } + } + } + String payload = ""; + payload.reserve(200); + json.printTo(payload); + return payload; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_4_persistence.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_4_persistence.ino" +#ifdef USE_ZIGBEE +# 50 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_4_persistence.ino" +const static uint16_t z_spi_start_sector = 0xFF; +const static uint8_t* z_spi_start = (uint8_t*) 0x402FF000; +const static uint8_t* z_dev_start = z_spi_start + 0x0800; +const static size_t z_spi_len = 0x1000; +const static size_t z_block_offset = 0x0800; +const static size_t z_block_len = 0x0800; + +class z_flashdata_t { +public: + uint32_t name; + uint16_t len; + uint16_t reserved; +}; + +const static uint32_t ZIGB_NAME = 0x3167697A; +const static size_t Z_MAX_FLASH = z_block_len - sizeof(z_flashdata_t); + + +const uint16_t Z_ClusterNumber[] PROGMEM = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0100, 0x0101, 0x0102, + 0x0201, 0x0202, 0x0203, 0x0204, + 0x0300, 0x0301, + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, + 0x0500, 0x0501, 0x0502, + 0x0700, 0x0701, 0x0702, + 0x0B00, 0x0B01, 0x0B02, 0x0B03, 0x0B04, 0x0B05, + 0x1000, + 0xFC0F, +}; + + +uint16_t fromClusterCode(uint8_t c) { + if (c >= sizeof(Z_ClusterNumber)/sizeof(Z_ClusterNumber[0])) { + return 0xFFFF; + } + return pgm_read_word(&Z_ClusterNumber[c]); +} + + +uint8_t toClusterCode(uint16_t c) { + for (uint32_t i = 0; i < sizeof(Z_ClusterNumber)/sizeof(Z_ClusterNumber[0]); i++) { + if (c == pgm_read_word(&Z_ClusterNumber[i])) { + return i; + } + } + return 0xFF; +} + +class SBuffer hibernateDevice(const struct Z_Device &device) { + SBuffer buf(128); + + buf.add8(0x00); + buf.add16(device.shortaddr); + buf.add64(device.longaddr); + uint32_t endpoints = device.endpoints.size(); + if (endpoints > 254) { endpoints = 254; } + buf.add8(endpoints); + + for (std::vector::const_iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) { + uint32_t ep_profile = *ite; + uint8_t endpoint = (ep_profile >> 16) & 0xFF; + uint16_t profileId = ep_profile & 0xFFFF; + + buf.add8(endpoint); + buf.add16(profileId); + for (std::vector::const_iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t c_endpoint = (*itc >> 16) & 0xFF; + + if (endpoint == c_endpoint) { + uint8_t clusterCode = toClusterCode(cluster); + if (0xFF != clusterCode) { buf.add8(clusterCode); } + } + } + buf.add8(0xFF); + + for (std::vector::const_iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t c_endpoint = (*itc >> 16) & 0xFF; + + if (endpoint == c_endpoint) { + uint8_t clusterCode = toClusterCode(cluster); + if (0xFF != clusterCode) { buf.add8(clusterCode); } + } + } + buf.add8(0xFF); + } + + + size_t model_len = device.modelId.length(); + if (model_len > 32) { model_len = 32; } + buf.addBuffer(device.modelId.c_str(), model_len); + buf.add8(0x00); + + + size_t manuf_len = device.manufacturerId.length(); + if (manuf_len > 32) {manuf_len = 32; } + buf.addBuffer(device.manufacturerId.c_str(), manuf_len); + buf.add8(0x00); + + + size_t frname_len = device.friendlyName.length(); + if (frname_len > 32) {frname_len = 32; } + buf.addBuffer(device.friendlyName.c_str(), frname_len); + buf.add8(0x00); + + + buf.set8(0, buf.len()); + + return buf; +} + +class SBuffer hibernateDevices(void) { + SBuffer buf(2048); + + size_t devices_size = zigbee_devices.devicesSize(); + if (devices_size > 32) { devices_size = 32; } + buf.add8(devices_size); + + for (uint32_t i = 0; i < devices_size; i++) { + const Z_Device & device = zigbee_devices.devicesAt(i); + const SBuffer buf_device = hibernateDevice(device); + buf.addBuffer(buf_device); + } + + size_t buf_len = buf.len(); + if (buf_len > 2040) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Devices list too big to fit in Flash (%d)"), buf_len); + } + + + char *hex_char = (char*) malloc((buf_len * 2) + 2); + if (hex_char) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbFlashStore %s"), + ToHex_P(buf.getBuffer(), buf_len, hex_char, (buf_len * 2) + 2)); + free(hex_char); + } + + return buf; +} + +void hidrateDevices(const SBuffer &buf) { + uint32_t buf_len = buf.len(); + if (buf_len <= 10) { return; } + + uint32_t k = 0; + uint32_t num_devices = buf.get8(k++); + + for (uint32_t i = 0; (i < num_devices) && (k < buf_len); i++) { + uint32_t dev_record_len = buf.get8(k); + + SBuffer buf_d = buf.subBuffer(k, dev_record_len); + + uint32_t d = 1; + uint16_t shortaddr = buf_d.get16(d); d += 2; + uint64_t longaddr = buf_d.get64(d); d += 8; + zigbee_devices.updateDevice(shortaddr, longaddr); + + uint32_t endpoints = buf_d.get8(d++); + for (uint32_t j = 0; j < endpoints; j++) { + uint8_t ep = buf_d.get8(d++); + uint16_t ep_profile = buf_d.get16(d); d += 2; + zigbee_devices.addEndointProfile(shortaddr, ep, ep_profile); + + + while (d < dev_record_len) { + uint8_t ep_cluster = buf_d.get8(d++); + if (0xFF == ep_cluster) { break; } + zigbee_devices.addCluster(shortaddr, ep, fromClusterCode(ep_cluster), false); + } + + while (d < dev_record_len) { + uint8_t ep_cluster = buf_d.get8(d++); + if (0xFF == ep_cluster) { break; } + zigbee_devices.addCluster(shortaddr, ep, fromClusterCode(ep_cluster), true); + } + } + + + char empty[] = ""; + + + uint32_t s_len = buf_d.strlen_s(d); + char *ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setModelId(shortaddr, ptr); + d += s_len + 1; + + + s_len = buf_d.strlen_s(d); + ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setManufId(shortaddr, ptr); + d += s_len + 1; + + + s_len = buf_d.strlen_s(d); + ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setFriendlyName(shortaddr, ptr); + d += s_len + 1; + + + k += dev_record_len; + } +} + +void loadZigbeeDevices(void) { + z_flashdata_t flashdata; + memcpy_P(&flashdata, z_dev_start, sizeof(z_flashdata_t)); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len); + + + if ((flashdata.name == ZIGB_NAME) && (flashdata.len > 0)) { + uint16_t buf_len = flashdata.len; + + SBuffer buf(buf_len); + buf.addBuffer(z_dev_start + sizeof(z_flashdata_t), buf_len); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee devices data in Flash (%d bytes)"), buf_len); + hidrateDevices(buf); + zigbee_devices.clean(); + } else { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "No zigbee devices data in Flash")); + } +} + +void saveZigbeeDevices(void) { + SBuffer buf = hibernateDevices(); + size_t buf_len = buf.len(); + if (buf_len > Z_MAX_FLASH) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Buffer too big to fit in Flash (%d bytes)"), buf_len); + return; + } + + + uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len); + if (!spi_buffer) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Cannot allocate 4KB buffer")); + return; + } + + ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + + z_flashdata_t *flashdata = (z_flashdata_t*)(spi_buffer + z_block_offset); + flashdata->name = ZIGB_NAME; + flashdata->len = buf_len; + flashdata->reserved = 0; + + memcpy(spi_buffer + z_block_offset + sizeof(z_flashdata_t), buf.getBuffer(), buf_len); + + + if (ESP.flashEraseSector(z_spi_start_sector)) { + ESP.flashWrite(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + } + + free(spi_buffer); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee Devices Data store in Flash (0x%08X - %d bytes)"), z_dev_start, buf_len); +} + + +void eraseZigbeeDevices(void) { + zigbee_devices.clean(); + + uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len); + if (!spi_buffer) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Cannot allocate 4KB buffer")); + return; + } + + ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + + + memset(spi_buffer + z_block_offset, 0xFF, z_block_len); + + + if (ESP.flashEraseSector(z_spi_start_sector)) { + ESP.flashWrite(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + } + + free(spi_buffer); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee Devices Data erased (0x%08X - %d bytes)"), z_dev_start, z_block_len); +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino" +#ifdef USE_ZIGBEE + + + + + +typedef union ZCLHeaderFrameControl_t { + struct { + uint8_t frame_type : 2; + uint8_t manuf_specific : 1; + uint8_t direction : 1; + uint8_t disable_def_resp : 1; + uint8_t reserved : 3; + } b; + uint32_t d8; +} ZCLHeaderFrameControl_t; + + +class ZCLFrame { +public: + + ZCLFrame(uint8_t frame_control, uint16_t manuf_code, uint8_t transact_seq, uint8_t cmd_id, + const char *buf, size_t buf_len, uint16_t clusterid, uint16_t groupid, + uint16_t srcaddr, uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast, + uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber, + uint32_t timestamp): + _cmd_id(cmd_id), _manuf_code(manuf_code), _transact_seq(transact_seq), + _payload(buf_len ? buf_len : 250), + _cluster_id(clusterid), _group_id(groupid), + _srcaddr(srcaddr), _srcendpoint(srcendpoint), _dstendpoint(dstendpoint), _wasbroadcast(wasbroadcast), + _linkquality(linkquality), _securityuse(securityuse), _seqnumber(seqnumber), + _timestamp(timestamp) + { + _frame_control.d8 = frame_control; + _payload.addBuffer(buf, buf_len); + }; + + + void log(void) { + char hex_char[_payload.len()*2+2]; + ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char)); + Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{" + "\"groupid\":%d," "\"clusterid\":%d," "\"srcaddr\":\"0x%04X\"," + "\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d," + "\"" D_CMND_ZIGBEE_LINKQUALITY "\":%d," "\"securityuse\":%d," "\"seqnumber\":%d," + "\"timestamp\":%d," + "\"fc\":\"0x%02X\",\"manuf\":\"0x%04X\",\"transact\":%d," + "\"cmdid\":\"0x%02X\",\"payload\":\"%s\"}}"), + _group_id, _cluster_id, _srcaddr, + _srcendpoint, _dstendpoint, _wasbroadcast, + _linkquality, _securityuse, _seqnumber, + _timestamp, + _frame_control, _manuf_code, _transact_seq, _cmd_id, + hex_char); + if (Settings.flag3.tuya_serial_mqtt_publish) { + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); + } else { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data); + } + } + + static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid, + uint16_t srcaddr, uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast, + uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber, + uint32_t timestamp) { + uint32_t i = offset; + ZCLHeaderFrameControl_t frame_control; + uint16_t manuf_code = 0; + uint8_t transact_seq; + uint8_t cmd_id; + + frame_control.d8 = buf.get8(i++); + if (frame_control.b.manuf_specific) { + manuf_code = buf.get16(i); + i += 2; + } + transact_seq = buf.get8(i++); + cmd_id = buf.get8(i++); + ZCLFrame zcl_frame(frame_control.d8, manuf_code, transact_seq, cmd_id, + (const char *)(buf.buf() + i), len + offset - i, + clusterid, groupid, + srcaddr, srcendpoint, dstendpoint, wasbroadcast, + linkquality, securityuse, seqnumber, + timestamp); + return zcl_frame; + } + + bool isClusterSpecificCommand(void) { + return _frame_control.b.frame_type & 1; + } + + static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len); + void parseRawAttributes(JsonObject& json, uint8_t offset = 0); + void parseReadAttributes(JsonObject& json, uint8_t offset = 0); + void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0); + void postProcessAttributes(uint16_t shortaddr, JsonObject& json); + + inline void setGroupId(uint16_t groupid) { + _group_id = groupid; + } + + inline void setClusterId(uint16_t clusterid) { + _cluster_id = clusterid; + } + + inline uint8_t getCmdId(void) const { + return _cmd_id; + } + + inline uint16_t getClusterId(void) const { + return _cluster_id; + } + + inline uint16_t getSrcEndpoint(void) const { + return _srcendpoint; + } + + const SBuffer &getPayload(void) const { + return _payload; + } + + uint16_t getManufCode(void) const { + return _manuf_code; + } + +private: + ZCLHeaderFrameControl_t _frame_control = { .d8 = 0 }; + uint16_t _manuf_code = 0; + uint8_t _transact_seq = 0; + uint8_t _cmd_id = 0; + uint16_t _cluster_id = 0; + uint16_t _group_id = 0; + SBuffer _payload; + + uint16_t _srcaddr; + uint8_t _srcendpoint; + uint8_t _dstendpoint; + uint8_t _wasbroadcast; + uint8_t _linkquality; + uint8_t _securityuse; + uint8_t _seqnumber; + uint32_t _timestamp; +}; + + + + + + +uint8_t toPercentageCR2032(uint32_t voltage) { + uint32_t percentage; + if (voltage < 2100) { + percentage = 0; + } else if (voltage < 2440) { + percentage = 6 - ((2440 - voltage) * 6) / 340; + } else if (voltage < 2740) { + percentage = 18 - ((2740 - voltage) * 12) / 300; + } else if (voltage < 2900) { + percentage = 42 - ((2900 - voltage) * 24) / 160; + } else if (voltage < 3000) { + percentage = 100 - ((3000 - voltage) * 58) / 100; + } else if (voltage >= 3000) { + percentage = 100; + } + return percentage; +} + + +uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf, + uint32_t offset, uint32_t len) { + + uint32_t i = offset; + uint32_t attrtype = buf.get8(i++); + + + json[attrid_str] = (char*) nullptr; + + + switch (attrtype) { + case 0x00: + case 0xFF: + break; + case 0x10: + { + uint8_t val_bool = buf.get8(i++); + if (0xFF != val_bool) { + json[attrid_str] = (bool) (val_bool ? true : false); + } + } + break; + case 0x20: + { + uint8_t uint8_val = buf.get8(i); + i += 1; + if (0xFF != uint8_val) { + json[attrid_str] = uint8_val; + } + } + break; + case 0x21: + { + uint16_t uint16_val = buf.get16(i); + i += 2; + if (0xFFFF != uint16_val) { + json[attrid_str] = uint16_val; + } + } + break; + case 0x23: + { + uint32_t uint32_val = buf.get32(i); + i += 4; + if (0xFFFFFFFF != uint32_val) { + json[attrid_str] = uint32_val; + } + } + break; + + case 0x24: + case 0x25: + case 0x26: + case 0x27: + { + uint8_t len = attrtype - 0x1F; + + char hex[2*len+1]; + ToHex_P(buf.buf(i), len, hex, sizeof(hex)); + json[attrid_str] = hex; + i += len; + } + break; + case 0x28: + { + int8_t int8_val = buf.get8(i); + i += 1; + if (0x80 != int8_val) { + json[attrid_str] = int8_val; + } + } + break; + case 0x29: + { + int16_t int16_val = buf.get16(i); + i += 2; + if (0x8000 != int16_val) { + json[attrid_str] = int16_val; + } + } + break; + case 0x2B: + { + int32_t int32_val = buf.get32(i); + i += 4; + if (0x80000000 != int32_val) { + json[attrid_str] = int32_val; + } + } + break; + + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + { + uint8_t len = attrtype - 0x27; + + char hex[2*len+1]; + ToHex_P(buf.buf(i), len, hex, sizeof(hex)); + json[attrid_str] = hex; + i += len; + } + break; + + case 0x41: + case 0x42: + case 0x43: + case 0x44: + + { + bool parse_as_string = true; + uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); + i += (attrtype <= 0x42) ? 1 : 2; + if (i + len > buf.len()) { + len = buf.len() - i; + } + + + if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; } +# 318 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino" + if (parse_as_string) { + char str[len+1]; + strncpy(str, buf.charptr(i), len); + str[len] = 0x00; + json[attrid_str] = str; + } else { + + char hex[2*len+1]; + ToHex_P(buf.buf(i), len, hex, sizeof(hex)); + json[attrid_str] = hex; + } + + i += len; + break; + } + i += buf.get8(i) + 1; + break; + + case 0x08: + case 0x18: + { + uint8_t uint8_val = buf.get8(i); + i += 1; + json[attrid_str] = uint8_val; + } + break; + case 0x09: + case 0x19: + { + uint16_t uint16_val = buf.get16(i); + i += 2; + json[attrid_str] = uint16_val; + } + break; + case 0x0B: + case 0x1B: + { + uint32_t uint32_val = buf.get32(i); + i += 4; + json[attrid_str] = uint32_val; + } + break; + + case 0x30: + case 0x31: + i += attrtype - 0x2F; + break; + + + case 0x39: + { + uint32_t uint32_val = buf.get32(i); + float * float_val = (float*) &uint32_val; + i += 4; + json[attrid_str] = *float_val; + } + break; + + case 0xE0: + case 0xE1: + case 0xE2: + i += 4; + break; + + case 0xE8: + case 0xE9: + i += 2; + break; + case 0xEA: + i += 4; + break; + + case 0xF0: + i += 8; + break; + case 0xF1: + i += 16; + break; + + + case 0x0A: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + i += attrtype - 0x07; + break; + + case 0x1A: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + i += attrtype - 0x17; + break; + + case 0x38: + i += 2; + break; + case 0x3A: + { + uint64_t uint64_val = buf.get64(i); + double * double_val = (double*) &uint64_val; + i += 8; + json[attrid_str] = *double_val; + } + break; + } + + + + + + + return i - offset; +} + + +void ZCLFrame::generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len) { + uint32_t suffix = 1; + + snprintf_P(key, key_len, PSTR("%04X/%04X"), cluster, attr); + while (json.containsKey(key)) { + suffix++; + snprintf_P(key, key_len, PSTR("%04X/%04X+%d"), cluster, attr, suffix); + } +} + + +void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) { + uint32_t i = offset; + uint32_t len = _payload.len(); + + while (len >= i + 3) { + uint16_t attrid = _payload.get16(i); + i += 2; + + char key[16]; + generateAttributeName(json, _cluster_id, attrid, key, sizeof(key)); + + + if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) { + if (0x42 == _payload.get8(i)) { + _payload.set8(i, 0x41); + } + } + i += parseSingleAttribute(json, key, _payload, i, len); + } +} + + +void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) { + uint32_t i = offset; + uint32_t len = _payload.len(); + + while (len - i >= 4) { + uint16_t attrid = _payload.get16(i); + i += 2; + uint8_t status = _payload.get8(i++); + + if (0 == status) { + char key[16]; + generateAttributeName(json, _cluster_id, attrid, key, sizeof(key)); + + i += parseSingleAttribute(json, key, _payload, i, len); + } + } +} + + + +void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { + convertClusterSpecific(json, _cluster_id, _cmd_id, _frame_control.b.direction, _payload); +} + + + + +typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +typedef struct Z_AttributeConverter { + uint16_t cluster; + uint16_t attribute; + const char * name; + Z_AttrConverter func; +} Z_AttributeConverter; + + +const Z_AttributeConverter Z_PostProcess[] PROGMEM = { + { 0x0000, 0x0000, "ZCLVersion", &Z_Copy }, + { 0x0000, 0x0001, "AppVersion", &Z_Copy }, + { 0x0000, 0x0002, "StackVersion", &Z_Copy }, + { 0x0000, 0x0003, "HWVersion", &Z_Copy }, + { 0x0000, 0x0004, "Manufacturer", &Z_ManufKeep }, + { 0x0000, 0x0005, D_JSON_MODEL D_JSON_ID, &Z_ModelKeep }, + { 0x0000, 0x0006, "DateCode", &Z_Copy }, + { 0x0000, 0x0007, "PowerSource", &Z_Copy }, + { 0x0000, 0x4000, "SWBuildID", &Z_Copy }, + { 0x0000, 0xFFFF, nullptr, &Z_Remove }, + + { 0x0000, 0xFF01, nullptr, &Z_AqaraSensor }, + + + { 0x0001, 0x0000, "MainsVoltage", &Z_Copy }, + { 0x0001, 0x0001, "MainsFrequency", &Z_Copy }, + { 0x0001, 0x0020, "BatteryVoltage", &Z_Copy }, + { 0x0001, 0x0021, "BatteryPercentageRemaining",&Z_Copy }, + + + { 0x0002, 0x0000, "CurrentTemperature", &Z_Copy }, + { 0x0002, 0x0001, "MinTempExperienced", &Z_Copy }, + { 0x0002, 0x0002, "MaxTempExperienced", &Z_Copy }, + { 0x0002, 0x0003, "OverTempTotalDwell", &Z_Copy }, + + + { 0x0006, 0x0000, "Power", &Z_Copy }, + { 0x0006, 0x8000, "Power", &Z_Copy }, + + + { 0x0007, 0x0000, "SwitchType", &Z_Copy }, + + + { 0x0008, 0x0000, "Dimmer", &Z_Copy }, +# 548 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino" + { 0x0009, 0x0000, "AlarmCount", &Z_Copy }, + + { 0x000A, 0x0000, "Time", &Z_Copy }, + { 0x000A, 0x0001, "TimeStatus", &Z_Copy }, + { 0x000A, 0x0002, "TimeZone", &Z_Copy }, + { 0x000A, 0x0003, "DstStart", &Z_Copy }, + { 0x000A, 0x0004, "DstStart", &Z_Copy }, + { 0x000A, 0x0005, "DstShift", &Z_Copy }, + { 0x000A, 0x0006, "StandardTime", &Z_Copy }, + { 0x000A, 0x0007, "LocalTime", &Z_Copy }, + { 0x000A, 0x0008, "LastSetTime", &Z_Copy }, + { 0x000A, 0x0009, "ValidUntilTime", &Z_Copy }, + + { 0x000B, 0x0000, "LocationType", &Z_Copy }, + { 0x000B, 0x0000, "LocationMethod", &Z_Copy }, + { 0x000B, 0x0000, "LocationAge", &Z_Copy }, + { 0x000B, 0x0000, "QualityMeasure", &Z_Copy }, + { 0x000B, 0x0000, "NumberOfDevices", &Z_Copy }, + + { 0x000C, 0x0004, "ActiveText", &Z_Copy }, + { 0x000C, 0x001C, "Description", &Z_Copy }, + { 0x000C, 0x002E, "InactiveText", &Z_Copy }, + { 0x000C, 0x0041, "MaxPresentValue", &Z_Copy }, + { 0x000C, 0x0045, "MinPresentValue", &Z_Copy }, + { 0x000C, 0x0051, "OutOfService", &Z_Copy }, + { 0x000C, 0x0055, "AqaraRotate", &Z_Copy }, + { 0x000C, 0x0057, "PriorityArray", &Z_Copy }, + { 0x000C, 0x0067, "Reliability", &Z_Copy }, + { 0x000C, 0x0068, "RelinquishDefault", &Z_Copy }, + { 0x000C, 0x006A, "Resolution", &Z_Copy }, + { 0x000C, 0x006F, "StatusFlags", &Z_Copy }, + { 0x000C, 0x0075, "EngineeringUnits", &Z_Copy }, + { 0x000C, 0x0100, "ApplicationType", &Z_Copy }, + { 0x000C, 0xFF05, "Aqara_FF05", &Z_Copy }, + + { 0x0010, 0x0004, "ActiveText", &Z_Copy }, + { 0x0010, 0x001C, "Description", &Z_Copy }, + { 0x0010, 0x002E, "InactiveText", &Z_Copy }, + { 0x0010, 0x0042, "MinimumOffTime", &Z_Copy }, + { 0x0010, 0x0043, "MinimumOnTime", &Z_Copy }, + { 0x0010, 0x0051, "OutOfService", &Z_Copy }, + { 0x0010, 0x0054, "Polarity", &Z_Copy }, + { 0x0010, 0x0055, "PresentValue", &Z_Copy }, + { 0x0010, 0x0057, "PriorityArray", &Z_Copy }, + { 0x0010, 0x0067, "Reliability", &Z_Copy }, + { 0x0010, 0x0068, "RelinquishDefault", &Z_Copy }, + { 0x0010, 0x006F, "StatusFlags", &Z_Copy }, + { 0x0010, 0x0100, "ApplicationType", &Z_Copy }, + + { 0x0011, 0x0004, "ActiveText", &Z_Copy }, + { 0x0011, 0x001C, "Description", &Z_Copy }, + { 0x0011, 0x002E, "InactiveText", &Z_Copy }, + { 0x0011, 0x0042, "MinimumOffTime", &Z_Copy }, + { 0x0011, 0x0043, "MinimumOnTime", &Z_Copy }, + { 0x0011, 0x0051, "OutOfService", &Z_Copy }, + { 0x0011, 0x0055, "PresentValue", &Z_Copy }, + { 0x0011, 0x0057, "PriorityArray", &Z_Copy }, + { 0x0011, 0x0067, "Reliability", &Z_Copy }, + { 0x0011, 0x0068, "RelinquishDefault", &Z_Copy }, + { 0x0011, 0x006F, "StatusFlags", &Z_Copy }, + { 0x0011, 0x0100, "ApplicationType", &Z_Copy }, + + { 0x0012, 0x000E, "StateText", &Z_Copy }, + { 0x0012, 0x001C, "Description", &Z_Copy }, + { 0x0012, 0x004A, "NumberOfStates", &Z_Copy }, + { 0x0012, 0x0051, "OutOfService", &Z_Copy }, + { 0x0012, 0x0055, "PresentValue", &Z_AqaraCube }, + { 0x0012, 0x0067, "Reliability", &Z_Copy }, + { 0x0012, 0x006F, "StatusFlags", &Z_Copy }, + { 0x0012, 0x0100, "ApplicationType", &Z_Copy }, + + { 0x0013, 0x000E, "StateText", &Z_Copy }, + { 0x0013, 0x001C, "Description", &Z_Copy }, + { 0x0013, 0x004A, "NumberOfStates", &Z_Copy }, + { 0x0013, 0x0051, "OutOfService", &Z_Copy }, + { 0x0013, 0x0055, "PresentValue", &Z_Copy }, + { 0x0013, 0x0057, "PriorityArray", &Z_Copy }, + { 0x0013, 0x0067, "Reliability", &Z_Copy }, + { 0x0013, 0x0068, "RelinquishDefault", &Z_Copy }, + { 0x0013, 0x006F, "StatusFlags", &Z_Copy }, + { 0x0013, 0x0100, "ApplicationType", &Z_Copy }, + + { 0x0014, 0x000E, "StateText", &Z_Copy }, + { 0x0014, 0x001C, "Description", &Z_Copy }, + { 0x0014, 0x004A, "NumberOfStates", &Z_Copy }, + { 0x0014, 0x0051, "OutOfService", &Z_Copy }, + { 0x0014, 0x0055, "PresentValue", &Z_Copy }, + { 0x0014, 0x0067, "Reliability", &Z_Copy }, + { 0x0014, 0x0068, "RelinquishDefault", &Z_Copy }, + { 0x0014, 0x006F, "StatusFlags", &Z_Copy }, + { 0x0014, 0x0100, "ApplicationType", &Z_Copy }, + + { 0x001A, 0x0000, "TotalProfileNum", &Z_Copy }, + { 0x001A, 0x0001, "MultipleScheduling", &Z_Copy }, + { 0x001A, 0x0002, "EnergyFormatting", &Z_Copy }, + { 0x001A, 0x0003, "EnergyRemote", &Z_Copy }, + { 0x001A, 0x0004, "ScheduleMode", &Z_Copy }, + + { 0x0020, 0x0000, "CheckinInterval", &Z_Copy }, + { 0x0020, 0x0001, "LongPollInterval", &Z_Copy }, + { 0x0020, 0x0002, "ShortPollInterval", &Z_Copy }, + { 0x0020, 0x0003, "FastPollTimeout", &Z_Copy }, + { 0x0020, 0x0004, "CheckinIntervalMin", &Z_Copy }, + { 0x0020, 0x0005, "LongPollIntervalMin", &Z_Copy }, + { 0x0020, 0x0006, "FastPollTimeoutMax", &Z_Copy }, + + { 0x0100, 0x0000, "PhysicalClosedLimit", &Z_Copy }, + { 0x0100, 0x0001, "MotorStepSize", &Z_Copy }, + { 0x0100, 0x0002, "Status", &Z_Copy }, + { 0x0100, 0x0010, "ClosedLimit", &Z_Copy }, + { 0x0100, 0x0011, "Mode", &Z_Copy }, + + { 0x0101, 0x0000, "LockState", &Z_Copy }, + { 0x0101, 0x0001, "LockType", &Z_Copy }, + { 0x0101, 0x0002, "ActuatorEnabled", &Z_Copy }, + { 0x0101, 0x0003, "DoorState", &Z_Copy }, + { 0x0101, 0x0004, "DoorOpenEvents", &Z_Copy }, + { 0x0101, 0x0005, "DoorClosedEvents", &Z_Copy }, + { 0x0101, 0x0006, "OpenPeriod", &Z_Copy }, + + { 0x0101, 0x0055, "AqaraVibrationMode", &Z_AqaraVibration }, + { 0x0101, 0x0503, "AqaraVibrationsOrAngle", &Z_Copy }, + { 0x0101, 0x0505, "AqaraVibration505", &Z_Copy }, + { 0x0101, 0x0508, "AqaraAccelerometer", &Z_AqaraVibration }, + + { 0x0102, 0x0000, "WindowCoveringType", &Z_Copy }, + { 0x0102, 0x0001, "PhysicalClosedLimitLift",&Z_Copy }, + { 0x0102, 0x0002, "PhysicalClosedLimitTilt",&Z_Copy }, + { 0x0102, 0x0003, "CurrentPositionLift", &Z_Copy }, + { 0x0102, 0x0004, "CurrentPositionTilt", &Z_Copy }, + { 0x0102, 0x0005, "NumberofActuationsLift",&Z_Copy }, + { 0x0102, 0x0006, "NumberofActuationsTilt",&Z_Copy }, + { 0x0102, 0x0007, "ConfigStatus", &Z_Copy }, + { 0x0102, 0x0008, "CurrentPositionLiftPercentage",&Z_Copy }, + { 0x0102, 0x0009, "CurrentPositionTiltPercentage",&Z_Copy }, + { 0x0102, 0x0010, "InstalledOpenLimitLift",&Z_Copy }, + { 0x0102, 0x0011, "InstalledClosedLimitLift",&Z_Copy }, + { 0x0102, 0x0012, "InstalledOpenLimitTilt", &Z_Copy }, + { 0x0102, 0x0013, "InstalledClosedLimitTilt", &Z_Copy }, + { 0x0102, 0x0014, "VelocityLift",&Z_Copy }, + { 0x0102, 0x0015, "AccelerationTimeLift",&Z_Copy }, + { 0x0102, 0x0016, "DecelerationTimeLift", &Z_Copy }, + { 0x0102, 0x0017, "Mode",&Z_Copy }, + { 0x0102, 0x0018, "IntermediateSetpointsLift",&Z_Copy }, + { 0x0102, 0x0019, "IntermediateSetpointsTilt",&Z_Copy }, + + + { 0x0300, 0x0000, "Hue", &Z_Copy }, + { 0x0300, 0x0001, "Sat", &Z_Copy }, + { 0x0300, 0x0002, "RemainingTime", &Z_Copy }, + { 0x0300, 0x0003, "X", &Z_Copy }, + { 0x0300, 0x0004, "Y", &Z_Copy }, + { 0x0300, 0x0005, "DriftCompensation", &Z_Copy }, + { 0x0300, 0x0006, "CompensationText", &Z_Copy }, + { 0x0300, 0x0007, "CT", &Z_Copy }, + { 0x0300, 0x0008, "ColorMode", &Z_Copy }, + { 0x0300, 0x0010, "NumberOfPrimaries", &Z_Copy }, + { 0x0300, 0x0011, "Primary1X", &Z_Copy }, + { 0x0300, 0x0012, "Primary1Y", &Z_Copy }, + { 0x0300, 0x0013, "Primary1Intensity", &Z_Copy }, + { 0x0300, 0x0015, "Primary2X", &Z_Copy }, + { 0x0300, 0x0016, "Primary2Y", &Z_Copy }, + { 0x0300, 0x0017, "Primary2Intensity", &Z_Copy }, + { 0x0300, 0x0019, "Primary3X", &Z_Copy }, + { 0x0300, 0x001A, "Primary3Y", &Z_Copy }, + { 0x0300, 0x001B, "Primary3Intensity", &Z_Copy }, + { 0x0300, 0x0030, "WhitePointX", &Z_Copy }, + { 0x0300, 0x0031, "WhitePointY", &Z_Copy }, + { 0x0300, 0x0032, "ColorPointRX", &Z_Copy }, + { 0x0300, 0x0033, "ColorPointRY", &Z_Copy }, + { 0x0300, 0x0034, "ColorPointRIntensity", &Z_Copy }, + { 0x0300, 0x0036, "ColorPointGX", &Z_Copy }, + { 0x0300, 0x0037, "ColorPointGY", &Z_Copy }, + { 0x0300, 0x0038, "ColorPointGIntensity", &Z_Copy }, + { 0x0300, 0x003A, "ColorPointBX", &Z_Copy }, + { 0x0300, 0x003B, "ColorPointBY", &Z_Copy }, + { 0x0300, 0x003C, "ColorPointBIntensity", &Z_Copy }, + + + { 0x0400, 0x0000, D_JSON_ILLUMINANCE, &Z_Copy }, + { 0x0400, 0x0001, "MinMeasuredValue", &Z_Copy }, + { 0x0400, 0x0002, "MaxMeasuredValue", &Z_Copy }, + { 0x0400, 0x0003, "Tolerance", &Z_Copy }, + { 0x0400, 0x0004, "LightSensorType", &Z_Copy }, + { 0x0400, 0xFFFF, nullptr, &Z_Remove }, + + + { 0x0401, 0x0000, "LevelStatus", &Z_Copy }, + { 0x0401, 0x0001, "LightSensorType", &Z_Copy }, + { 0x0401, 0xFFFF, nullptr, &Z_Remove }, + + + { 0x0402, 0x0000, D_JSON_TEMPERATURE, &Z_FloatDiv100 }, + { 0x0402, 0x0001, "MinMeasuredValue", &Z_FloatDiv100 }, + { 0x0402, 0x0002, "MaxMeasuredValue", &Z_FloatDiv100 }, + { 0x0402, 0x0003, "Tolerance", &Z_FloatDiv100 }, + { 0x0402, 0xFFFF, nullptr, &Z_Remove }, + + + { 0x0403, 0x0000, D_JSON_PRESSURE_UNIT, &Z_AddPressureUnit }, + { 0x0403, 0x0000, D_JSON_PRESSURE, &Z_Copy }, + { 0x0403, 0x0001, "MinMeasuredValue", &Z_Copy }, + { 0x0403, 0x0002, "MaxMeasuredValue", &Z_Copy }, + { 0x0403, 0x0003, "Tolerance", &Z_Copy }, + { 0x0403, 0x0010, "ScaledValue", &Z_Copy }, + { 0x0403, 0x0011, "MinScaledValue", &Z_Copy }, + { 0x0403, 0x0012, "MaxScaledValue", &Z_Copy }, + { 0x0403, 0x0013, "ScaledTolerance", &Z_Copy }, + { 0x0403, 0x0014, "Scale", &Z_Copy }, + { 0x0403, 0xFFFF, nullptr, &Z_Remove }, + + + { 0x0404, 0x0000, D_JSON_FLOWRATE, &Z_FloatDiv10 }, + { 0x0404, 0x0001, "MinMeasuredValue", &Z_Copy }, + { 0x0404, 0x0002, "MaxMeasuredValue", &Z_Copy }, + { 0x0404, 0x0003, "Tolerance", &Z_Copy }, + { 0x0404, 0xFFFF, nullptr, &Z_Remove }, + + + { 0x0405, 0x0000, D_JSON_HUMIDITY, &Z_FloatDiv100 }, + { 0x0405, 0x0001, "MinMeasuredValue", &Z_Copy }, + { 0x0405, 0x0002, "MaxMeasuredValue", &Z_Copy }, + { 0x0405, 0x0003, "Tolerance", &Z_Copy }, + { 0x0405, 0xFFFF, nullptr, &Z_Remove }, + + + { 0x0406, 0x0000, OCCUPANCY, &Z_Copy }, + { 0x0406, 0x0001, "OccupancySensorType", &Z_Copy }, + { 0x0406, 0xFFFF, nullptr, &Z_Remove }, + + + { 0x0B01, 0x0000, "CompanyName", &Z_Copy }, + { 0x0B01, 0x0001, "MeterTypeID", &Z_Copy }, + { 0x0B01, 0x0004, "DataQualityID", &Z_Copy }, + { 0x0B01, 0x0005, "CustomerName", &Z_Copy }, + { 0x0B01, 0x0006, "Model", &Z_Copy }, + { 0x0B01, 0x0007, "PartNumber", &Z_Copy }, + { 0x0B01, 0x000A, "SoftwareRevision", &Z_Copy }, + { 0x0B01, 0x000C, "POD", &Z_Copy }, + { 0x0B01, 0x000D, "AvailablePower", &Z_Copy }, + { 0x0B01, 0x000E, "PowerThreshold", &Z_Copy }, + + + { 0x0B05, 0x0000, "NumberOfResets", &Z_Copy }, + { 0x0B05, 0x0001, "PersistentMemoryWrites",&Z_Copy }, + { 0x0B05, 0x011C, "LastMessageLQI", &Z_Copy }, + { 0x0B05, 0x011D, "LastMessageRSSI", &Z_Copy }, + +}; + + + +int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + json[new_name] = value; + zigbee_devices.setManufId(shortaddr, value.as()); + return 1; +} + +int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + json[new_name] = value; + zigbee_devices.setModelId(shortaddr, value.as()); + return 1; +} + + + +int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + return 1; +} + + +int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + json[new_name] = value; + return 1; +} + + +int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + json[new_name] = F(D_UNIT_PRESSURE); + return 0; +} + + +int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + json[new_name] = ((float)value) / 100.0f; + return 1; +} + +int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + json[new_name] = ((float)value) / 10.0f; + return 1; +} + + +int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) { + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json[F(OCCUPANCY)] = 0; + zigbee_devices.jsonPublishNow(shortaddr, json); +} + + +int32_t Z_AqaraCube(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + json[new_name] = value; + int32_t val = value; + const __FlashStringHelper *aqara_cube = F("AqaraCube"); + const __FlashStringHelper *aqara_cube_side = F("AqaraCubeSide"); + const __FlashStringHelper *aqara_cube_from_side = F("AqaraCubeFromSide"); + + switch (val) { + case 0: + json[aqara_cube] = F("shake"); + break; + case 2: + json[aqara_cube] = F("wakeup"); + break; + case 3: + json[aqara_cube] = F("fall"); + break; + case 64 ... 127: + json[aqara_cube] = F("flip90"); + json[aqara_cube_side] = val % 8; + json[aqara_cube_from_side] = (val - 64) / 8; + break; + case 128 ... 132: + json[aqara_cube] = F("flip180"); + json[aqara_cube_side] = val - 128; + break; + case 256 ... 261: + json[aqara_cube] = F("slide"); + json[aqara_cube_side] = val - 256; + break; + case 512 ... 517: + json[aqara_cube] = F("tap"); + json[aqara_cube_side] = val - 512; + break; + } +# 905 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino" + return 1; +} + + +int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + + switch (attr) { + case 0x0055: + { + int32_t ivalue = value; + const __FlashStringHelper * svalue; + switch (ivalue) { + case 1: svalue = F("vibrate"); break; + case 2: svalue = F("tilt"); break; + case 3: svalue = F("drop"); break; + default: svalue = F("unknown"); break; + } + json[new_name] = svalue; + } + break; + + + + + case 0x0508: + { + + + String hex = value; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + int16_t x, y, z; + z = buf2.get16(0); + y = buf2.get16(2); + x = buf2.get16(4); + JsonArray& xyz = json.createNestedArray(new_name); + xyz.add(x); + xyz.add(y); + xyz.add(z); + + float X = x; + float Y = y; + float Z = z; + int32_t Angle_X = 0.5f + atanf(X/sqrtf(z*z+y*y)) * f_180pi; + int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(x*x+z*z)) * f_180pi; + int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(x*x+y*y)) * f_180pi; + + + + JsonArray& angles = json.createNestedArray(F("AqaraAngles")); + angles.add(Angle_X); + angles.add(Angle_Y); + angles.add(Angle_Z); + } + break; + } + return 1; +} + +int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + String hex = value; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint32_t i = 0; + uint32_t len = buf2.len(); + char tmp[] = "tmp"; + + JsonVariant sub_value; + + while (len - i >= 2) { + uint8_t attrid = buf2.get8(i++); + + i += parseSingleAttribute(json, tmp, buf2, i, len); + float val = json[tmp]; + json.remove(tmp); + if (0x01 == attrid) { + json[F(D_JSON_VOLTAGE)] = val / 1000.0f; + json[F("Battery")] = toPercentageCR2032(val); + } else if (0 == zcl->getManufCode()) { + + if (0x64 == attrid) { + json[F(D_JSON_TEMPERATURE)] = val / 100.0f; + } else if (0x65 == attrid) { + json[F(D_JSON_HUMIDITY)] = val / 100.0f; + } else if (0x66 == attrid) { + json[F(D_JSON_PRESSURE)] = val / 100.0f; + json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); + } else if (0x01 == attrid) { + json[F(D_JSON_VOLTAGE)] = val / 1000.0f; + json[F("Battery")] = toPercentageCR2032(val); + } + } else if (0x115F == zcl->getManufCode()) { + + json[F("AqaraUnknown")] = val; + } + } + return 1; +} + + +void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { + + for (auto kv : json) { + String key_string = kv.key; + const char * key = key_string.c_str(); + JsonVariant& value = kv.value; + + char * delimiter = strchr(key, '/'); + char * delimiter2 = strchr(key, '+'); + if (delimiter) { + uint16_t attribute; + uint16_t suffix = 1; + uint16_t cluster = strtoul(key, &delimiter, 16); + if (!delimiter2) { + attribute = strtoul(delimiter+1, nullptr, 16); + } else { + attribute = strtoul(delimiter+1, &delimiter2, 16); + suffix = strtoul(delimiter2+1, nullptr, 10); + } + + + for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) { + const Z_AttributeConverter *converter = &Z_PostProcess[i]; + uint16_t conv_cluster = pgm_read_word(&converter->cluster); + uint16_t conv_attribute = pgm_read_word(&converter->attribute); + + if ((conv_cluster == cluster) && + ((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) { + String new_name_str = converter->name; + if (suffix > 1) { new_name_str += suffix; } + int32_t drop = (*converter->func)(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute); + if (drop) { + json.remove(key); + } + + } + } + } + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_6_commands.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_6_commands.ino" +#ifdef USE_ZIGBEE + +typedef struct Z_CommandConverter { + const char * tasmota_cmd; + uint16_t cluster; + uint8_t cmd; + uint8_t direction; + const char * param; +} Z_CommandConverter; + +typedef struct Z_XYZ_Var { + uint32_t x = 0; + uint32_t y = 0; + uint32_t z = 0; + uint8_t x_type = 0; + uint8_t y_type = 0; + uint8_t z_type = 0; +} Z_XYZ_Var; + + +const Z_CommandConverter Z_Commands[] = { + + { "AddGroup", 0x0004, 0x00, 0x01, "xxxx00" }, + { "ViewGroup", 0x0004, 0x01, 0x01, "xxxx" }, + { "GetGroup", 0x0004, 0x02, 0x01, "01xxxx" }, + { "GetAllGroups", 0x0004, 0x02, 0x01, "00" }, + { "RemoveGroup", 0x0004, 0x03, 0x01, "xxxx" }, + { "RemoveAllGroups",0x0004, 0x04, 0x01, "" }, + + { "Power", 0x0006, 0xFF, 0x01, "" }, + { "Dimmer", 0x0008, 0x04, 0x01, "xx0A00" }, + { "Dimmer+", 0x0008, 0x06, 0x01, "001902" }, + { "Dimmer-", 0x0008, 0x06, 0x01, "011902" }, + { "DimmerStop", 0x0008, 0x03, 0x01, "" }, + { "ResetAlarm", 0x0009, 0x00, 0x01, "xxyyyy" }, + { "ResetAllAlarms", 0x0009, 0x01, 0x01, "" }, + { "Hue", 0x0300, 0x00, 0x01, "xx000A00" }, + { "Sat", 0x0300, 0x03, 0x01, "xx0A00" }, + { "HueSat", 0x0300, 0x06, 0x01, "xxyy0A00" }, + { "Color", 0x0300, 0x07, 0x01, "xxxxyyyy0A00" }, + { "CT", 0x0300, 0x0A, 0x01, "xxxx0A00" }, + { "ShutterOpen", 0x0102, 0x00, 0x01, "" }, + { "ShutterClose", 0x0102, 0x01, 0x01, "" }, + { "ShutterStop", 0x0102, 0x02, 0x01, "" }, + { "ShutterLift", 0x0102, 0x05, 0x01, "xx" }, + { "ShutterTilt", 0x0102, 0x08, 0x01, "xx" }, + { "Shutter", 0x0102, 0xFF, 0x01, "" }, + + { "Occupancy", 0xEF00, 0x01, 0x01, "xx"}, + + { "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" }, + + { "ArrowClick", 0x0005, 0x07, 0x01, "xx" }, + { "ArrowHold", 0x0005, 0x08, 0x01, "xx" }, + { "ArrowRelease", 0x0005, 0x09, 0x01, "" }, + + { "ZoneStatusChange",0x0500, 0x00, 0x02, "xxxxyyzz" }, + + { "AddGroupResp", 0x0004, 0x00, 0x02, "xxyyyy" }, + { "ViewGroupResp", 0x0004, 0x01, 0x02, "xxyyyy" }, + { "GetGroupResp", 0x0004, 0x02, 0x02, "xxyyzzzz" }, + { "RemoveGroup", 0x0004, 0x03, 0x02, "xxyyyy" }, +}; + + +#define ZLE(x) ((x) & 0xFF), ((x) >> 8) + + +const uint8_t CLUSTER_0006[] = { ZLE(0x0000) }; +const uint8_t CLUSTER_0008[] = { ZLE(0x0000) }; +const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; +const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007) }; + + +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; + + switch (cluster) { + case 0x0006: + attrs = CLUSTER_0006; + attrs_len = sizeof(CLUSTER_0006); + break; + case 0x0008: + attrs = CLUSTER_0008; + attrs_len = sizeof(CLUSTER_0008); + break; + case 0x0009: + attrs = CLUSTER_0009; + attrs_len = sizeof(CLUSTER_0009); + break; + case 0x0300: + attrs = CLUSTER_0300; + attrs_len = sizeof(CLUSTER_0300); + break; + } + if (attrs) { + ZigbeeZCLSend(shortaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, true , zigbee_devices.getNextSeqNumber(shortaddr)); + } +} + + +void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) { + uint32_t wait_ms = 0; + + switch (cluster) { + case 0x0006: + case 0x0009: + wait_ms = 200; + break; + case 0x0008: + case 0x0300: + wait_ms = 1050; + break; + case 0x0102: + wait_ms = 10000; + break; + } + if (wait_ms) { + zigbee_devices.setTimer(shortaddr, wait_ms, cluster, endpoint, 0 , &Z_ReadAttrCallback); + } +} + + +inline bool isXYZ(char c) { + return (c >= 'x') && (c <= 'z'); +} + + + + +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_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; } + ret = (ret << 4) | v; + *data += 1; + } + return ret; +} + + + + + +void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz) { + const char *p = model; + uint32_t v = 0; + char c = pgm_read_byte(p); + while (c) { + char c1 = pgm_read_byte(p+1); + if (!c1) { break; } + if (isXYZ(c) && (c == c1) && (v < payload.len())) { + 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); + } +} +# 231 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_6_commands.ino" +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; + + + 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) { + + if ((0xFF == conv->cmd) || (cmd == conv->cmd)) { + + if ((direction && (conv->direction & 0x02)) || (!direction && (conv->direction & 0x01))) { + + + + + const char * p = conv->param; + + 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); + + if ((0x00 == c1) || isXYZ(c1)) { + break; + } + const char * p2 = p; + uint32_t nextbyte = parseHex_P(&p2, 2); + + 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) { + + 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; + } + break; + } + } + } + } + } + + + + + 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; + } else if (0 == xyz.y_type) { + json[command_name] = xyz.x; + } else { + + JsonArray &arr = json.createNestedArray(command_name); + arr.add(xyz.x); + arr.add(xyz.y); + if (xyz.z_type) { + arr.add(xyz.z); + } + } + } +} + + + + +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)) { + *cluster = conv->cluster; + *cmd = conv->cmd; + return (const __FlashStringHelper*) conv->param; + } + } + + return nullptr; +} + + +inline char hexDigit(uint32_t h) { + uint32_t nybble = h & 0x0F; + return (nybble > 9) ? 'A' - 10 + nybble : '0' + nybble; +} + + +String zigbeeCmdAddParams(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z) { + size_t len = strlen_P(zcl_cmd_P); + char zcl_cmd[len+1]; + strcpy_P(zcl_cmd, zcl_cmd_P); + + char *p = zcl_cmd; + while (*p) { + if (isXYZ(*p) && (*p == *(p+1))) { + uint8_t val; + switch (*p) { + case 'x': + val = x & 0xFF; + x = x >> 8; + break; + case 'y': + val = y & 0xFF; + y = y >> 8; + break; + case 'z': + val = z & 0xFF; + z = z >> 8; + break; + } + *p = hexDigit(val >> 4); + *(p+1) = hexDigit(val); + p++; + } + p++; + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SendZCLCommand_P: zcl_cmd = %s"), zcl_cmd); + + return String(zcl_cmd); +} + +const char kZ_Alias[] PROGMEM = "OFF|" D_OFF "|" D_FALSE "|" D_STOP "|" "OPEN" "|" + "ON|" D_ON "|" D_TRUE "|" D_START "|" "CLOSE" "|" + "TOGGLE|" D_TOGGLE "|" + "ALL" ; + +const uint8_t kZ_Numbers[] PROGMEM = { 0,0,0,0,0, + 1,1,1,1,1, + 2,2, + 255 }; + + +uint32_t ZigbeeAliasOrNumber(const char *state_text) { + char command[16]; + int state_number = GetCommandCode(command, sizeof(command), state_text, kZ_Alias); + if (state_number >= 0) { + + return pgm_read_byte(kZ_Numbers + state_number); + } else { + + return strtoul(state_text, nullptr, 0); + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino" +#ifdef USE_ZIGBEE + + + +const uint8_t ZIGBEE_STATUS_OK = 0; +const uint8_t ZIGBEE_STATUS_BOOT = 1; +const uint8_t ZIGBEE_STATUS_RESET_CONF = 2; +const uint8_t ZIGBEE_STATUS_STARTING = 3; +const uint8_t ZIGBEE_STATUS_PERMITJOIN_CLOSE = 20; +const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_60 = 21; +const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_XX = 22; +const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; +const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; +const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; +const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; +const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; +const uint8_t ZIGBEE_STATUS_DEVICE_IEEE = 35; +const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; +const uint8_t ZIGBEE_STATUS_CC_INFO = 51; +const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; +const uint8_t ZIGBEE_STATUS_ABORT = 99; + +typedef int32_t (*ZB_Func)(uint8_t value); +typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, const class SBuffer &buf); + +typedef union Zigbee_Instruction { + struct { + uint8_t i; + uint8_t d8; + uint16_t d16; + } i; + const void *p; + + + +} Zigbee_Instruction; + + + + +typedef struct Zigbee_Instruction_Type { + uint8_t instr; + uint8_t data; +} Zigbee_Instruction_Type; + +enum Zigbee_StateMachine_Instruction_Set { + + ZGB_INSTR_4_BYTES = 0, + ZGB_INSTR_NOOP = 0, + ZGB_INSTR_LABEL, + ZGB_INSTR_GOTO, + ZGB_INSTR_ON_ERROR_GOTO, + ZGB_INSTR_ON_TIMEOUT_GOTO, + ZGB_INSTR_WAIT, + ZGB_INSTR_WAIT_FOREVER, + ZGB_INSTR_STOP, + + + ZGB_INSTR_8_BYTES = 0x80, + ZGB_INSTR_CALL = 0x80, + ZGB_INSTR_LOG, + ZGB_INSTR_MQTT_STATE, + ZGB_INSTR_SEND, + ZGB_INSTR_WAIT_UNTIL, + ZGB_INSTR_WAIT_RECV, + ZGB_ON_RECV_UNEXPECTED, + + + ZGB_INSTR_12_BYTES = 0xF0, + ZGB_INSTR_WAIT_RECV_CALL, +}; + +#define ZI_NOOP() { .i = { ZGB_INSTR_NOOP, 0x00, 0x0000} }, +#define ZI_LABEL(x) { .i = { ZGB_INSTR_LABEL, (x), 0x0000} }, +#define ZI_GOTO(x) { .i = { ZGB_INSTR_GOTO, (x), 0x0000} }, +#define ZI_ON_ERROR_GOTO(x) { .i = { ZGB_INSTR_ON_ERROR_GOTO, (x), 0x0000} }, +#define ZI_ON_TIMEOUT_GOTO(x) { .i = { ZGB_INSTR_ON_TIMEOUT_GOTO, (x), 0x0000} }, +#define ZI_WAIT(x) { .i = { ZGB_INSTR_WAIT, 0x00, (x)} }, +#define ZI_WAIT_FOREVER() { .i = { ZGB_INSTR_WAIT_FOREVER, 0x00, 0x0000} }, +#define ZI_STOP(x) { .i = { ZGB_INSTR_STOP, (x), 0x0000} }, + +#define ZI_CALL(f,x) { .i = { ZGB_INSTR_CALL, (x), 0x0000} }, { .p = (const void*)(f) }, +#define ZI_LOG(x,m) { .i = { ZGB_INSTR_LOG, (x), 0x0000 } }, { .p = ((const void*)(m)) }, +#define ZI_MQTT_STATE(x,m) { .i = { ZGB_INSTR_MQTT_STATE, (x), 0x0000 } }, { .p = ((const void*)(m)) }, +#define ZI_ON_RECV_UNEXPECTED(f) { .i = { ZGB_ON_RECV_UNEXPECTED, 0x00, 0x0000} }, { .p = (const void*)(f) }, +#define ZI_SEND(m) { .i = { ZGB_INSTR_SEND, sizeof(m), 0x0000} }, { .p = (const void*)(m) }, +#define ZI_WAIT_RECV(x,m) { .i = { ZGB_INSTR_WAIT_RECV, sizeof(m), (x)} }, { .p = (const void*)(m) }, +#define ZI_WAIT_UNTIL(x,m) { .i = { ZGB_INSTR_WAIT_UNTIL, sizeof(m), (x)} }, { .p = (const void*)(m) }, +#define ZI_WAIT_RECV_FUNC(x,m,f) { .i = { ZGB_INSTR_WAIT_RECV_CALL, sizeof(m), (x)} }, { .p = (const void*)(m) }, { .p = (const void*)(f) }, + + +const uint8_t ZIGBEE_LABEL_START = 10; +const uint8_t ZIGBEE_LABEL_READY = 20; +const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; +const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_CLOSE = 30; +const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60 = 31; +const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX = 32; + +const uint8_t ZIGBEE_LABEL_ABORT = 99; +const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; + +struct ZigbeeStatus { + bool active = true; + bool state_machine = false; + bool state_waiting = false; + bool state_no_timeout = false; + bool ready = false; + uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; + uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; + int16_t pc = 0; + uint32_t next_timeout = 0; + + uint8_t *recv_filter = nullptr; + bool recv_until = false; + size_t recv_filter_len = 0; + ZB_RecvMsgFunc recv_func = nullptr; + ZB_RecvMsgFunc recv_unexpected = nullptr; + + bool init_phase = true; +}; +struct ZigbeeStatus zigbee; + +SBuffer *zigbee_buffer = nullptr; + + + + + +#define Z_B0(a) (uint8_t)( ((a) ) & 0xFF ) +#define Z_B1(a) (uint8_t)( ((a) >> 8) & 0xFF ) +#define Z_B2(a) (uint8_t)( ((a) >> 16) & 0xFF ) +#define Z_B3(a) (uint8_t)( ((a) >> 24) & 0xFF ) +#define Z_B4(a) (uint8_t)( ((a) >> 32) & 0xFF ) +#define Z_B5(a) (uint8_t)( ((a) >> 40) & 0xFF ) +#define Z_B6(a) (uint8_t)( ((a) >> 48) & 0xFF ) +#define Z_B7(a) (uint8_t)( ((a) >> 56) & 0xFF ) + +#define ZBM(n,x...) const uint8_t n[] PROGMEM = { x }; + +#define USE_ZIGBEE_CHANNEL_MASK (1 << (USE_ZIGBEE_CHANNEL)) + + + +ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 ) +ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND ) + +ZBM(ZBS_VERSION, Z_SREQ | Z_SYS, SYS_VERSION ) +ZBM(ZBR_VERSION, Z_SRSP | Z_SYS, SYS_VERSION ) + + +ZBM(ZBS_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, 0x00 ) +ZBM(ZBR_ZNPHC, Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_Success, 0x01 , 0x55) + + +ZBM(ZBS_PAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PANID ) +ZBM(ZBR_PAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PANID, 0x02 , + Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) + +ZBM(ZBS_EXTPAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_EXTENDED_PAN_ID ) +ZBM(ZBR_EXTPAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_EXTENDED_PAN_ID, + 0x08 , + Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID), + Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID), + ) + +ZBM(ZBS_CHANN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_CHANLIST ) +ZBM(ZBR_CHANN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_CHANLIST, + 0x04 , + Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK), + ) + +ZBM(ZBS_PFGK, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEY ) +ZBM(ZBR_PFGK, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEY, + 0x10 , + Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L), + Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L), + Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H), + Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H), + + ) + +ZBM(ZBS_PFGKEN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEYS_ENABLE ) +ZBM(ZBR_PFGKEN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEYS_ENABLE, + 0x01 , 0x00 ) + + + +ZBM(ZBR_W_OK, Z_SRSP | Z_SAPI, SAPI_WRITE_CONFIGURATION, Z_Success ) +ZBM(ZBR_WNV_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Success ) + + +ZBM(ZBS_FACTRES, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 , 0x02 ) + +ZBM(ZBS_W_PAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PANID, 0x02 , Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) + +ZBM(ZBS_W_EXTPAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_EXTENDED_PAN_ID, 0x08 , + Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID), + Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID) + ) + +ZBM(ZBS_W_CHANN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_CHANLIST, 0x04 , + Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK), + ) + +ZBM(ZBS_W_LOGTYP, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_LOGICAL_TYPE, 0x01 , 0x00 ) + +ZBM(ZBS_W_PFGK, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEY, + 0x10 , + Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L), + Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L), + Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H), + Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H), + + ) + +ZBM(ZBS_W_PFGKEN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEYS_ENABLE, 0x01 , 0x00 ) + +ZBM(ZBS_WNV_SECMODE, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(CONF_TCLK_TABLE_START), Z_B1(CONF_TCLK_TABLE_START), + 0x00 , 0x20 , + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, + 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + +ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 0x01 , 0x01 ) + +ZBM(ZBS_WNV_INITZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_ITEM_INIT, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, + 0x01, 0x00 , 0x01 , 0x00 ) + + +ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT ) + + +ZBM(ZBS_WNV_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(ZNP_HAS_CONFIGURED), Z_B1(ZNP_HAS_CONFIGURED), + 0x00 , 0x01 , 0x55 ) + +ZBM(ZBS_STARTUPFROMAPP, Z_SREQ | Z_ZDO, ZDO_STARTUP_FROM_APP, 100, 0 ) +ZBM(ZBR_STARTUPFROMAPP, Z_SRSP | Z_ZDO, ZDO_STARTUP_FROM_APP ) +ZBM(AREQ_STARTUPFROMAPP, Z_AREQ | Z_ZDO, ZDO_STATE_CHANGE_IND, ZDO_DEV_ZB_COORD ) + +ZBM(ZBS_GETDEVICEINFO, Z_SREQ | Z_UTIL, Z_UTIL_GET_DEVICE_INFO ) +ZBM(ZBR_GETDEVICEINFO, Z_SRSP | Z_UTIL, Z_UTIL_GET_DEVICE_INFO, Z_Success ) +# 272 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino" +ZBM(ZBS_ZDO_NODEDESCREQ, Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, 0x00, 0x00 , 0x00, 0x00 ) +ZBM(ZBR_ZDO_NODEDESCREQ, Z_SRSP | Z_ZDO, ZDO_NODE_DESC_REQ, Z_Success ) + +ZBM(AREQ_ZDO_NODEDESCRSP, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP) +# 290 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino" +ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00) +ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_Success) +ZBM(ZBR_ZDO_ACTIVEEPRSP_NONE, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 , Z_Success, + 0x00, 0x00 , 0x00 ) +ZBM(ZBR_ZDO_ACTIVEEPRSP_OK, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 , Z_Success, + 0x00, 0x00 , 0x02 , 0x0B, 0x01 ) + + +ZBM(ZBS_AF_REGISTER01, Z_SREQ | Z_AF, AF_REGISTER, 0x01 , Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), + 0x05, 0x00 , 0x00 , 0x00 , + 0x00 , 0x00 ) +ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_Success) +ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B , Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), + 0x05, 0x00 , 0x00 , 0x00 , + 0x00 , 0x00 ) + +ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 , + 0x00, 0x00 , 0x00 , 0x00 ) +ZBM(ZBS_PERMITJOINREQ_OPEN_60, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F , + 0xFC, 0xFF , 60 , 0x00 ) +ZBM(ZBS_PERMITJOINREQ_OPEN_XX, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F , + 0xFC, 0xFF , 0xFF , 0x00 ) +ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success) +ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 ) +ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 ) +ZBM(ZBR_PERMITJOIN_AREQ_OPEN_FF, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF ) +ZBM(ZBR_PERMITJOIN_AREQ_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_RSP, 0x00, 0x00 , Z_Success ) + +static const Zigbee_Instruction zb_prog[] PROGMEM = { + ZI_LABEL(0) + ZI_NOOP() + ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) + ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT) + ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default) + ZI_WAIT(10500) + ZI_ON_ERROR_GOTO(50) + + + + ZI_SEND(ZBS_RESET) + ZI_WAIT_RECV_FUNC(5000, ZBR_RESET, &Z_Reboot) + ZI_WAIT(100) + ZI_LOG(LOG_LEVEL_DEBUG, D_LOG_ZIGBEE "checking device configuration") + ZI_SEND(ZBS_ZNPHC) + ZI_WAIT_RECV(2000, ZBR_ZNPHC) + ZI_SEND(ZBS_VERSION) + ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) + ZI_SEND(ZBS_PAN) + ZI_WAIT_RECV(1000, ZBR_PAN) + ZI_SEND(ZBS_EXTPAN) + ZI_WAIT_RECV(1000, ZBR_EXTPAN) + ZI_SEND(ZBS_CHANN) + ZI_WAIT_RECV(1000, ZBR_CHANN) + ZI_SEND(ZBS_PFGK) + ZI_WAIT_RECV(1000, ZBR_PFGK) + ZI_SEND(ZBS_PFGKEN) + ZI_WAIT_RECV(1000, ZBR_PFGKEN) + + + + ZI_LABEL(ZIGBEE_LABEL_START) + ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, "Configured, starting coordinator") + + ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) + + +ZI_SEND(ZBS_STARTUPFROMAPP) + ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) + ZI_WAIT_UNTIL(10000, AREQ_STARTUPFROMAPP) + ZI_SEND(ZBS_GETDEVICEINFO) + ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo) + + ZI_SEND(ZBS_ZDO_NODEDESCREQ) + ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ) + ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCRSP) + ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) + ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ) + ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_NONE) + ZI_SEND(ZBS_AF_REGISTER01) + ZI_WAIT_RECV(1000, ZBR_AF_REGISTER) + ZI_SEND(ZBS_AF_REGISTER0B) + ZI_WAIT_RECV(1000, ZBR_AF_REGISTER) + + + ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) + ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ) + ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_OK) + ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) + ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) + + + + + + + ZI_LABEL(ZIGBEE_LABEL_READY) + ZI_MQTT_STATE(ZIGBEE_STATUS_OK, "Started") + ZI_LOG(LOG_LEVEL_INFO, D_LOG_ZIGBEE "Zigbee started") + ZI_CALL(&Z_State_Ready, 1) + ZI_CALL(&Z_Load_Devices, 0) + ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP) + ZI_WAIT_FOREVER() + ZI_GOTO(ZIGBEE_LABEL_READY) + + ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE) + + ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) + ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + + + ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP) + + ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60) + + ZI_SEND(ZBS_PERMITJOINREQ_OPEN_60) + ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + + + ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP) + + ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX) + + ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX) + ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + + + ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP) + + ZI_LABEL(50) + ZI_MQTT_STATE(ZIGBEE_STATUS_RESET_CONF, "Reseting configuration") + + ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) + ZI_SEND(ZBS_FACTRES) + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_RESET) + ZI_WAIT_RECV(5000, ZBR_RESET) + ZI_SEND(ZBS_W_PAN) + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_EXTPAN) + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_CHANN) + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_LOGTYP) + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_PFGK) + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_PFGKEN) + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_WNV_SECMODE) + ZI_WAIT_RECV(1000, ZBR_WNV_OK) + ZI_SEND(ZBS_W_ZDODCB) + ZI_WAIT_RECV(1000, ZBR_W_OK) + + ZI_SEND(ZBS_WNV_INITZNPHC) + ZI_WAIT_RECV_FUNC(1000, ZBR_WNV_INIT_OK, &Z_CheckNVWrite) + ZI_SEND(ZBS_WNV_ZNPHC) + ZI_WAIT_RECV(1000, ZBR_WNV_OK) + + + ZI_GOTO(ZIGBEE_LABEL_START) + + ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION) + ZI_MQTT_STATE(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported") + ZI_GOTO(ZIGBEE_LABEL_ABORT) + + ZI_LABEL(ZIGBEE_LABEL_ABORT) + ZI_MQTT_STATE(ZIGBEE_STATUS_ABORT, "Abort") + ZI_LOG(LOG_LEVEL_ERROR, D_LOG_ZIGBEE "Abort") + ZI_STOP(ZIGBEE_LABEL_ABORT) +}; + +uint8_t ZigbeeGetInstructionSize(uint8_t instr) { + if (instr >= ZGB_INSTR_12_BYTES) { + return 3; + } else if (instr >= ZGB_INSTR_8_BYTES) { + return 2; + } else { + return 1; + } +} + +void ZigbeeGotoLabel(uint8_t label) { + + uint16_t goto_pc = 0xFFFF; + uint8_t cur_instr = 0; + uint8_t cur_d8 = 0; + uint8_t cur_instr_len = 1; + + for (uint32_t i = 0; i < sizeof(zb_prog)/sizeof(zb_prog[0]); i += cur_instr_len) { + const Zigbee_Instruction *cur_instr_line = &zb_prog[i]; + cur_instr = pgm_read_byte(&cur_instr_line->i.i); + cur_d8 = pgm_read_byte(&cur_instr_line->i.d8); + + + if (ZGB_INSTR_LABEL == cur_instr) { + + if (label == cur_d8) { + + zigbee.pc = i; + zigbee.state_machine = true; + zigbee.state_waiting = false; + return; + } + } + + cur_instr_len = ZigbeeGetInstructionSize(cur_instr); + } + + + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Goto label not found, label=%d pc=%d"), label, zigbee.pc); + if (ZIGBEE_LABEL_ABORT != label) { + + ZigbeeGotoLabel(ZIGBEE_LABEL_ABORT); + } else { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Label Abort (%d) not present, aborting Zigbee"), ZIGBEE_LABEL_ABORT); + zigbee.state_machine = false; + zigbee.active = false; + } +} + +void ZigbeeStateMachine_Run(void) { + uint8_t cur_instr = 0; + uint8_t cur_d8 = 0; + uint16_t cur_d16 = 0; + const void* cur_ptr1 = nullptr; + const void* cur_ptr2 = nullptr; + uint32_t now = millis(); + + if (zigbee.state_waiting) { + + if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) { + + if (!zigbee.state_no_timeout) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "timeout, goto label %d"), zigbee.on_timeout_goto); + ZigbeeGotoLabel(zigbee.on_timeout_goto); + } else { + zigbee.state_waiting = false; + } + } + } + + while ((zigbee.state_machine) && (!zigbee.state_waiting)) { + + zigbee.recv_filter = nullptr; + zigbee.recv_func = nullptr; + zigbee.recv_until = false; + zigbee.state_no_timeout = false; + + if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Invalid pc: %d, aborting"), zigbee.pc); + zigbee.pc = -1; + } + if (zigbee.pc < 0) { + zigbee.state_machine = false; + return; + } + + + + const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc]; + cur_instr = pgm_read_byte(&cur_instr_line->i.i); + cur_d8 = pgm_read_byte(&cur_instr_line->i.d8); + cur_d16 = pgm_read_word(&cur_instr_line->i.d16); + if (cur_instr >= ZGB_INSTR_8_BYTES) { + cur_instr_line++; + cur_ptr1 = cur_instr_line->p; + } + if (cur_instr >= ZGB_INSTR_12_BYTES) { + cur_instr_line++; + cur_ptr2 = cur_instr_line->p; + } + + zigbee.pc += ZigbeeGetInstructionSize(cur_instr); + + switch (cur_instr) { + case ZGB_INSTR_NOOP: + case ZGB_INSTR_LABEL: + break; + case ZGB_INSTR_GOTO: + ZigbeeGotoLabel(cur_d8); + break; + case ZGB_INSTR_ON_ERROR_GOTO: + zigbee.on_error_goto = cur_d8; + break; + case ZGB_INSTR_ON_TIMEOUT_GOTO: + zigbee.on_timeout_goto = cur_d8; + break; + case ZGB_INSTR_WAIT: + zigbee.next_timeout = now + cur_d16; + zigbee.state_waiting = true; + zigbee.state_no_timeout = true; + break; + case ZGB_INSTR_WAIT_FOREVER: + zigbee.next_timeout = 0; + zigbee.state_waiting = true; + + break; + case ZGB_INSTR_STOP: + zigbee.state_machine = false; + if (cur_d8) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Stopping (%d)"), cur_d8); + } + break; + case ZGB_INSTR_CALL: + if (cur_ptr1) { + uint32_t res; + res = (*((ZB_Func)cur_ptr1))(cur_d8); + if (res > 0) { + ZigbeeGotoLabel(res); + continue; + } else if (res == 0) { + + } else if (res == -1) { + + } else { + ZigbeeGotoLabel(zigbee.on_error_goto); + continue; + } + } + break; + case ZGB_INSTR_LOG: + AddLog_P(cur_d8, (char*) cur_ptr1); + break; + case ZGB_INSTR_MQTT_STATE: + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{\"Status\":%d,\"Message\":\"%s\"}}"), + cur_d8, (char*) cur_ptr1); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE)); + XdrvRulesProcess(); + break; + case ZGB_INSTR_SEND: + ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 ); + break; + case ZGB_INSTR_WAIT_UNTIL: + zigbee.recv_until = true; + case ZGB_INSTR_WAIT_RECV: + zigbee.recv_filter = (uint8_t *) cur_ptr1; + zigbee.recv_filter_len = cur_d8; + zigbee.next_timeout = now + cur_d16; + zigbee.state_waiting = true; + break; + case ZGB_ON_RECV_UNEXPECTED: + zigbee.recv_unexpected = (ZB_RecvMsgFunc) cur_ptr1; + break; + case ZGB_INSTR_WAIT_RECV_CALL: + zigbee.recv_filter = (uint8_t *) cur_ptr1; + zigbee.recv_filter_len = cur_d8; + zigbee.recv_func = (ZB_RecvMsgFunc) cur_ptr2; + zigbee.next_timeout = now + cur_d16; + zigbee.state_waiting = true; + break; + } + } +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_8_parsers.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_8_parsers.ino" +#ifdef USE_ZIGBEE + +int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) { + + + + + + + + Z_IEEEAddress long_adr = buf.get64(3); + Z_ShortAddress short_adr = buf.get16(11); + uint8_t device_type = buf.get8(13); + uint8_t device_state = buf.get8(14); + uint8_t device_associated = buf.get8(15); + + + localIEEEAddr = long_adr; + + char hex[20]; + Uint64toHex(long_adr, hex, 64); + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\"" + ",\"DeviceType\":%d,\"DeviceState\":%d" + ",\"NumAssocDevices\":%d"), + ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state, + device_associated); + + if (device_associated > 0) { + uint idx = 16; + ResponseAppend_P(PSTR(",\"AssocDevicesList\":[")); + for (uint32_t i = 0; i < device_associated; i++) { + if (i > 0) { ResponseAppend_P(PSTR(",")); } + ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx)); + idx += 2; + } + ResponseAppend_P(PSTR("]")); + } + + ResponseJsonEnd(); + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE)); + XdrvRulesProcess(); + + return res; +} + +int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) { + + + + uint8_t status = buf.get8(2); + if ((0x00 == status) || (0x09 == status)) { + return 0; + } else { + return -2; + } +} + +const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog"; + +int32_t Z_Reboot(int32_t res, class SBuffer &buf) { + + + + uint8_t reason = buf.get8(2); + uint8_t transport_rev = buf.get8(3); + uint8_t product_id = buf.get8(4); + uint8_t major_rel = buf.get8(5); + uint8_t minor_rel = buf.get8(6); + uint8_t hw_rev = buf.get8(7); + char reason_str[12]; + + if (reason > 3) { reason = 3; } + GetTextIndexed(reason_str, sizeof(reason_str), reason, Z_RebootReason); + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"Message\":\"%s\",\"RestartReason\":\"%s\"" + ",\"MajorRel\":%d,\"MinorRel\":%d}}"), + ZIGBEE_STATUS_BOOT, "CC2530 booted", reason_str, + major_rel, minor_rel); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE)); + XdrvRulesProcess(); + + if ((0x02 == major_rel) && (0x06 == minor_rel)) { + return 0; + } else { + return ZIGBEE_LABEL_UNSUPPORTED_VERSION; + } +} + +int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) { +# 122 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_8_parsers.ino" + uint8_t major_rel = buf.get8(4); + uint8_t minor_rel = buf.get8(5); + uint8_t maint_rel = buf.get8(6); + uint32_t revision = buf.get32(7); + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"MajorRel\":%d,\"MinorRel\":%d" + ",\"MaintRel\":%d,\"Revision\":%d}}"), + ZIGBEE_STATUS_CC_VERSION, major_rel, minor_rel, + maint_rel, revision); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE)); + XdrvRulesProcess(); + + if ((0x02 == major_rel) && (0x06 == minor_rel)) { + return 0; + } else { + return ZIGBEE_LABEL_UNSUPPORTED_VERSION; + } +} + +bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) { + if ( (pgm_read_byte(&match[0]) == buf.get8(0)) && + (pgm_read_byte(&match[1]) == buf.get8(1)) ) { + return true; + } else { + return false; + } +} + +int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) { + + uint8_t duration = buf.get8(2); + uint8_t status_code; + const char* message; + + if (0xFF == duration) { + status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_XX; + message = PSTR("Enable Pairing mode until next boot"); + } else if (duration > 0) { + status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_60; + message = PSTR("Enable Pairing mode for %d seconds"); + } else { + status_code = ZIGBEE_STATUS_PERMITJOIN_CLOSE; + message = PSTR("Disable Pairing mode"); + } + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"Message\":\""), + status_code); + ResponseAppend_P(message, duration); + ResponseAppend_P(PSTR("\"}}")); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE)); + XdrvRulesProcess(); + return -1; +} + + +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)); +} + + +void Z_SendActiveEpReq(uint16_t shortaddr) { + uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, + Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) }; + + ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq)); + + + + + +} + + +void Z_SendSimpleDescReq(uint16_t shortaddr, uint8_t endpoint) { + uint8_t SimpleDescReq[] = { Z_SREQ | Z_ZDO, ZDO_SIMPLE_DESC_REQ, + Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr), + endpoint }; + + ZigbeeZNPSend(SimpleDescReq, sizeof(SimpleDescReq)); +} + +const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" }; +int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) { + + Z_ShortAddress srcAddr = buf.get16(2); + uint8_t status = buf.get8(4); + Z_ShortAddress nwkAddr = buf.get16(5); + uint8_t logicalType = buf.get8(7); + uint8_t apsFlags = buf.get8(8); + uint8_t MACCapabilityFlags = buf.get8(9); + uint16_t manufacturerCapabilities = buf.get16(10); + uint8_t maxBufferSize = buf.get8(12); + uint16_t maxInTransferSize = buf.get16(13); + uint16_t serverMask = buf.get16(15); + uint16_t maxOutTransferSize = buf.get16(17); + uint8_t descriptorCapabilities = buf.get8(19); + + if (0 == status) { + zigbee_devices.updateLastSeen(nwkAddr); + + uint8_t deviceType = logicalType & 0x7; + if (deviceType > 3) { deviceType = 3; } + bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0; + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"NodeType\":\"%s\",\"ComplexDesc\":%s}}"), + ZIGBEE_STATUS_NODE_DESC, Z_DeviceType[deviceType], + complexDescriptorAvailable ? "true" : "false" + ); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + } + + return -1; +} + +int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) { + + Z_ShortAddress srcAddr = buf.get16(2); + uint8_t status = buf.get8(4); + Z_ShortAddress nwkAddr = buf.get16(5); + uint8_t activeEpCount = buf.get8(7); + uint8_t* activeEpList = (uint8_t*) buf.charptr(8); + + + for (uint32_t i = 0; i < activeEpCount; i++) { + zigbee_devices.addEndoint(nwkAddr, activeEpList[i]); + } + + for (uint32_t i = 0; i < activeEpCount; i++) { + Z_SendSimpleDescReq(nwkAddr, activeEpList[i]); + } + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"ActiveEndpoints\":["), + ZIGBEE_STATUS_ACTIVE_EP); + for (uint32_t i = 0; i < activeEpCount; i++) { + if (i > 0) { ResponseAppend_P(PSTR(",")); } + ResponseAppend_P(PSTR("\"0x%02X\""), activeEpList[i]); + } + ResponseAppend_P(PSTR("]}}")); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + return -1; +} + +void Z_SendAFInfoRequest(uint16_t shortaddr, uint8_t endpoint, uint16_t clusterid, uint8_t transacid) { + SBuffer buf(100); + buf.add8(Z_SREQ | Z_AF); + buf.add8(AF_DATA_REQUEST); + buf.add16(shortaddr); + buf.add8(endpoint); + buf.add8(0x01); + buf.add16(clusterid); + buf.add8(transacid); + buf.add8(0x30); + buf.add8(0x1E); + + buf.add8(3 + 2*sizeof(uint16_t)); + buf.add8(0x00); + buf.add8(transacid); + buf.add8(ZCL_READ_ATTRIBUTES); + buf.add16(0x0004); + buf.add16(0x0005); + + ZigbeeZNPSend(buf.getBuffer(), buf.len()); +} + + +int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) { + + Z_ShortAddress srcAddr = buf.get16(2); + uint8_t status = buf.get8(4); + Z_ShortAddress nwkAddr = buf.get16(5); + uint8_t lenDescriptor = buf.get8(7); + uint8_t endpoint = buf.get8(8); + uint16_t profileId = buf.get16(9); + uint16_t deviceId = buf.get16(11); + uint8_t deviceVersion = buf.get8(13); + uint8_t numInCluster = buf.get8(14); + uint8_t numOutCluster = buf.get8(15 + numInCluster*2); + + if (0 == status) { + zigbee_devices.addEndointProfile(nwkAddr, endpoint, profileId); + for (uint32_t i = 0; i < numInCluster; i++) { + zigbee_devices.addCluster(nwkAddr, endpoint, buf.get16(15 + i*2), false); + } + for (uint32_t i = 0; i < numOutCluster; i++) { + zigbee_devices.addCluster(nwkAddr, endpoint, buf.get16(16 + numInCluster*2 + i*2), true); + } + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"Endpoint\":\"0x%02X\"" + ",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVersion\":%d" + "\"InClusters\":["), + ZIGBEE_STATUS_SIMPLE_DESC, endpoint, + profileId, deviceId, deviceVersion); + for (uint32_t i = 0; i < numInCluster; i++) { + if (i > 0) { ResponseAppend_P(PSTR(",")); } + ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(15 + i*2)); + } + ResponseAppend_P(PSTR("],\"OutClusters\":[")); + for (uint32_t i = 0; i < numOutCluster; i++) { + if (i > 0) { ResponseAppend_P(PSTR(",")); } + ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(16 + numInCluster*2 + i*2)); + } + ResponseAppend_P(PSTR("]}}")); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + + uint8_t cluster = zigbee_devices.findClusterEndpointIn(nwkAddr, 0x0000); + if (cluster) { + Z_SendAFInfoRequest(nwkAddr, cluster, 0x0000, 0x01); + } + } + 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); + + + + if (0 == status) { + 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(); + + 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); + Z_IEEEAddress ieeeAddr = buf.get64(6); + uint8_t capabilities = buf.get8(14); + + 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\"" + ",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"), + ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr, + (capabilities & 0x04) ? "true" : "false", + (capabilities & 0x08) ? "true" : "false", + (capabilities & 0x40) ? "true" : "false" + ); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + Z_SendActiveEpReq(nwkAddr); + return -1; +} + + +int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) { + Z_ShortAddress srcAddr = buf.get16(2); + Z_IEEEAddress ieeeAddr = buf.get64(4); + Z_ShortAddress parentNw = buf.get16(12); + + zigbee_devices.updateDevice(srcAddr, ieeeAddr); + + char hex[20]; + Uint64toHex(ieeeAddr, hex, 64); + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\"" + ",\"ParentNetwork\":\"0x%04X\"}}"), + ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw + ); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + + return -1; +} + + + +const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; + +void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, const JsonObject *json) { + + const JsonVariant &val_endpoint = getCaseInsensitive(*json, PSTR(OCCUPANCY)); + if (nullptr != &val_endpoint) { + uint32_t occupancy = strToUInt(val_endpoint); + + if (occupancy) { + zigbee_devices.setTimer(shortaddr, OCCUPANCY_TIMEOUT, cluster, endpoint, 0, &Z_OccupancyCallback); + } + } +} + + + +int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) { + const JsonObject *json = zigbee_devices.jsonGet(shortaddr); + if (json == nullptr) { return 0; } + + Z_AqaraOccupancy(shortaddr, cluster, endpoint, json); + + zigbee_devices.jsonPublishFlush(shortaddr); + return 1; +} + +int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { + uint16_t groupid = buf.get16(2); + uint16_t clusterid = buf.get16(4); + Z_ShortAddress srcaddr = buf.get16(6); + uint8_t srcendpoint = buf.get8(8); + uint8_t dstendpoint = buf.get8(9); + uint8_t wasbroadcast = buf.get8(10); + uint8_t linkquality = buf.get8(11); + uint8_t securityuse = buf.get8(12); + uint32_t timestamp = buf.get32(13); + uint8_t seqnumber = buf.get8(17); + + bool defer_attributes = false; + + zigbee_devices.updateLastSeen(srcaddr); + ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid, + srcaddr, + srcendpoint, dstendpoint, wasbroadcast, + linkquality, securityuse, seqnumber, + timestamp); + zcl_received.log(); + char shortaddr[8]; + snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr); + + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + + if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { + zcl_received.parseRawAttributes(json); + if (clusterid) { defer_attributes = true; } + } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) { + zcl_received.parseReadAttributes(json); + } else if (zcl_received.isClusterSpecificCommand()) { + zcl_received.parseClusterSpecificCommand(json); + } + String msg(""); + msg.reserve(100); + json.printTo(msg); + 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); + + json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint; + + json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality; + + if (defer_attributes) { + + if (zigbee_devices.jsonIsConflict(srcaddr, json)) { + + zigbee_devices.jsonPublishFlush(srcaddr); + } else { + zigbee_devices.jsonAppend(srcaddr, json); + zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes); + } + } else { + + zigbee_devices.jsonPublishNow(srcaddr, json); + } + return -1; +} + +typedef struct Z_Dispatcher { + const uint8_t* match; + ZB_RecvMsgFunc func; +} Z_Dispatcher; + + +ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) +ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) +ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) +ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) +ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) +ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) +ZBM(AREQ_ZDO_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) + +const Z_Dispatcher Z_DispatchTable[] PROGMEM = { + { AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage }, + { AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce }, + { AREQ_END_DEVICE_TC_DEV_IND, &Z_ReceiveTCDevInd }, + { AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus }, + { 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) { + + if (zigbee.init_phase) { + + return -1; + } else { + for (uint32_t i = 0; i < sizeof(Z_DispatchTable)/sizeof(Z_Dispatcher); i++) { + if (Z_ReceiveMatchPrefix(buf, Z_DispatchTable[i].match)) { + (*Z_DispatchTable[i].func)(res, buf); + } + } + return -1; + } +} + +int32_t Z_Load_Devices(uint8_t value) { + + loadZigbeeDevices(); + return 0; +} + +int32_t Z_State_Ready(uint8_t value) { + zigbee.init_phase = false; + return 0; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino" +#ifdef USE_ZIGBEE + +#define XDRV_23 23 + +const uint32_t ZIGBEE_BUFFER_SIZE = 256; +const uint8_t ZIGBEE_SOF = 0xFE; +const uint8_t ZIGBEE_SOF_ALT = 0xFF; + +#include +TasmotaSerial *ZigbeeSerial = nullptr; + + +const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" + 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_PING ; + +const char kZigbeeCommands[] PROGMEM = D_PRFX_ZIGBEE "|" + 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_PING ; + +void (* const ZigbeeCommand[])(void) PROGMEM = { + &CmndZbZNPSend, &CmndZbPermitJoin, + &CmndZbStatus, &CmndZbReset, &CmndZbSend, + &CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive, + &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind, + &CmndZbPing, + }; + +int32_t ZigbeeProcessInput(class SBuffer &buf) { + if (!zigbee.state_machine) { return -1; } + + + bool recv_filter_match = true; + bool recv_prefix_match = false; + if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) { + if (zigbee.recv_filter_len >= 2) { + recv_prefix_match = false; + if ( (pgm_read_byte(&zigbee.recv_filter[0]) == buf.get8(0)) && + (pgm_read_byte(&zigbee.recv_filter[1]) == buf.get8(1)) ) { + recv_prefix_match = true; + } + } + + for (uint32_t i = 0; i < zigbee.recv_filter_len; i++) { + if (pgm_read_byte(&zigbee.recv_filter[i]) != buf.get8(i)) { + recv_filter_match = false; + break; + } + } + + + } + + + int32_t res = -1; + + + + + + if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) { + if (!recv_prefix_match) { + res = -1; + } else { + if (recv_filter_match) { + res = 0; + } else { + if (zigbee.recv_until) { + res = -1; + } else { + res = -2; + } + } + } + } else { + res = -1; + } + + if (recv_prefix_match) { + if (zigbee.recv_func) { + res = (*zigbee.recv_func)(res, buf); + } + } + if (-1 == res) { + + if (zigbee.recv_unexpected) { + res = (*zigbee.recv_unexpected)(res, buf); + } + } + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZbProcessInput: res = %d"), res); + + + if (0 == res) { + + zigbee.state_waiting = false; + } else if (res > 0) { + ZigbeeGotoLabel(res); + } else if (-1 == res) { + + + } else { + + ZigbeeGotoLabel(zigbee.on_error_goto); + } +} + +void ZigbeeInput(void) +{ + static uint32_t zigbee_polling_window = 0; + static uint8_t fcs = ZIGBEE_SOF; + static uint32_t zigbee_frame_len = 5; +# 145 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino" + while (ZigbeeSerial->available()) { + yield(); + uint8_t zigbee_in_byte = ZigbeeSerial->read(); + + + if (0 == zigbee_buffer->len()) { + zigbee_frame_len = 5; + fcs = ZIGBEE_SOF; + + + + if (ZIGBEE_SOF_ALT == zigbee_in_byte) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput forgiven first byte %02X (only for statistics)"), zigbee_in_byte); + zigbee_in_byte = ZIGBEE_SOF; + } + } + + if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) { + + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput discarding byte %02X"), zigbee_in_byte); + continue; + } + + if (zigbee_buffer->len() < zigbee_frame_len) { + zigbee_buffer->add8(zigbee_in_byte); + zigbee_polling_window = millis(); + fcs ^= zigbee_in_byte; + } + + if (zigbee_buffer->len() >= zigbee_frame_len) { + zigbee_polling_window = 0; + break; + } + + + if (02 == zigbee_buffer->len()) { + + uint8_t len_byte = zigbee_buffer->get8(1); + if (len_byte > 250) len_byte = 250; + + zigbee_frame_len = len_byte + 5; + } + } + + if (zigbee_buffer->len() && (millis() > (zigbee_polling_window + ZIGBEE_POLLING))) { + char hex_char[(zigbee_buffer->len() * 2) + 2]; + ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char)); + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric()); + + if (zigbee_buffer->len() != zigbee_frame_len) { + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received frame of wrong size %s, len %d, expected %d"), hex_char, zigbee_buffer->len(), zigbee_frame_len); + } else if (0x00 != fcs) { + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received bad FCS frame %s, %d"), hex_char, fcs); + } else { + + + + SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); + + ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char)); + Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char); + if (Settings.flag3.tuya_serial_mqtt_publish) { + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); + } else { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data); + } + + ZigbeeProcessInput(znp_buffer); + } + zigbee_buffer->setLen(0); + } +} + + + +void ZigbeeInit(void) +{ + zigbee.active = false; + if ((pin[GPIO_ZIGBEE_RX] < 99) && (pin[GPIO_ZIGBEE_TX] < 99)) { + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "GPIOs Rx:%d Tx:%d"), pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX]); + + ZigbeeSerial = new TasmotaSerial(pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX], seriallog_level ? 1 : 2, 0, 256); + ZigbeeSerial->begin(115200); + if (ZigbeeSerial->hardwareSerial()) { + ClaimSerial(); + uint32_t aligned_buffer = ((uint32_t)serial_in_buffer + 3) & ~3; + zigbee_buffer = new PreAllocatedSBuffer(sizeof(serial_in_buffer) - 3, (char*) aligned_buffer); + } else { + zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE); + } + zigbee.active = true; + zigbee.init_phase = true; + zigbee.state_machine = true; + ZigbeeSerial->flush(); + } +} + + + + + +uint32_t strToUInt(const JsonVariant &val) { + + if (val.is()) { + return val.as(); + } else { + if (val.is()) { + String sval = val.as(); + return strtoull(sval.c_str(), nullptr, 0); + } + } + return 0; +} + +const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM = + { Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 , 0x01 }; + + +void CmndZbReset(void) { + if (ZigbeeSerial) { + switch (XdrvMailbox.payload) { + case 1: + ZigbeeZNPSend(ZIGBEE_FACTORY_RESET, sizeof(ZIGBEE_FACTORY_RESET)); + eraseZigbeeDevices(); + restart_flag = 2; + ResponseCmndChar(D_JSON_ZIGBEE_CC2530 " " D_JSON_RESET_AND_RESTARTING); + break; + default: + ResponseCmndChar(D_JSON_ONE_TO_RESET); + } + } +} + +void CmndZbZNPSendOrReceive(bool send) +{ + if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) { + uint8_t code; + + char *codes = RemoveSpace(XdrvMailbox.data); + int32_t size = strlen(XdrvMailbox.data); + + SBuffer buf((size+1)/2); + + while (size > 1) { + char stemp[3]; + strlcpy(stemp, codes, sizeof(stemp)); + code = strtol(stemp, nullptr, 16); + buf.add8(code); + size -= 2; + codes += 2; + } + if (send) { + ZigbeeZNPSend(buf.getBuffer(), buf.len()); + } else { + ZigbeeProcessInput(buf); + } + } + ResponseCmndDone(); +} + + +void CmndZbZNPReceive(void) +{ + CmndZbZNPSendOrReceive(false); +} + +void CmndZbZNPSend(void) +{ + CmndZbZNPSendOrReceive(true); +} + +void ZigbeeZNPSend(const uint8_t *msg, size_t len) { + if ((len < 2) || (len > 252)) { + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPSENT ": bad message len %d"), len); + return; + } + uint8_t data_len = len - 2; + + if (ZigbeeSerial) { + uint8_t fcs = data_len; + + ZigbeeSerial->write(ZIGBEE_SOF); + + ZigbeeSerial->write(data_len); + + for (uint32_t i = 0; i < len; i++) { + uint8_t b = pgm_read_byte(msg + i); + ZigbeeSerial->write(b); + fcs ^= b; + + } + ZigbeeSerial->write(fcs); + + } + + char hex_char[(len * 2) + 2]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"), + 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 needResponse, uint8_t transacId) { + SBuffer buf(25+len); + buf.add8(Z_SREQ | Z_AF); + buf.add8(AF_DATA_REQUEST); + buf.add16(dtsAddr); + buf.add8(endpoint); + buf.add8(0x01); + buf.add16(clusterId); + buf.add8(transacId); + buf.add8(0x30); + buf.add8(0x1E); + + buf.add8(3 + len); + buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00)); + buf.add8(transacId); + buf.add8(cmdId); + if (len > 0) { + buf.addBuffer(msg, len); + } + + ZigbeeZNPSend(buf.getBuffer(), buf.len()); +} + +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); + + if (param) { + while (*param) { + uint8_t code = parseHex_P(¶m, 2); + buf.add8(code); + } + } + + if (0 == endpoint) { + + endpoint = zigbee_devices.findClusterEndpointIn(dstAddr, cluster); + 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, param); + + if (0 == endpoint) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint")); + return; + } + + + ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len(), false, zigbee_devices.getNextSeqNumber(dstAddr)); + + if (clusterSpecific) { + zigbeeSetCommandTimer(dstAddr, cluster, endpoint); + } + ResponseCmndDone(); +} + +void CmndZbSend(void) { +# 419 "S:/Development/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino" + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + DynamicJsonBuffer jsonBuf; + JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data); + if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; } + + + static char delim[] = ", "; + uint16_t device = 0xFFFF; + uint8_t endpoint = 0x00; + + uint16_t cluster = 0; + uint8_t cmd = 0; + String cmd_str = ""; + + + const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); + if (nullptr != &val_device) { + device = zigbee_devices.parseDeviceParam(val_device.as()); + if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; } + } + if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; } + + const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint")); + if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } + const JsonVariant &val_cmd = getCaseInsensitive(json, PSTR("Send")); + if (nullptr != &val_cmd) { + + + + if (val_cmd.is()) { + + JsonObject &cmd_obj = val_cmd.as(); + int32_t cmd_size = cmd_obj.size(); + if (cmd_size > 1) { + Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size); + return; + } else if (1 == cmd_size) { + + JsonObject::iterator it = cmd_obj.begin(); + 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(), &cluster, &cmd_var); + if (tasmota_cmd) { + cmd_str = tasmota_cmd; + } else { + Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str()); + return; + } + + + if (value.is()) { + x = value.as() ? 1 : 0; + } else if (value.is()) { + x = value.as(); + } else { + + const char *s_const = value.as(); + if (s_const != nullptr) { + char s[strlen(s_const)+1]; + strcpy(s, s_const); + if ((nullptr != s) && (0x00 != *s)) { + char *sval = strtok(s, delim); + if (sval) { + x = ZigbeeAliasOrNumber(sval); + sval = strtok(nullptr, delim); + if (sval) { + y = ZigbeeAliasOrNumber(sval); + sval = strtok(nullptr, delim); + if (sval) { + z = ZigbeeAliasOrNumber(sval); + } + } + } + } + } + } + + + if (0xFF == cmd_var) { + cmd = x; + x = y; + y = z; + } else { + cmd = cmd_var; + } + cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); + + } else { + + } + } else if (val_cmd.is()) { + + cmd_str = val_cmd.as(); + } else { + + } + + 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; + } + +} + +ZBM(ZBS_BIND_REQ, Z_SREQ | Z_ZDO, ZDO_BIND_REQ, + 0,0, + 0,0,0,0,0,0,0,0, + 0x00, + 0x00, 0x00, + 0x03, + 0,0,0,0,0,0,0,0, + 0x01 +) + +void CmndZbBind(void) { + + + + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + DynamicJsonBuffer jsonBuf; + JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data); + if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; } + + + + uint16_t device = 0xFFFF; + uint8_t endpoint = 0x00; + uint16_t cluster = 0; + uint32_t group = 0xFFFFFFFF; + + const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); + if (nullptr != &val_device) { + device = zigbee_devices.parseDeviceParam(val_device.as()); + if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; } + } + if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; } + + const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint")); + if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } + const JsonVariant &val_cluster = getCaseInsensitive(json, PSTR("Cluster")); + if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); } + + + + SBuffer buf(sizeof(ZBS_BIND_REQ)); + buf.add8(Z_SREQ | Z_ZDO); + buf.add8(ZDO_BIND_REQ); + buf.add16(device); + buf.add64(zigbee_devices.getDeviceLongAddr(device)); + buf.add8(endpoint); + buf.add16(cluster); + buf.add8(0x03); + buf.add64(localIEEEAddr); + buf.add8(0x01); + + ZigbeeZNPSend(buf.getBuffer(), buf.len()); + + ResponseCmndDone(); +} + + +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; } + + + Z_SendIEEEAddrReq(shortaddr); + if (probe) { + Z_SendActiveEpReq(shortaddr); + } + ResponseCmndDone(); +} + + +void CmndZbPing(void) { + CmndZbProbeOrPing(false); +} + + +void CmndZbName(void) { + + + + + + + + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + + + char *p; + char *str = strtok_r(XdrvMailbox.data, ", ", &p); + + + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } + + if (p == nullptr) { + const String * friendlyName = zigbee_devices.getFriendlyName(shortaddr); + Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), shortaddr, friendlyName ? friendlyName->c_str() : ""); + } else { + zigbee_devices.setFriendlyName(shortaddr, p); + Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), shortaddr, p); + } +} + + +void CmndZbForget(void) { + 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; } + + + if (zigbee_devices.removeDevice(shortaddr)) { + ResponseCmndDone(); + } else { + ResponseCmndChar("Unknown device"); + } +} + + +void CmndZbSave(void) { + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + + saveZigbeeDevices(); + + ResponseCmndDone(); +} + + +void CmndZbRead(void) { + + + + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + DynamicJsonBuffer jsonBuf; + JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data); + if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; } + + + uint16_t device = 0xFFFF; + uint16_t cluster = 0x0000; + uint8_t endpoint = 0x00; + size_t attrs_len = 0; + uint8_t* attrs = nullptr; + + const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); + if (nullptr != &val_device) { + device = zigbee_devices.parseDeviceParam(val_device.as()); + if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; } + } + if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; } + + const JsonVariant &val_cluster = getCaseInsensitive(json, PSTR("Cluster")); + if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); } + const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint")); + if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } + + const JsonVariant &val_attr = getCaseInsensitive(json, PSTR("Read")); + if (nullptr != &val_attr) { + uint16_t val = strToUInt(val_attr); + if (val_attr.is()) { + JsonArray& attr_arr = val_attr; + attrs_len = attr_arr.size() * 2; + attrs = new uint8_t[attrs_len]; + + uint32_t i = 0; + for (auto value : attr_arr) { + uint16_t val = strToUInt(value); + attrs[i++] = val & 0xFF; + attrs[i++] = val >> 8; + } + } else { + attrs_len = 2; + attrs = new uint8_t[attrs_len]; + attrs[0] = val & 0xFF; + attrs[1] = val >> 8; + } + } + + if (0 == 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, true , zigbee_devices.getNextSeqNumber(device)); + ResponseCmndDone(); + } else { + ResponseCmndChar("Missing parameters"); + } + + if (attrs) { delete[] attrs; } +} + + +void CmndZbPermitJoin(void) +{ + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + uint32_t payload = XdrvMailbox.payload; + if (payload < 0) { payload = 0; } + if ((99 != payload) && (payload > 1)) { payload = 1; } + + if (1 == payload) { + ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60); + } else if (99 == payload){ + ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX); + } else { + ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE); + } + ResponseCmndDone(); +} + +void CmndZbStatus(void) { + if (ZigbeeSerial) { + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } + if (XdrvMailbox.payload > 0) { + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + } + + String dump = zigbee_devices.dump(XdrvMailbox.index, shortaddr); + Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.index, dump.c_str()); + } +} + + + + + +bool Xdrv23(uint8_t function) +{ + bool result = false; + + if (zigbee.active) { + switch (function) { + case FUNC_EVERY_50_MSECOND: + if (!zigbee.init_phase) { + zigbee_devices.runTimer(); + } + break; + case FUNC_LOOP: + if (ZigbeeSerial) { ZigbeeInput(); } + if (zigbee.state_machine) { + + ZigbeeStateMachine_Run(); + } + break; + case FUNC_PRE_INIT: + ZigbeeInit(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kZbCommands, ZigbeeCommand); + result = result || DecodeCommand(kZigbeeCommands, ZigbeeCommand); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_24_buzzer.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_24_buzzer.ino" +#ifdef USE_BUZZER + + + + +#define XDRV_24 24 + +struct BUZZER { + uint32_t tune = 0; + uint32_t tune_reload = 0; + bool active = true; + bool enable = false; + uint8_t inverted = 0; + uint8_t count = 0; + uint8_t mode = 0; + uint8_t set[2]; + uint8_t duration; + uint8_t state = 0; +} Buzzer; + + + +void BuzzerOff(void) +{ + DigitalWrite(GPIO_BUZZER, Buzzer.inverted); +} + + +void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune, uint32_t mode) +{ + Buzzer.set[0] = off; + Buzzer.set[1] = on; + Buzzer.duration = 1; + Buzzer.tune_reload = 0; + Buzzer.mode = mode; + + if (tune) { + uint32_t tune1 = tune; + uint32_t tune2 = tune; + for (uint32_t i = 0; i < 32; i++) { + if (!(tune2 & 0x80000000)) { + tune2 <<= 1; + } else { + Buzzer.tune_reload <<= 1; + Buzzer.tune_reload |= tune1 & 1; + tune1 >>= 1; + } + } + Buzzer.tune = Buzzer.tune_reload; + } + Buzzer.count = count * 2; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BUZ: %d(%d),%d,%d,0x%08X(0x%08X)"), count, Buzzer.count, on, off, tune, Buzzer.tune); + + Buzzer.enable = (Buzzer.count > 0); + if (!Buzzer.enable) { + BuzzerOff(); + } +} + +void BuzzerSetStateToLed(uint32_t state) +{ + if (Buzzer.enable && (2 == Buzzer.mode)) { + Buzzer.state = (state != 0); + DigitalWrite(GPIO_BUZZER, (Buzzer.inverted) ? !Buzzer.state : Buzzer.state); + } +} + +void BuzzerBeep(uint32_t count) +{ + BuzzerBeep(count, 1, 1, 0, 0); +} + +void BuzzerEnabledBeep(uint32_t count, uint32_t duration) +{ + if (Settings.flag3.buzzer_enable) { + BuzzerBeep(count, duration, 1, 0, 0); + } +} + + + +bool BuzzerPinState(void) +{ + if (XdrvMailbox.index == GPIO_BUZZER_INV) { + Buzzer.inverted = 1; + XdrvMailbox.index -= (GPIO_BUZZER_INV - GPIO_BUZZER); + return true; + } + return false; +} + +void BuzzerInit(void) +{ + if (pin[GPIO_BUZZER] < 99) { + pinMode(pin[GPIO_BUZZER], OUTPUT); + BuzzerOff(); + } else { + Buzzer.active = false; + } +} + +void BuzzerEvery100mSec(void) +{ + if (Buzzer.enable && (Buzzer.mode != 2)) { + if (Buzzer.count) { + if (Buzzer.duration) { + Buzzer.duration--; + if (!Buzzer.duration) { + if (Buzzer.tune) { + Buzzer.state = Buzzer.tune & 1; + Buzzer.tune >>= 1; + } else { + Buzzer.tune = Buzzer.tune_reload; + Buzzer.count -= (Buzzer.tune_reload) ? 2 : 1; + Buzzer.state = Buzzer.count & 1; + if (Buzzer.mode) { + Buzzer.count |= 2; + } + } + Buzzer.duration = Buzzer.set[Buzzer.state]; + } + } + DigitalWrite(GPIO_BUZZER, (Buzzer.inverted) ? !Buzzer.state : Buzzer.state); + } else { + Buzzer.enable = false; + } + } +} + + + + + +const char kBuzzerCommands[] PROGMEM = "|" + "Buzzer" ; + +void (* const BuzzerCommand[])(void) PROGMEM = { + &CmndBuzzer }; + +void CmndBuzzer(void) +{ +# 174 "S:/Development/Tasmota/tasmota/xdrv_24_buzzer.ino" + if (XdrvMailbox.data_len > 0) { + if (XdrvMailbox.payload != 0) { + uint32_t parm[4] = { 0 }; + uint32_t mode = 0; + ParseParameters(4, parm); + if (XdrvMailbox.payload <= 0) { + parm[0] = 1; + mode = -XdrvMailbox.payload; + } + for (uint32_t i = 1; i < 3; i++) { + if (parm[i] < 1) { parm[i] = 1; } + } + BuzzerBeep(parm[0], parm[1], parm[2], parm[3], mode); + } else { + BuzzerBeep(0); + } + } else { + BuzzerBeep(1); + } + ResponseCmndDone(); +} + + + + + +bool Xdrv24(uint8_t function) +{ + bool result = false; + + if (Buzzer.active) { + switch (function) { + case FUNC_EVERY_100_MSECOND: + BuzzerEvery100mSec(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kBuzzerCommands, BuzzerCommand); + break; + case FUNC_PRE_INIT: + BuzzerInit(); + break; + case FUNC_PIN_STATE: + result = BuzzerPinState(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_25_A4988_Stepper.ino" +# 21 "S:/Development/Tasmota/tasmota/xdrv_25_A4988_Stepper.ino" +#ifdef USE_A4988_STEPPER + + + + +#define XDRV_25 25 + +#include + +short A4988_dir_pin = pin[GPIO_MAX]; +short A4988_stp_pin = pin[GPIO_MAX]; +short A4988_ms1_pin = pin[GPIO_MAX]; +short A4988_ms2_pin = pin[GPIO_MAX]; +short A4988_ms3_pin = pin[GPIO_MAX]; +short A4988_ena_pin = pin[GPIO_MAX]; +int A4988_spr = 0; +float A4988_rpm = 0; +short A4988_mis = 0; + +A4988_Stepper* myA4988 = nullptr; + +void A4988Init(void) +{ + A4988_dir_pin = pin[GPIO_A4988_DIR]; + A4988_stp_pin = pin[GPIO_A4988_STP]; + A4988_ena_pin = pin[GPIO_A4988_ENA]; + A4988_ms1_pin = pin[GPIO_A4988_MS1]; + A4988_ms2_pin = pin[GPIO_A4988_MS2]; + A4988_ms3_pin = pin[GPIO_A4988_MS3]; + A4988_spr = 200; + A4988_rpm = 30; + A4988_mis = 1; + + myA4988 = new A4988_Stepper( A4988_spr + , A4988_rpm + , A4988_mis + , A4988_dir_pin + , A4988_stp_pin + , A4988_ena_pin + , A4988_ms1_pin + , A4988_ms2_pin + , A4988_ms3_pin ); +} + +const char kA4988Commands[] PROGMEM = "Motor|" + "Move|Rotate|Turn|MIS|SPR|RPM"; + +void (* const A4988Command[])(void) PROGMEM = { + &CmndDoMove,&CmndDoRotate,&CmndDoTurn,&CmndSetMIS,&CmndSetSPR,&CmndSetRPM}; + +void CmndDoMove(void) { + if (XdrvMailbox.data_len > 0) { + long stepsPlease = strtoul(XdrvMailbox.data,nullptr,10); + myA4988->doMove(stepsPlease); + ResponseCmndDone(); + } +} + +void CmndDoRotate(void) { + if (XdrvMailbox.data_len > 0) { + long degrsPlease = strtoul(XdrvMailbox.data,nullptr,10); + myA4988->doRotate(degrsPlease); + ResponseCmndDone(); + } +} + +void CmndDoTurn(void) { + if (XdrvMailbox.data_len > 0) { + float turnsPlease = strtod(XdrvMailbox.data,nullptr); + myA4988->doTurn(turnsPlease); + ResponseCmndDone(); + } +} + +void CmndSetMIS(void) { + if ((pin[GPIO_A4988_MS1] < 99) && (pin[GPIO_A4988_MS2] < 99) && (pin[GPIO_A4988_MS3] < 99) && (XdrvMailbox.data_len > 0)) { + short newMIS = strtoul(XdrvMailbox.data,nullptr,10); + myA4988->setMIS(newMIS); + ResponseCmndDone(); + } +} + +void CmndSetSPR(void) { + if (XdrvMailbox.data_len > 0) { + int newSPR = strtoul(XdrvMailbox.data,nullptr,10); + myA4988->setSPR(newSPR); + ResponseCmndDone(); + } +} + +void CmndSetRPM(void) { + if (XdrvMailbox.data_len > 0) { + short newRPM = strtoul(XdrvMailbox.data,nullptr,10); + myA4988->setRPM(newRPM); + ResponseCmndDone(); + } +} + + + + +bool Xdrv25(uint8_t function) +{ + bool result = false; + if ((pin[GPIO_A4988_DIR] < 99) && (pin[GPIO_A4988_STP] < 99)) { + switch (function) { + case FUNC_INIT: + A4988Init(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kA4988Commands, A4988Command); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_26_ariluxrf.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_26_ariluxrf.ino" +#ifdef USE_LIGHT +#ifdef USE_ARILUX_RF + + + + +#define XDRV_26 26 + +const uint32_t ARILUX_RF_TIME_AVOID_DUPLICATE = 1000; + +const uint8_t ARILUX_RF_MAX_CHANGES = 51; +const uint32_t ARILUX_RF_SEPARATION_LIMIT = 4300; +const uint32_t ARILUX_RF_RECEIVE_TOLERANCE = 60; + +struct ARILUX { + unsigned int rf_timings[ARILUX_RF_MAX_CHANGES]; + + unsigned long rf_received_value = 0; + unsigned long rf_last_received_value = 0; + unsigned long rf_last_time = 0; + unsigned long rf_lasttime = 0; + + unsigned int rf_change_count = 0; + unsigned int rf_repeat_count = 0; + + uint8_t rf_toggle = 0; +} Arilux; + +#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +#ifndef USE_WS2812_DMA +void AriluxRfInterrupt(void) ICACHE_RAM_ATTR; +#endif +#endif + +void AriluxRfInterrupt(void) +{ + unsigned long time = micros(); + unsigned int duration = time - Arilux.rf_lasttime; + + if (duration > ARILUX_RF_SEPARATION_LIMIT) { + if (abs(duration - Arilux.rf_timings[0]) < 200) { + Arilux.rf_repeat_count++; + if (Arilux.rf_repeat_count == 2) { + unsigned long code = 0; + const unsigned int delay = Arilux.rf_timings[0] / 31; + const unsigned int delayTolerance = delay * ARILUX_RF_RECEIVE_TOLERANCE / 100; + for (unsigned int i = 1; i < Arilux.rf_change_count -1; i += 2) { + code <<= 1; + if (abs(Arilux.rf_timings[i] - (delay *3)) < delayTolerance && abs(Arilux.rf_timings[i +1] - delay) < delayTolerance) { + code |= 1; + } + } + if (Arilux.rf_change_count > 49) { + Arilux.rf_received_value = code; + } + Arilux.rf_repeat_count = 0; + } + } + Arilux.rf_change_count = 0; + } + if (Arilux.rf_change_count >= ARILUX_RF_MAX_CHANGES) { + Arilux.rf_change_count = 0; + Arilux.rf_repeat_count = 0; + } + Arilux.rf_timings[Arilux.rf_change_count++] = duration; + Arilux.rf_lasttime = time; +} + +void AriluxRfHandler(void) +{ + unsigned long now = millis(); + if (Arilux.rf_received_value && !((Arilux.rf_received_value == Arilux.rf_last_received_value) && (now - Arilux.rf_last_time < ARILUX_RF_TIME_AVOID_DUPLICATE))) { + Arilux.rf_last_received_value = Arilux.rf_received_value; + Arilux.rf_last_time = now; + + uint16_t hostcode = Arilux.rf_received_value >> 8 & 0xFFFF; + if (Settings.rf_code[1][6] == Settings.rf_code[1][7]) { + Settings.rf_code[1][6] = hostcode >> 8 & 0xFF; + Settings.rf_code[1][7] = hostcode & 0xFF; + } + uint16_t stored_hostcode = Settings.rf_code[1][6] << 8 | Settings.rf_code[1][7]; + + DEBUG_DRIVER_LOG(PSTR(D_LOG_RFR D_HOST D_CODE " 0x%04X, " D_RECEIVED " 0x%06X"), stored_hostcode, Arilux.rf_received_value); + + if (hostcode == stored_hostcode) { + char command[33]; + char value = '-'; + command[0] = '\0'; + uint8_t keycode = Arilux.rf_received_value & 0xFF; + switch (keycode) { + case 1: + case 3: + snprintf_P(command, sizeof(command), PSTR(D_CMND_POWER " %d"), (1 == keycode) ? 1 : 0); + break; + case 2: + Arilux.rf_toggle++; + Arilux.rf_toggle &= 0x3; + snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), 200 + Arilux.rf_toggle); + break; + case 4: + value = '+'; + case 7: + snprintf_P(command, sizeof(command), PSTR(D_CMND_SPEED " %c"), value); + break; + case 5: + value = '+'; + case 8: + snprintf_P(command, sizeof(command), PSTR(D_CMND_SCHEME " %c"), value); + break; + case 6: + value = '+'; + case 9: + snprintf_P(command, sizeof(command), PSTR(D_CMND_DIMMER " %c"), value); + break; + default: { + if ((keycode >= 10) && (keycode <= 21)) { + snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), keycode -9); + } + } + } + if (strlen(command)) { + ExecuteCommand(command, SRC_LIGHT); + } + } + } + Arilux.rf_received_value = 0; +} + +void AriluxRfInit(void) +{ + if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_ARIRFSEL] < 99)) { + if (Settings.last_module != Settings.module) { + Settings.rf_code[1][6] = 0; + Settings.rf_code[1][7] = 0; + Settings.last_module = Settings.module; + } + Arilux.rf_received_value = 0; + + digitalWrite(pin[GPIO_ARIRFSEL], 0); + attachInterrupt(pin[GPIO_ARIRFRCV], AriluxRfInterrupt, CHANGE); + } +} + +void AriluxRfDisable(void) +{ + if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_ARIRFSEL] < 99)) { + detachInterrupt(pin[GPIO_ARIRFRCV]); + digitalWrite(pin[GPIO_ARIRFSEL], 1); + } +} + + + + + +bool Xdrv26(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_50_MSECOND: + if (pin[GPIO_ARIRFRCV] < 99) { AriluxRfHandler(); } + break; + case FUNC_EVERY_SECOND: + if (10 == uptime) { AriluxRfInit(); } + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_27_shutter.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_27_shutter.ino" +#ifdef USE_SHUTTER + + + + +#define XDRV_27 27 + +#define D_SHUTTER "SHUTTER" + +const uint16_t MOTOR_STOP_TIME = 500; +const uint8_t steps_per_second = 20; + +uint8_t calibrate_pos[6] = {0,30,50,70,90,100}; +uint16_t messwerte[5] = {30,50,70,90,100}; +uint16_t last_execute_step; + +enum ShutterModes { SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE, SHT_OFF_ON__OPEN_CLOSE_STEPPER,}; +enum ShutterButtonStates { SHT_NOT_PRESSED, SHT_PRESSED_MULTI, SHT_PRESSED_HOLD, SHT_PRESSED_IMMEDIATE, SHT_PRESSED_MULTI_SIMULTANEOUS, SHT_PRESSED_HOLD_SIMULTANEOUS, SHT_PRESSED_EXT_HOLD_SIMULTANEOUS,}; + +const char kShutterCommands[] PROGMEM = D_PRFX_SHUTTER "|" + D_CMND_SHUTTER_OPEN "|" D_CMND_SHUTTER_CLOSE "|" D_CMND_SHUTTER_STOP "|" D_CMND_SHUTTER_POSITION "|" + D_CMND_SHUTTER_OPENTIME "|" D_CMND_SHUTTER_CLOSETIME "|" D_CMND_SHUTTER_RELAY "|" + D_CMND_SHUTTER_SETHALFWAY "|" D_CMND_SHUTTER_SETCLOSE "|" D_CMND_SHUTTER_INVERT "|" D_CMND_SHUTTER_CLIBRATION "|" + D_CMND_SHUTTER_MOTORDELAY "|" D_CMND_SHUTTER_FREQUENCY "|" D_CMND_SHUTTER_BUTTON "|" D_CMND_SHUTTER_LOCK "|" D_CMND_SHUTTER_ENABLEENDSTOPTIME; + +void (* const ShutterCommand[])(void) PROGMEM = { + &CmndShutterOpen, &CmndShutterClose, &CmndShutterStop, &CmndShutterPosition, + &CmndShutterOpenTime, &CmndShutterCloseTime, &CmndShutterRelay, + &CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay, + &CmndShutterFrequency, &CmndShutterButton, &CmndShutterLock, &CmndShutterEnableEndStopTime}; + + 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 + +Ticker TickerShutter; + +struct SHUTTER { + power_t mask = 0; + power_t old_power = 0; + power_t switched_relay = 0; + uint32_t time[MAX_SHUTTERS]; + int32_t open_max[MAX_SHUTTERS]; + int32_t target_position[MAX_SHUTTERS]; + int32_t start_position[MAX_SHUTTERS]; + int32_t real_position[MAX_SHUTTERS]; + uint16_t open_time[MAX_SHUTTERS]; + uint16_t close_time[MAX_SHUTTERS]; + uint16_t close_velocity[MAX_SHUTTERS]; + int8_t direction[MAX_SHUTTERS]; + uint8_t mode = 0; + int16_t motordelay[MAX_SHUTTERS]; + int16_t pwm_frequency[MAX_SHUTTERS]; + uint16_t max_pwm_frequency = 1000; + uint16_t max_close_pwm_frequency[MAX_SHUTTERS]; + uint8_t skip_relay_change; + int32_t accelerator[MAX_SHUTTERS]; +} Shutter; + +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]); +} + +void ShutterRtc50mS(void) +{ + for (uint8_t i = 0; i < shutters_present; i++) { + Shutter.time[i]++; + if (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); + } + } +} + +#define SHT_DIV_ROUND(__A,__B) (((__A) + (__B)/2) / (__B)) + +int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index) +{ + if (Settings.shutter_set50percent[index] != 50) { + return (percent <= 5) ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index]; + } else { + uint32_t realpos; + + for (uint32_t j = 0; j < 5; j++) { + if (0 == Settings.shuttercoeff[j][index]) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SHT: RESET/INIT CALIBRATION MATRIX DIV 0")); + for (uint32_t k = 0; k < 5; k++) { + Settings.shuttercoeff[k][index] = SHT_DIV_ROUND(calibrate_pos[k+1] * 1000, calibrate_pos[5]); + } + } + } + for (uint32_t i = 0; i < 5; i++) { + if ((percent * 10) >= Settings.shuttercoeff[i][index]) { + realpos = SHT_DIV_ROUND(Shutter.open_max[index] * calibrate_pos[i+1], 100); + + } else { + if (0 == i) { + realpos = SHT_DIV_ROUND(SHT_DIV_ROUND(percent * Shutter.open_max[index] * calibrate_pos[i+1], Settings.shuttercoeff[i][index]), 10); + } else { + + + realpos += SHT_DIV_ROUND(SHT_DIV_ROUND((percent*10 - Settings.shuttercoeff[i-1][index] ) * Shutter.open_max[index] * (calibrate_pos[i+1] - calibrate_pos[i]), Settings.shuttercoeff[i][index] - Settings.shuttercoeff[i-1][index]), 100); + } + break; + } + } + return realpos; + } +} + +uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index) +{ + 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 { + uint16_t realpercent; + + for (uint32_t i = 0; i < 5; i++) { + if (realpos >= Shutter.open_max[index] * calibrate_pos[i+1] / 100) { + realpercent = SHT_DIV_ROUND(Settings.shuttercoeff[i][index], 10); + + } else { + if (0 == i) { + realpercent = SHT_DIV_ROUND(SHT_DIV_ROUND((realpos - SHT_DIV_ROUND(Shutter.open_max[index] * calibrate_pos[i], 100)) * 10 * Settings.shuttercoeff[i][index], calibrate_pos[i+1]), Shutter.open_max[index]); + } else { + + + + realpercent += SHT_DIV_ROUND(SHT_DIV_ROUND((realpos - SHT_DIV_ROUND(Shutter.open_max[index] * calibrate_pos[i], 100)) * 10 * (Settings.shuttercoeff[i][index] - Settings.shuttercoeff[i-1][index]), (calibrate_pos[i+1] - calibrate_pos[i])), Shutter.open_max[index]) ; + } + break; + } + } + return realpercent; + } +} + +void ShutterInit(void) +{ + shutters_present = 0; + Shutter.mask = 0; + + Shutter.old_power = power; + bool relay_in_interlock = false; + + + if (Settings.shutter_startrelay[MAX_SHUTTERS] == 0) { + Shutter.max_pwm_frequency = Settings.shuttercoeff[4][3] > 0 ? Settings.shuttercoeff[4][3] : Shutter.max_pwm_frequency; + } + for (uint32_t i = 0; i < MAX_SHUTTERS; i++) { + + Settings.shutter_startrelay[i] = (Settings.shutter_startrelay[i] == 0 && i == 0? 1 : Settings.shutter_startrelay[i]); + if (Settings.shutter_startrelay[i] && (Settings.shutter_startrelay[i] < 9)) { + shutters_present++; + + + Shutter.mask |= 3 << (Settings.shutter_startrelay[i] -1) ; + + for (uint32_t j = 0; j < MAX_INTERLOCKS * Settings.flag.interlock; j++) { + + if (Settings.interlock[j] && (Settings.interlock[j] & Shutter.mask)) { + + relay_in_interlock = true; + } + } + if (relay_in_interlock) { + if (Settings.pulse_timer[i] > 0) { + Shutter.mode = SHT_PULSE_OPEN__PULSE_CLOSE; + } else { + Shutter.mode = SHT_OFF_OPEN__OFF_CLOSE; + } + } else { + Shutter.mode = SHT_OFF_ON__OPEN_CLOSE; + if ((pin[GPIO_PWM1+i] < 99) && (pin[GPIO_CNTR1+i] < 99)) { + Shutter.mode = SHT_OFF_ON__OPEN_CLOSE_STEPPER; + Shutter.pwm_frequency[i] = 0; + Shutter.accelerator[i] = 0; + analogWriteFreq(Shutter.pwm_frequency[i]); + analogWrite(pin[GPIO_PWM1+i], 50); + } + } + + TickerShutter.attach_ms(50, ShutterRtc50mS ); + + Settings.shutter_set50percent[i] = (Settings.shutter_set50percent[i] > 0) ? Settings.shutter_set50percent[i] : 50; + + + Shutter.open_time[i] = (Settings.shutter_opentime[i] > 0) ? Settings.shutter_opentime[i] : 100; + Shutter.close_time[i] = (Settings.shutter_closetime[i] > 0) ? Settings.shutter_closetime[i] : 100; + + + Shutter.open_max[i] = 200 * Shutter.open_time[i]; + Shutter.close_velocity[i] = Shutter.open_max[i] / Shutter.close_time[i] / 2 ; + Shutter.max_close_pwm_frequency[i] = Shutter.max_pwm_frequency*Shutter.open_time[i] / Shutter.close_time[i]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d Closefreq: %d"),i, Shutter.max_close_pwm_frequency[i]); + + + if (Settings.shutter_set50percent[i] != 50) { + Settings.shuttercoeff[1][i] = Shutter.open_max[i] * (100 - Settings.shutter_set50percent[i] ) / 5000; + Settings.shuttercoeff[0][i] = Shutter.open_max[i] - (Settings.shuttercoeff[1][i] * 100); + Settings.shuttercoeff[2][i] = (Settings.shuttercoeff[0][i] + 5 * Settings.shuttercoeff[1][i]) / 5; + } + Shutter.mask |= 3 << (Settings.shutter_startrelay[i] -1); + + Shutter.real_position[i] = ShutterPercentToRealPosition(Settings.shutter_position[i], i); + + Shutter.start_position[i] = Shutter.target_position[i] = Shutter.real_position[i]; + Shutter.motordelay[i] = Settings.shutter_motordelay[i]; + + char shutter_open_chr[10]; + dtostrfd((float)Shutter.open_time[i] / 10 , 1, shutter_open_chr); + char shutter_close_chr[10]; + dtostrfd((float)Shutter.close_time[i] / 10, 1, shutter_close_chr); + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100, Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoeffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, is locked %d, end stop time enabled %d, shuttermode %d, motordelay %d"), + i+1, Settings.shutter_startrelay[i], Shutter.real_position[i], Settings.shutter_position[i], Shutter.close_velocity[i], Shutter.open_max[i], shutter_open_chr, shutter_close_chr, + Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i], + Shutter.mask, (Settings.shutter_options[i]&1) ? 1 : 0, (Settings.shutter_options[i]&2) ? 1 : 0, (Settings.shutter_options[i]&4) ? 1 : 0, Shutter.mode, Shutter.motordelay[i]); + + } else { + + break; + } + ShutterLimitRealAndTargetPositions(i); + Settings.shutter_accuracy = 1; + } +} + +void ShutterReportPosition(bool always) +{ + Response_P(PSTR("{")); + for (uint32_t i = 0; i < shutters_present; i++) { + + uint32_t position = ShutterRealToPercentPosition(Shutter.real_position[i], i); + if (Shutter.direction[i] != 0) { + 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], ShutterRealToPercentPosition(Shutter.target_position[i], i)); + } + ResponseJsonEnd(); + if (always || (rules_flag.shutter_moving)) { + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER)); + XdrvRulesProcess(); + } + + +} + +void ShutterLimitRealAndTargetPositions(uint32_t i) { + if (Shutter.real_position[i]<0) Shutter.real_position[i] = 0; + if (Shutter.real_position[i]>Shutter.open_max[i]) Shutter.real_position[i] = Shutter.open_max[i]; + if (Shutter.target_position[i]<0) Shutter.target_position[i] = 0; + if (Shutter.target_position[i]>Shutter.open_max[i]) Shutter.target_position[i] = Shutter.open_max[i]; +} + +void ShutterUpdatePosition(void) +{ + + char scommand[CMDSZ]; + char stopic[TOPSZ]; + + for (uint32_t i = 0; i < shutters_present; i++) { + if (Shutter.direction[i] != 0) { + int32_t stop_position_delta = 20; + if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) { + + + Shutter.real_position[i] = ShutterCounterBasedPosition(i); + + int32_t max_frequency = Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i]; + int32_t max_freq_change_per_sec = Shutter.max_pwm_frequency*steps_per_second / (Shutter.motordelay[i]>0 ? Shutter.motordelay[i] : 1); + int32_t 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 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]); + + + 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] ) { + + 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); + + } else if ( Shutter.accelerator[i] > 0 && Shutter.pwm_frequency[i] == max_frequency) { + Shutter.accelerator[i] = 0; + } + } else { + Shutter.real_position[i] = Shutter.start_position[i] + ( (Shutter.time[i] - Shutter.motordelay[i]) * (Shutter.direction[i] > 0 ? 100 : -Shutter.close_velocity[i])); + } + if ( Shutter.real_position[i] * Shutter.direction[i] + stop_position_delta >= Shutter.target_position[i] * Shutter.direction[i] ) { + + + uint8_t cur_relay = Settings.shutter_startrelay[i] + (Shutter.direction[i] == 1 ? 0 : 1) ; + int16_t missing_steps; + + switch (Shutter.mode) { + case SHT_PULSE_OPEN__PULSE_CLOSE: + + if (SRC_PULSETIMER == last_source || SRC_SHUTTER == last_source || SRC_WEBGUI == last_source) { + ExecuteCommandPower(cur_relay, 1, SRC_SHUTTER); + } else { + last_source = SRC_SHUTTER; + } + break; + case SHT_OFF_ON__OPEN_CLOSE_STEPPER: + missing_steps = ((Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) - RtcSettings.pulse_counter[i]; + + 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[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[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); + } + 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]); + + if ((1 << (Settings.shutter_startrelay[i]-1)) & power) { + ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER); + ExecuteCommandPower(Settings.shutter_startrelay[i]+1, 0, SRC_SHUTTER); + } + break; + case SHT_OFF_ON__OPEN_CLOSE: + if ((1 << (Settings.shutter_startrelay[i]-1)) & power) { + ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER); + ExecuteCommandPower(Settings.shutter_startrelay[i]+1, 0, SRC_SHUTTER); + } + break; + case SHT_OFF_OPEN__OFF_CLOSE: + + if ((1 << (cur_relay-1)) & power) { + + ExecuteCommandPower(cur_relay, 0, SRC_SHUTTER); + } + break; + } + ShutterLimitRealAndTargetPositions(i); + Settings.shutter_position[i] = ShutterRealToPercentPosition(Shutter.real_position[i], i); + + ShutterLogPos(i); + + Shutter.start_position[i] = Shutter.real_position[i]; + + + snprintf_P(scommand, sizeof(scommand),PSTR(D_SHUTTER "%d"), i+1); + GetTopic_P(stopic, STAT, mqtt_topic, scommand); + Response_P("%d", (Settings.shutter_options[i] & 1) ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]); + MqttPublish(stopic, Settings.flag.mqtt_power_retain); + + Shutter.direction[i] = 0; + ShutterReportPosition(true); + rules_flag.shutter_moved = 1; + XdrvRulesProcess(); + } + } + } +} + +bool ShutterState(uint32_t device) +{ + device--; + device &= 3; + return (Settings.flag3.shutter_mode && + (Shutter.mask & (1 << (Settings.shutter_startrelay[device]-1))) ); +} + +void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos) +{ + + if ( ( (1 == direction) && ((Shutter.open_max[i] - Shutter.real_position[i]) / 100 <= 2) ) + || ( (-1 == direction) && (Shutter.real_position[i] / Shutter.close_velocity[i] <= 2)) ) { + Shutter.skip_relay_change = 1; + } else { + if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) { + Shutter.pwm_frequency[i] = 0; + analogWriteFreq(Shutter.pwm_frequency[i]); + analogWrite(pin[GPIO_PWM1+i], 0); + + if (pin[GPIO_CNTR1+i] < 99) { + RtcSettings.pulse_counter[i] = 0; + } + Shutter.accelerator[i] = Shutter.max_pwm_frequency / (Shutter.motordelay[i]>0 ? Shutter.motordelay[i] : 1); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Ramp up: %d"), Shutter.accelerator[i]); + } + Shutter.target_position[i] = target_pos; + Shutter.start_position[i] = Shutter.real_position[i]; + Shutter.time[i] = 0; + Shutter.skip_relay_change = 0; + Shutter.direction[i] = direction; + + } + +} + +void ShutterWaitForMotorStop(uint32_t i) +{ + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Wait for Motorstop..")); + 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) { + + while (Shutter.pwm_frequency[i] > 0) { + + Shutter.pwm_frequency[i] = tmax(Shutter.pwm_frequency[i]-((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) , 0); + + analogWriteFreq(Shutter.pwm_frequency[i]); + analogWrite(pin[GPIO_PWM1+i], 50); + delay(50); + } + analogWrite(pin[GPIO_PWM1+i], 0); + Shutter.real_position[i] = ShutterCounterBasedPosition(i); + } else { + ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER); + delay(MOTOR_STOP_TIME); + } + } else { + delay(MOTOR_STOP_TIME); + } +} + +int32_t ShutterCounterBasedPosition(uint32_t i) +{ + return ((int32_t)RtcSettings.pulse_counter[i]*Shutter.direction[i]*2000 / Shutter.max_pwm_frequency)+Shutter.start_position[i]; +} + +void ShutterRelayChanged(void) +{ + + + + + char stemp1[10]; + + for (uint32_t i = 0; i < shutters_present; i++) { + power_t powerstate_local = (power >> (Settings.shutter_startrelay[i] -1)) & 3; + + uint8 manual_relays_changed = ((Shutter.switched_relay >> (Settings.shutter_startrelay[i] -1)) & 3) && SRC_SHUTTER != last_source && SRC_PULSETIMER != last_source ; + + if (manual_relays_changed) { + + ShutterLimitRealAndTargetPositions(i); + if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE || Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) { + ShutterWaitForMotorStop(i); + switch (powerstate_local) { + case 1: + ShutterStartInit(i, 1, Shutter.open_max[i]); + break; + case 3: + ShutterStartInit(i, -1, 0); + break; + default: + + Shutter.target_position[i] = Shutter.real_position[i]; + } + } else { + if (Shutter.direction[i] != 0 && (!powerstate_local || (powerstate_local && Shutter.mode == SHT_PULSE_OPEN__PULSE_CLOSE))) { + Shutter.target_position[i] = Shutter.real_position[i]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Switch OFF motor. Target: %ld, source: %s, powerstate_local %ld, Shutter.switched_relay %d, manual change %d"), i+1, Shutter.target_position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), powerstate_local,Shutter.switched_relay,manual_relays_changed); + } else { + last_source = SRC_SHUTTER; + if (powerstate_local == 2) { + + ShutterWaitForMotorStop(i); + ShutterStartInit(i, -1, 0); + } else { + + ShutterWaitForMotorStop(i); + ShutterStartInit(i, 1, Shutter.open_max[i]); + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Target: %ld, powerstatelocal %d"), i+1, Shutter.target_position[i], powerstate_local); + } + } + } +} + +bool ShutterButtonIsSimultaneousHold(uint32_t button_index, uint32_t shutter_index) { + + uint32 min_shutterbutton_hold_timer = -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.hold_timer[i] < min_shutterbutton_hold_timer)) + min_shutterbutton_hold_timer = Button.hold_timer[i]; + } + return (min_shutterbutton_hold_timer > (Button.hold_timer[button_index]>>1)); +} + +void ShutterButtonHandler(void) +{ + uint8_t buttonState = SHT_NOT_PRESSED; + uint8_t button = XdrvMailbox.payload; + uint8_t press_index; + uint32_t button_index = XdrvMailbox.index; + uint8_t shutter_index = Settings.shutter_button[button_index] & 0x03; + + uint16_t loops_per_second = 1000 / Settings.button_debounce; + if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) { + if (Settings.flag.button_single) { + buttonState = SHT_PRESSED_MULTI; + press_index = 1; + } else { + if ((Shutter.direction[shutter_index]) && (Button.press_counter[button_index]==0)) { + buttonState = SHT_PRESSED_IMMEDIATE; + press_index = 1; + Button.press_counter[button_index] = 99; + } else { + Button.press_counter[button_index] = (Button.window_timer[button_index]) ? Button.press_counter[button_index] +1 : 1; + } + } + blinks = 201; + } + + if (NOT_PRESSED == button) { + Button.hold_timer[button_index] = 0; + } else { + Button.hold_timer[button_index]++; + if (!Settings.flag.button_single) { + if (Settings.param[P_HOLD_IGNORE] > 0) { + if (Button.hold_timer[button_index] > loops_per_second * Settings.param[P_HOLD_IGNORE] / 10) { + Button.hold_timer[button_index] = 0; + Button.press_counter[button_index] = 0; + } + } + if ((Button.press_counter[button_index]<99) && (Button.hold_timer[button_index] == loops_per_second * Settings.param[P_HOLD_TIME] / 10)) { + + if (ShutterButtonIsSimultaneousHold(button_index, shutter_index)) { + + 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] = 99; + press_index = 0; + buttonState = SHT_PRESSED_HOLD_SIMULTANEOUS; + } + if (Button.press_counter[button_index]<99) { + press_index = 0; + buttonState = SHT_PRESSED_HOLD; + } + Button.press_counter[button_index] = 0; + } + if ((Button.press_counter[button_index]==0) && (Button.hold_timer[button_index] == loops_per_second * IMMINENT_RESET_FACTOR * Settings.param[P_HOLD_TIME] / 10)) { + + if (ShutterButtonIsSimultaneousHold(button_index, shutter_index)) { + + press_index = 0; + buttonState = SHT_PRESSED_EXT_HOLD_SIMULTANEOUS; + } + } + } + } + + if (!Settings.flag.button_single) { + if (Button.window_timer[button_index]) { + Button.window_timer[button_index]--; + } else { + if (!restart_flag && !Button.hold_timer[button_index] && (Button.press_counter[button_index] > 0)) { + if (Button.press_counter[button_index]<99) { + + 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)) { + min_shutterbutton_press_counter = Button.press_counter[i]; + } + + } + if (min_shutterbutton_press_counter == Button.press_counter[button_index]) { + + 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)) + Button.press_counter[i] = 99; + buttonState = SHT_PRESSED_MULTI_SIMULTANEOUS; + } + if ((buttonState != SHT_PRESSED_MULTI_SIMULTANEOUS) && (Button.press_counter[button_index]<99)) { + + press_index = Button.press_counter[button_index]; + buttonState = SHT_PRESSED_MULTI; + } + } + Button.press_counter[button_index] = 0; + } + } + } + + if (buttonState != SHT_NOT_PRESSED) { + if (buttonState == SHT_PRESSED_MULTI_SIMULTANEOUS) { + if ((press_index>=5) && (press_index<=7) && (!Settings.flag.button_restrict)) { + + char scmnd[20]; + GetTextIndexed(scmnd, sizeof(scmnd), press_index -3, kCommands); + ExecuteCommand(scmnd, SRC_BUTTON); + return; + } + } else if (buttonState == SHT_PRESSED_EXT_HOLD_SIMULTANEOUS) { + if (!Settings.flag.button_restrict) { + char scmnd[20]; + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); + ExecuteCommand(scmnd, SRC_BUTTON); + return; + } + } else if (buttonState <= SHT_PRESSED_IMMEDIATE) { + if (Settings.shutter_startrelay[shutter_index] && Settings.shutter_startrelay[shutter_index] <9) { + uint8_t pos_press_index = (buttonState == SHT_PRESSED_HOLD) ? 3 : (press_index-1); + if (pos_press_index>3) pos_press_index=3; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: shutter %d, button %d = %d (single=1, double=2, tripple=3, hold=4)"), shutter_index+1, button_index+1, pos_press_index+1); + XdrvMailbox.index = shutter_index +1; + last_source = SRC_BUTTON; + XdrvMailbox.data_len = 0; + char databuf[1] = ""; + XdrvMailbox.data = databuf; + XdrvMailbox.command = NULL; + if (buttonState == SHT_PRESSED_IMMEDIATE) { + XdrvMailbox.payload = XdrvMailbox.index; + CmndShutterStop(); + } else { + uint8_t position = (Settings.shutter_button[button_index]>>(6*pos_press_index + 2)) & 0x03f; + if (position) { + if (Shutter.direction[shutter_index]) { + XdrvMailbox.payload = XdrvMailbox.index; + CmndShutterStop(); + } else { + XdrvMailbox.payload = position = (position-1)<<1; + CmndShutterPosition(); + if (Settings.shutter_button[button_index] & ((0x01<<26)< 0) && (XdrvMailbox.index <= shutters_present)) { + if (!(Settings.shutter_options[XdrvMailbox.index-1] & 2)) { + if ((1 == XdrvMailbox.index) && (XdrvMailbox.payload != -99)) { + XdrvMailbox.index = XdrvMailbox.payload; + } + uint32_t i = XdrvMailbox.index -1; + if (Shutter.direction[i] != 0) { + + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Stop moving %d: dir: %d"), XdrvMailbox.index, Shutter.direction[i]); + + int32_t temp_realpos = Shutter.start_position[i] + ( (Shutter.time[i]+10) * (Shutter.direction[i] > 0 ? 100 : -Shutter.close_velocity[i])); + XdrvMailbox.payload = ShutterRealToPercentPosition(temp_realpos, i); + + last_source = SRC_WEBGUI; + CmndShutterPosition(); + } else { + if (XdrvMailbox.command) + ResponseCmndDone(); + } + } else { + if (XdrvMailbox.command) + ResponseCmndIdxChar("Locked"); + } + } +} + +void CmndShutterPosition(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (!(Settings.shutter_options[XdrvMailbox.index-1] & 2)) { + uint32_t index = XdrvMailbox.index-1; + + 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 ); + + + + if ((XdrvMailbox.data_len > 1) && (XdrvMailbox.payload <= 0)) { + + 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))) { + CmndShutterOpen(); + return; + } + if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_DOWN) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_CLOSE) || ((Shutter.direction[index]==0) && !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEDOWN))) { + CmndShutterClose(); + return; + } + if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_STOP) || ((Shutter.direction[index]) && (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEUP) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEDOWN)))) { + XdrvMailbox.payload = -99; + CmndShutterStop(); + return; + } + } + + int8_t target_pos_percent = (XdrvMailbox.payload < 0) ? (XdrvMailbox.payload == -99 ? ShutterRealToPercentPosition(Shutter.real_position[index], index) : 0) : ((XdrvMailbox.payload > 100) ? 100 : XdrvMailbox.payload); + + target_pos_percent = ((Settings.shutter_options[index] & 1) && (SRC_WEBGUI != last_source)) ? 100 - target_pos_percent : target_pos_percent; + if (XdrvMailbox.payload != -99) { + + Shutter.target_position[index] = ShutterPercentToRealPosition(target_pos_percent, 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); + } + if ( (target_pos_percent >= 0) && (target_pos_percent <= 100) && abs(Shutter.target_position[index] - Shutter.real_position[index] ) / Shutter.close_velocity[index] > 2) { + if (Settings.shutter_options[index] & 4) { + if (0 == target_pos_percent) Shutter.target_position[index] -= 1 * 2000; + if (100 == target_pos_percent) Shutter.target_position[index] += 1 * 2000; + } + int8_t new_shutterdirection = Shutter.real_position[index] < Shutter.target_position[index] ? 1 : -1; + if (Shutter.direction[index] == -new_shutterdirection) { + + if (SHT_PULSE_OPEN__PULSE_CLOSE == Shutter.mode) { + + ExecuteCommandPower(Settings.shutter_startrelay[index] + ((new_shutterdirection == 1) ? 0 : 1), 1, SRC_SHUTTER); + delay(100); + } else { + if (SHT_OFF_OPEN__OFF_CLOSE == Shutter.mode) { + ExecuteCommandPower(Settings.shutter_startrelay[index] + ((new_shutterdirection == 1) ? 1 : 0), 0, SRC_SHUTTER); + ShutterWaitForMotorStop(index); + } + } + } + if (Shutter.direction[index] != new_shutterdirection) { + if ((SHT_OFF_ON__OPEN_CLOSE == Shutter.mode) || (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode)) { + + ShutterWaitForMotorStop(index); + ExecuteCommandPower(Settings.shutter_startrelay[index], 0, SRC_SHUTTER); + ShutterStartInit(index, new_shutterdirection, Shutter.target_position[index]); + if (Shutter.skip_relay_change == 0) { + + ExecuteCommandPower(Settings.shutter_startrelay[index] +1, new_shutterdirection == 1 ? 0 : 1, SRC_SHUTTER); + + ExecuteCommandPower(Settings.shutter_startrelay[index], 1, SRC_SHUTTER); + } + } else { + + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start in dir %d"), Shutter.direction[index]); + ShutterStartInit(index, new_shutterdirection, Shutter.target_position[index]); + if (Shutter.skip_relay_change == 0) { + ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER); + } + + } + Shutter.switched_relay = 0; + } + } else { + target_pos_percent = ShutterRealToPercentPosition(Shutter.real_position[index], index); + ShutterReportPosition(true); + } + XdrvMailbox.index = index +1; + if (XdrvMailbox.command) + ResponseCmndIdxNumber((Settings.shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent); + } else { + ShutterReportPosition(true); + if (XdrvMailbox.command) + ResponseCmndIdxChar("Locked"); + } + } +} + +void CmndShutterOpenTime(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.data_len > 0) { + Settings.shutter_opentime[XdrvMailbox.index -1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data)); + ShutterInit(); + } + char time_chr[10]; + dtostrfd((float)(Settings.shutter_opentime[XdrvMailbox.index -1]) / 10, 1, time_chr); + ResponseCmndIdxChar(time_chr); + } +} + +void CmndShutterCloseTime(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.data_len > 0) { + Settings.shutter_closetime[XdrvMailbox.index -1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data)); + ShutterInit(); + } + char time_chr[10]; + dtostrfd((float)(Settings.shutter_closetime[XdrvMailbox.index -1]) / 10, 1, time_chr); + ResponseCmndIdxChar(time_chr); + } +} + +void CmndShutterMotorDelay(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.data_len > 0) { + Settings.shutter_motordelay[XdrvMailbox.index -1] = (uint16_t)(steps_per_second * CharToFloat(XdrvMailbox.data)); + ShutterInit(); + } + char time_chr[10]; + dtostrfd((float)(Settings.shutter_motordelay[XdrvMailbox.index -1]) / steps_per_second, 2, time_chr); + ResponseCmndIdxChar(time_chr); + } +} + +void CmndShutterRelay(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 64)) { + Settings.shutter_startrelay[XdrvMailbox.index -1] = XdrvMailbox.payload; + if (XdrvMailbox.payload > 0) { + Shutter.mask |= 3 << (XdrvMailbox.payload - 1); + } else { + Shutter.mask ^= 3 << (Settings.shutter_startrelay[XdrvMailbox.index -1] - 1); + } + Settings.shutter_startrelay[XdrvMailbox.index -1] = XdrvMailbox.payload; + ShutterInit(); + + } + ResponseCmndIdxNumber(Settings.shutter_startrelay[XdrvMailbox.index -1]); + } +} + +void CmndShutterButton(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) { + uint32_t setting = 0; +# 912 "S:/Development/Tasmota/tasmota/xdrv_27_shutter.ino" + if (XdrvMailbox.data_len > 0) { + uint32_t i = 0; + uint32_t button_index = 0; + bool done = false; + bool isShortCommand = false; + char *str_ptr; + + char data_copy[strlen(XdrvMailbox.data) +1]; + strncpy(data_copy, XdrvMailbox.data, sizeof(data_copy)); + + for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < (1+4+4+1); str = strtok_r(nullptr, " ", &str_ptr), i++) { + int field; + if (str[0] == '-') { + field = -1; + } else { + field = atoi(str); + } + switch (i) { + case 0: + if ((field >= -1) && (field<=4)) { + button_index = (field<=0)?(-1):field; + done = (button_index==-1); + } else + done = true; + break; + case 1: + if (!strcmp_P(str, PSTR("up"))) { + setting |= (((100>>1)+1)<<2) | (((50>>1)+1)<<8) | (((75>>1)+1)<<14) | (((100>>1)+1)<<20); + isShortCommand = true; + break; + } else if (!strcmp_P(str, PSTR("down"))) { + setting |= (((0>>1)+1)<<2) | (((50>>1)+1)<<8) | (((25>>1)+1)<<14) | (((0>>1)+1)<<20); + isShortCommand = true; + break; + } else if (!strcmp_P(str, PSTR("updown"))) { + setting |= (((100>>1)+1)<<2) | (((0>>1)+1)<<8) | (((50>>1)+1)<<14); + isShortCommand = true; + break; + } + case 2: + if (isShortCommand) { + if ((field==1) && (setting & (0x3F<<(2+6*3)))) + + setting |= (0x3<<29); + done = true; + break; + } + case 3: + case 4: + if ((field >= -1) && (field<=100)) + setting |= (((field>>1)+1)<<(i*6 + (2-6))); + break; + case 5: + case 6: + case 7: + case 8: + case 9: + if (field==1) + setting |= (1<<(i + (26-5))); + break; + } + if (done) break; + } + + if (button_index) { + if (button_index==-1) { + + for (uint32_t i=0 ; i < MAX_KEYS ; i++) + if ((Settings.shutter_button[i]&0x3) == (XdrvMailbox.index-1)) + Settings.shutter_button[i] = 0; + } else { + if (setting) { + + setting |= (1<<31); + setting |= (XdrvMailbox.index-1) & 0x3; + } + Settings.shutter_button[button_index-1] = setting; + } + } + } + char setting_chr[30*MAX_KEYS] = "-", *setting_chr_ptr = setting_chr; + for (uint32_t i=0 ; i < MAX_KEYS ; i++) { + setting = Settings.shutter_button[i]; + if ((setting&(1<<31)) && ((setting&0x3) == (XdrvMailbox.index-1))) { + if (*setting_chr_ptr == 0) + setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR("|")); + setting_chr_ptr += snprintf_P(setting_chr_ptr, 2, PSTR("%d"), i+1); + + for (uint32_t j=0 ; j < 4 ; j++) { + int8_t pos = (((setting>> (2+6*j))&(0x3f))-1)<<1; + if (pos>=0) + setting_chr_ptr += snprintf_P(setting_chr_ptr, 5, PSTR(" %d"), pos); + else + setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" -")); + } + for (uint32_t j=0 ; j < 5 ; j++) { + bool mqtt = ((setting>>(26+j))&(0x01)!=0); + if (mqtt) + setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" 1")); + else + setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" -")); + } + } + } + ResponseCmndIdxChar(setting_chr); + } +} + +void CmndShutterSetHalfway(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { + Settings.shutter_set50percent[XdrvMailbox.index -1] = (Settings.shutter_options[XdrvMailbox.index -1] & 1) ? 100 - XdrvMailbox.payload : XdrvMailbox.payload; + ShutterInit(); + } + ResponseCmndIdxNumber((Settings.shutter_options[XdrvMailbox.index -1] & 1) ? 100 - Settings.shutter_set50percent[XdrvMailbox.index -1] : Settings.shutter_set50percent[XdrvMailbox.index -1]); + } +} + +void CmndShutterFrequency(void) +{ + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 20000)) { + Shutter.max_pwm_frequency = XdrvMailbox.payload; + if (shutters_present < 4) { + Settings.shuttercoeff[4][3] = Shutter.max_pwm_frequency; + } + ShutterInit(); + ResponseCmndNumber(XdrvMailbox.payload); + } else { + ResponseCmndNumber(Shutter.max_pwm_frequency); + } +} + +void CmndShutterSetClose(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + Shutter.real_position[XdrvMailbox.index -1] = 0; + ShutterStartInit(XdrvMailbox.index -1, 0, 0); + Settings.shutter_position[XdrvMailbox.index -1] = 0; + ResponseCmndIdxChar(D_CONFIGURATION_RESET); + } +} + +void CmndShutterInvert(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.payload == 0) { + Settings.shutter_options[XdrvMailbox.index -1] &= ~(1); + } else if (XdrvMailbox.payload == 1) { + Settings.shutter_options[XdrvMailbox.index -1] |= (1); + } + ResponseCmndIdxNumber((Settings.shutter_options[XdrvMailbox.index -1] & 1) ? 1 : 0); + } +} + +void CmndShutterCalibration(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.data_len > 0) { + uint32_t i = 0; + char *str_ptr; + + char data_copy[strlen(XdrvMailbox.data) +1]; + strncpy(data_copy, XdrvMailbox.data, sizeof(data_copy)); + + for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < 5; str = strtok_r(nullptr, " ", &str_ptr), i++) { + int field = atoi(str); + + + if ((field <= 0) || (field > 30000) || ( (i>0) && (field <= messwerte[i-1]) ) ) { + break; + } + messwerte[i] = field; + } + for (i = 0; i < 5; i++) { + Settings.shuttercoeff[i][XdrvMailbox.index -1] = SHT_DIV_ROUND((uint32_t)messwerte[i] * 1000, messwerte[4]); + AddLog_P2(LOG_LEVEL_INFO, PSTR("Settings.shuttercoeff: %d, i: %d, value: %d, messwert %d"), i,XdrvMailbox.index -1,Settings.shuttercoeff[i][XdrvMailbox.index -1], messwerte[i]); + } + ShutterInit(); + ResponseCmndIdxChar(XdrvMailbox.data); + } else { + char setting_chr[30] = "0"; + snprintf_P(setting_chr, sizeof(setting_chr), PSTR("%d %d %d %d %d"), Settings.shuttercoeff[0][XdrvMailbox.index -1], Settings.shuttercoeff[1][XdrvMailbox.index -1], Settings.shuttercoeff[2][XdrvMailbox.index -1], Settings.shuttercoeff[3][XdrvMailbox.index -1], Settings.shuttercoeff[4][XdrvMailbox.index -1]); + ResponseCmndIdxChar(setting_chr); + } + } +} + +void CmndShutterLock(void) { + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.payload == 0) { + Settings.shutter_options[XdrvMailbox.index -1] &= ~(2); + } else if (XdrvMailbox.payload == 1) { + Settings.shutter_options[XdrvMailbox.index -1] |= (2); + } + ResponseCmndIdxNumber((Settings.shutter_options[XdrvMailbox.index -1] & 2) ? 1 : 0); + } +} + +void CmndShutterEnableEndStopTime(void) { + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.payload == 0) { + Settings.shutter_options[XdrvMailbox.index -1] &= ~(4); + } else if (XdrvMailbox.payload == 1) { + Settings.shutter_options[XdrvMailbox.index -1] |= (4); + } + ResponseCmndIdxNumber((Settings.shutter_options[XdrvMailbox.index -1] & 4) ? 1 : 0); + } +} + + + + + +bool Xdrv27(uint8_t function) +{ + bool result = false; + + if (Settings.flag3.shutter_mode) { + switch (function) { + case FUNC_PRE_INIT: + ShutterInit(); + break; + case FUNC_EVERY_50_MSECOND: + ShutterUpdatePosition(); + break; + case FUNC_EVERY_SECOND: + + ShutterReportPosition(false); + break; + + case FUNC_COMMAND: + result = DecodeCommand(kShutterCommands, ShutterCommand); + break; + case FUNC_JSON_APPEND: + for (uint8_t i = 0; i < shutters_present; i++) { + uint8_t position = (Settings.shutter_options[i] & 1) ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]; + ResponseAppend_P(","); + ResponseAppend_P(JSON_SHUTTER_POS, i+1, position, Shutter.direction[i]); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzSensor(DZ_SHUTTER, position); + } +#endif + } + break; + case FUNC_SET_POWER: + char stemp1[10]; + + Shutter.switched_relay = XdrvMailbox.index ^ Shutter.old_power; + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Switched relay: %d by %s"), Shutter.switched_relay,GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource)); + ShutterRelayChanged(); + Shutter.old_power = XdrvMailbox.index; + break; + case FUNC_SET_DEVICE_POWER: + if (Shutter.skip_relay_change ) { + uint8_t i; + for (i = 0; i < devices_present; i++) { + if (Shutter.switched_relay &1) { + break; + } + Shutter.switched_relay >>= 1; + } + + result = true; + Shutter.skip_relay_change = 0; + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Skipping switch off relay %d"),i); + ExecuteCommandPower(i+1, 0, SRC_SHUTTER); + } + break; + case FUNC_BUTTON_PRESSED: + if (Settings.shutter_button[XdrvMailbox.index] & (1<<31)) { + ShutterButtonHandler(); + result = true; + } + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_28_pcf8574.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_28_pcf8574.ino" +#ifdef USE_I2C +#ifdef USE_PCF8574 + + + + + + + +#define XDRV_28 28 +#define XI2C_02 2 + +#define PCF8574_ADDR1 0x20 +#define PCF8574_ADDR2 0x38 + +struct PCF8574 { + int error; + uint8_t pin[64]; + uint8_t address[MAX_PCF8574]; + uint8_t pin_mask[MAX_PCF8574] = { 0 }; + uint8_t max_connected_ports = 0; + uint8_t max_devices = 0; + char stype[9]; + bool type = false; +} Pcf8574; + +void Pcf8574SwitchRelay(void) +{ + for (uint32_t i = 0; i < devices_present; i++) { + uint8_t relay_state = bitRead(XdrvMailbox.index, i); + + + + if (Pcf8574.max_devices > 0 && Pcf8574.pin[i] < 99) { + uint8_t board = Pcf8574.pin[i]>>3; + uint8_t oldpinmask = Pcf8574.pin_mask[board]; + uint8_t _val = bitRead(rel_inverted, i) ? !relay_state : relay_state; + + + + if (_val) { + Pcf8574.pin_mask[board] |= _val << (Pcf8574.pin[i]&0x7); + } else { + Pcf8574.pin_mask[board] &= ~(1 << (Pcf8574.pin[i]&0x7)); + } + if (oldpinmask != Pcf8574.pin_mask[board]) { + Wire.beginTransmission(Pcf8574.address[board]); + Wire.write(Pcf8574.pin_mask[board]); + Pcf8574.error = Wire.endTransmission(); + } + + } + } +} + +void Pcf8574Init(void) +{ + uint8_t pcf8574_address = PCF8574_ADDR1; + while ((Pcf8574.max_devices < MAX_PCF8574) && (pcf8574_address < PCF8574_ADDR2 +8)) { + + + + if (I2cSetDevice(pcf8574_address)) { + Pcf8574.type = true; + + Pcf8574.address[Pcf8574.max_devices] = pcf8574_address; + Pcf8574.max_devices++; + + strcpy(Pcf8574.stype, "PCF8574"); + if (pcf8574_address >= PCF8574_ADDR2) { + strcpy(Pcf8574.stype, "PCF8574A"); + } + I2cSetActiveFound(pcf8574_address, Pcf8574.stype); + } + + pcf8574_address++; +#ifdef USE_MCP230xx_ADDR + if (USE_MCP230xx_ADDR == pcf8574_address) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Addr: 0x%x reserved for MCP320xx, skipping PCF8574 probe"), pcf8574_address); + pcf8574_address++; + } +#endif + if ((PCF8574_ADDR1 +7) == pcf8574_address) { + pcf8574_address = PCF8574_ADDR2 +1; + } + } + if (Pcf8574.type) { + for (uint32_t i = 0; i < sizeof(Pcf8574.pin); i++) { + Pcf8574.pin[i] = 99; + } + devices_present = devices_present - Pcf8574.max_connected_ports; + Pcf8574.max_connected_ports = 0; + for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) { + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Device %d config 0x%02x"), idx +1, Settings.pcf8574_config[idx]); + + for (uint32_t i = 0; i < 8; i++) { + uint8_t _result = Settings.pcf8574_config[idx] >> i &1; + + if (_result > 0) { + Pcf8574.pin[devices_present] = i + 8 * idx; + bitWrite(rel_inverted, devices_present, Settings.flag3.pcf8574_ports_inverted); + devices_present++; + Pcf8574.max_connected_ports++; + } + } + } + AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Total devices %d, PCF8574 output ports %d"), Pcf8574.max_devices, Pcf8574.max_connected_ports); + } +} + + + + + +#ifdef USE_WEBSERVER + +#define WEB_HANDLE_PCF8574 "pcf" + +const char HTTP_BTN_MENU_PCF8574[] PROGMEM = + "

"; + +const char HTTP_FORM_I2C_PCF8574_1[] PROGMEM = + "
 " D_PCF8574_PARAMETERS " " + "
" + "

" D_INVERT_PORTS "


"; + +const char HTTP_FORM_I2C_PCF8574_2[] PROGMEM = + "" D_DEVICE " %d " D_PORT " %d"; + +void HandlePcf8574(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_PCF8574)); + + if (WebServer->hasArg("save")) { + Pcf8574SaveSettings(); + WebRestart(1); + return; + } + + WSContentStart_P(D_CONFIGURE_PCF8574); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_I2C_PCF8574_1, (Settings.flag3.pcf8574_ports_inverted) ? " checked" : ""); + WSContentSend_P(HTTP_TABLE100); + for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) { + for (uint32_t idx2 = 0; idx2 < 8; idx2++) { + uint8_t helper = 1 << idx2; + WSContentSend_P(HTTP_FORM_I2C_PCF8574_2, + idx +1, idx2, + idx2 + 8*idx, + idx2 + 8*idx, + ((helper & Settings.pcf8574_config[idx]) >> idx2 == 0) ? " selected " : " ", + ((helper & Settings.pcf8574_config[idx]) >> idx2 == 1) ? " selected " : " " + ); + } + } + WSContentSend_P(PSTR("")); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void Pcf8574SaveSettings(void) +{ + char stemp[7]; + char tmp[100]; + + + + Settings.flag3.pcf8574_ports_inverted = WebServer->hasArg("b1"); + for (byte idx = 0; idx < Pcf8574.max_devices; idx++) { + byte count=0; + byte n = Settings.pcf8574_config[idx]; + while(n!=0) { + n = n&(n-1); + count++; + } + if (count <= devices_present) { + devices_present = devices_present - count; + } + for (byte i = 0; i < 8; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("i2cs%d"), i+8*idx); + WebGetArg(stemp, tmp, sizeof(tmp)); + byte _value = (!strlen(tmp)) ? 0 : atoi(tmp); + if (_value) { + Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] | 1 << i; + devices_present++; + Pcf8574.max_connected_ports++; + } else { + Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] & ~(1 << i ); + } + } + + + + } +} +#endif + + + + + +bool Xdrv28(uint8_t function) +{ + if (!I2cEnabled(XI2C_02)) { return false; } + + bool result = false; + + if (FUNC_PRE_INIT == function) { + Pcf8574Init(); + } + else if (Pcf8574.type) { + switch (function) { + case FUNC_SET_POWER: + Pcf8574SwitchRelay(); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_PCF8574); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/" WEB_HANDLE_PCF8574, HandlePcf8574); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_29_deepsleep.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_29_deepsleep.ino" +#ifdef USE_DEEPSLEEP +# 31 "S:/Development/Tasmota/tasmota/xdrv_29_deepsleep.ino" +#define XDRV_29 29 + +#define D_PRFX_DEEPSLEEP "DeepSleep" +#define D_CMND_DEEPSLEEP_TIME "Time" + +const uint32_t DEEPSLEEP_MAX = 10 * 366 * 24 * 60 * 60; +const uint32_t DEEPSLEEP_MAX_CYCLE = 60 * 60; +const uint32_t DEEPSLEEP_MIN_TIME = 5; +const uint32_t DEEPSLEEP_START_COUNTDOWN = 4; + +const char kDeepsleepCommands[] PROGMEM = D_PRFX_DEEPSLEEP "|" + D_CMND_DEEPSLEEP_TIME ; + +void (* const DeepsleepCommand[])(void) PROGMEM = { + &CmndDeepsleepTime }; + +uint32_t deepsleep_sleeptime = 0; +uint8_t deepsleep_flag = 0; + +bool DeepSleepEnabled(void) +{ + if ((Settings.deepsleep < 10) || (Settings.deepsleep > DEEPSLEEP_MAX)) { + Settings.deepsleep = 0; + return false; + } + + if (pin[GPIO_DEEPSLEEP] < 99) { + pinMode(pin[GPIO_DEEPSLEEP], INPUT_PULLUP); + return (digitalRead(pin[GPIO_DEEPSLEEP])); + } + + return true; +} + +void DeepSleepReInit(void) +{ + if ((ResetReason() == REASON_DEEP_SLEEP_AWAKE) && DeepSleepEnabled()) { + if ((RtcSettings.ultradeepsleep > DEEPSLEEP_MAX_CYCLE) && (RtcSettings.ultradeepsleep < 1700000000)) { + + RtcSettings.ultradeepsleep = RtcSettings.ultradeepsleep - DEEPSLEEP_MAX_CYCLE; + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Remain DeepSleep %d"), RtcSettings.ultradeepsleep); + RtcSettingsSave(); + RtcRebootReset(); + ESP.deepSleep(100 * RtcSettings.deepsleep_slip * (DEEPSLEEP_MAX_CYCLE < RtcSettings.ultradeepsleep ? DEEPSLEEP_MAX_CYCLE : RtcSettings.ultradeepsleep), WAKE_RF_DEFAULT); + yield(); + + } + } + + RtcSettings.ultradeepsleep = 0; +} + +void DeepSleepPrepare(void) +{ + + + + + if ((RtcSettings.nextwakeup == 0) || + (RtcSettings.deepsleep_slip < 9000) || + (RtcSettings.deepsleep_slip > 11000) || + (RtcSettings.nextwakeup > (UtcTime() + Settings.deepsleep))) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Reset wrong settings wakeup: %ld, slip %ld"), RtcSettings.nextwakeup, RtcSettings.deepsleep_slip ); + RtcSettings.nextwakeup = 0; + RtcSettings.deepsleep_slip = 10000; + } + + + + int16_t timeslip = (int16_t)(RtcSettings.nextwakeup + millis() / 1000 - UtcTime()) * 10; + + + + timeslip = (timeslip < -(int32_t)Settings.deepsleep) ? 0 : (timeslip > (int32_t)Settings.deepsleep) ? 0 : 1; + if (timeslip) { + RtcSettings.deepsleep_slip = (Settings.deepsleep + RtcSettings.nextwakeup - UtcTime()) * RtcSettings.deepsleep_slip / tmax((Settings.deepsleep - (millis() / 1000)),5); + + RtcSettings.deepsleep_slip = tmin(tmax(RtcSettings.deepsleep_slip, 9000), 11000); + RtcSettings.nextwakeup += Settings.deepsleep; + } + + + + if (RtcSettings.nextwakeup <= (UtcTime() - DEEPSLEEP_MIN_TIME)) { + + RtcSettings.nextwakeup += (((UtcTime() + DEEPSLEEP_MIN_TIME - RtcSettings.nextwakeup) / Settings.deepsleep) + 1) * Settings.deepsleep; + } + + String dt = GetDT(RtcSettings.nextwakeup + LocalTime() - UtcTime()); + + + deepsleep_sleeptime = tmin((uint32_t)DEEPSLEEP_MAX_CYCLE ,RtcSettings.nextwakeup - UtcTime()); + + + Response_P(PSTR("{\"" D_PRFX_DEEPSLEEP "\":{\"" D_JSON_TIME "\":\"%s\",\"Epoch\":%d}}"), (char*)dt.c_str(), RtcSettings.nextwakeup); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_STATUS)); + + + +} + +void DeepSleepStart(void) +{ + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION "Sleeping")); + + WifiShutdown(); + RtcSettings.ultradeepsleep = RtcSettings.nextwakeup - UtcTime(); + RtcSettingsSave(); + + ESP.deepSleep(100 * RtcSettings.deepsleep_slip * deepsleep_sleeptime); + yield(); +} + +void DeepSleepEverySecond(void) +{ + if (!deepsleep_flag) { return; } + + if (DeepSleepEnabled()) { + if (DEEPSLEEP_START_COUNTDOWN == deepsleep_flag) { + SettingsSaveAll(); + DeepSleepPrepare(); + } + deepsleep_flag--; + if (deepsleep_flag <= 0) { + DeepSleepStart(); + } + } else { + deepsleep_flag = 0; + } +} + + + + + +void CmndDeepsleepTime(void) +{ + if ((0 == XdrvMailbox.payload) || + ((XdrvMailbox.payload > 10) && (XdrvMailbox.payload < DEEPSLEEP_MAX))) { + Settings.deepsleep = XdrvMailbox.payload; + RtcSettings.nextwakeup = 0; + deepsleep_flag = (0 == XdrvMailbox.payload) ? 0 : DEEPSLEEP_START_COUNTDOWN; + if (deepsleep_flag) { + if (!Settings.tele_period) { + Settings.tele_period = TELE_PERIOD; + } + } + } + Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, Settings.deepsleep); +} + + + + + +bool Xdrv29(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_SECOND: + DeepSleepEverySecond(); + break; + case FUNC_AFTER_TELEPERIOD: + if (DeepSleepEnabled() && !deepsleep_flag && (Settings.tele_period == 10 || Settings.tele_period == 300 || UpTime() > Settings.tele_period)) { + deepsleep_flag = DEEPSLEEP_START_COUNTDOWN; + } + break; + case FUNC_COMMAND: + result = DecodeCommand(kDeepsleepCommands, DeepsleepCommand); + break; + case FUNC_PRE_INIT: + DeepSleepReInit(); + break; + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_30_exs_dimmer.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_30_exs_dimmer.ino" +#ifdef USE_LIGHT +#ifdef USE_EXS_DIMMER + + + + + + + +#define XDRV_30 30 + +#define EXS_GATE_1_ON 0x20 +#define EXS_GATE_1_OFF 0x21 +#define EXS_DIMM_1_ON 0x22 +#define EXS_DIMM_1_OFF 0x23 +#define EXS_DIMM_1_TBL 0x24 +#define EXS_DIMM_1_VAL 0x25 +#define EXS_GATE_2_ON 0x30 +#define EXS_GATE_2_OFF 0x31 +#define EXS_DIMM_2_ON 0x32 +#define EXS_DIMM_2_OFF 0x33 +#define EXS_DIMM_2_TBL 0x34 +#define EXS_DIMM_2_VAL 0x35 +#define EXS_GATES_ON 0x40 +#define EXS_GATES_OFF 0x41 +#define EXS_DIMMS_ON 0x50 +#define EXS_DIMMS_OFF 0x51 +#define EXS_CH_LOCK 0x60 +#define EXS_GET_VALUES 0xFA +#define EXS_WRITE_EE 0xFC +#define EXS_READ_EE 0xFD +#define EXS_GET_VERSION 0xFE +#define EXS_RESET 0xFF + +#define EXS_BUFFER_SIZE 256 +#define EXS_ACK_TIMEOUT 200 + +#include + +TasmotaSerial *ExsSerial = nullptr; + +typedef struct +{ + uint8_t on = 0; + uint8_t bright_tbl = 0; + uint8_t dimm = 0; + uint8_t impuls_start = 0; + uint32_t impuls_len = 0; +} CHANNEL; + +typedef struct +{ + uint8_t version_major = 0; + uint8_t version_minor = 0; + CHANNEL channel[2]; + uint8_t gate_lock = 0; +} DIMMER; + +struct EXS +{ + uint8_t *buffer = nullptr; + int byte_counter = 0; + int cmd_status = 0; + uint8_t power = 0; + uint8_t dimm[2] = {0, 0}; + DIMMER dimmer; +} Exs; + + + + + +uint8_t crc8(const uint8_t *p, uint8_t len) +{ + const uint8_t table[] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, + 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D}; + + const uint8_t table_rev[] = { + 0x00, 0x70, 0xE0, 0x90, 0xC1, 0xB1, 0x21, 0x51, + 0x83, 0xF3, 0x63, 0x13, 0x42, 0x32, 0xA2, 0xD2}; + + uint8_t offset; + uint8_t temp, crc8_temp; + uint8_t crc8 = 0; + + for (int i = 0; i < len; i++) + { + temp = *(p + i); + offset = temp ^ crc8; + offset >>= 4; + crc8_temp = crc8 & 0x0f; + crc8 = crc8_temp ^ table_rev[offset]; + offset = crc8 ^ temp; + offset &= 0x0f; + crc8_temp = crc8 & 0xf0; + crc8 = crc8_temp ^ table[offset]; + } + return crc8 ^ 0x55; +} + +void ExsSerialSend(const uint8_t data[] = nullptr, uint16_t len = 0) +{ + int retries = 3; + char rc; + +#ifdef EXS_DEBUG + snprintf_P(log_data, sizeof(log_data), PSTR("EXS: Tx Packet: \"")); + for (uint32_t i = 0; i < len; i++) + { + snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, data[i]); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s\""), log_data); + AddLog(LOG_LEVEL_DEBUG_MORE); +#endif + + while (retries) + { + retries--; + + ExsSerial->write(data, len); + ExsSerial->flush(); + + + uint32_t snd_time = millis(); + while ((TimePassedSince(snd_time) < EXS_ACK_TIMEOUT) && + (!ExsSerial->available())) + ; + + if (!ExsSerial->available()) + { + +#ifdef EXS_DEBUG + AddLog_P(LOG_LEVEL_DEBUG, PSTR("ESX: serial send timeout")); +#endif + continue; + } + + rc = ExsSerial->read(); + if (rc == 0xFF) + break; + } +} + +void ExsSendCmd(uint8_t cmd, uint8_t value) +{ + uint8_t buffer[8]; + uint16_t len; + + buffer[0] = 0x7b; + buffer[3] = cmd; + + switch (cmd) + { + case EXS_GATE_1_ON: + case EXS_GATE_1_OFF: + case EXS_DIMM_1_ON: + case EXS_DIMM_1_OFF: + case EXS_GATE_2_ON: + case EXS_GATE_2_OFF: + case EXS_DIMM_2_ON: + case EXS_DIMM_2_OFF: + case EXS_GATES_ON: + case EXS_GATES_OFF: + case EXS_DIMMS_ON: + case EXS_DIMMS_OFF: + case EXS_GET_VALUES: + case EXS_GET_VERSION: + case EXS_RESET: + buffer[2] = 1; + len = 4; + break; + + case EXS_CH_LOCK: + case EXS_DIMM_1_TBL: + case EXS_DIMM_1_VAL: + case EXS_DIMM_2_TBL: + case EXS_DIMM_2_VAL: + buffer[2] = 2; + buffer[4] = value; + len = 5; + break; + } + buffer[1] = crc8(&buffer[3], buffer[2]); + + ExsSerialSend(buffer, len); +} + +uint8_t ExsSetPower(uint8_t device, uint8_t power) +{ + Exs.dimmer.channel[device].dimm = power; + ExsSendCmd(EXS_DIMM_1_ON + 0x10 * device + power ^ 1, 0); +} + +uint8_t ExsSetBri(uint8_t device, uint8_t bri) +{ + Exs.dimmer.channel[device].bright_tbl = bri; + ExsSendCmd(EXS_DIMM_1_TBL + 0x10 * device, bri); +} + +uint8_t ExsSyncState(uint8_t device) +{ +#ifdef EXS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Channel %d Power Want %d, Is %d"), + device, bitRead(Exs.power, device), Exs.dimmer.channel[device].dimm); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Set Channel %d Brightness Want %d, Is %d"), + device, Exs.dimm[device], Exs.dimmer.channel[device].bright_tbl); +#endif + + if (bitRead(Exs.power, device) && + Exs.dimm[device] != Exs.dimmer.channel[device].bright_tbl) { + ExsSetBri(device, Exs.dimm[device]); + } + + if (!Exs.dimm[device]) { + Exs.dimmer.channel[device].dimm = 0; + } else if (Exs.dimmer.channel[device].dimm != bitRead(Exs.power, device)) { + ExsSetPower(device, bitRead(Exs.power, device)); + } +} + +bool ExsSyncState() +{ +#ifdef EXS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Serial %p, Cmd %d"), ExsSerial, Exs.cmd_status); +#endif + + if (!ExsSerial || Exs.cmd_status != 0) + return false; + + ExsSyncState(0); + ExsSyncState(1); +} + +void ExsDebugState() +{ +#ifdef EXS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: MCU v%d.%d, c0: On:%d,Dim:%d,Tbl:%d(%d%%), c1: On:%d,Dim:%d,Tbl:%d(%d%%), ChLock: %d"), + Exs.dimmer.version_major, Exs.dimmer.version_minor, + Exs.dimmer.channel[0].on, Exs.dimmer.channel[0].dimm, + Exs.dimmer.channel[0].bright_tbl, + changeUIntScale(Exs.dimmer.channel[0].bright_tbl, 0, 255, 0, 100), + Exs.dimmer.channel[1].on, Exs.dimmer.channel[1].dimm, + Exs.dimmer.channel[1].bright_tbl, + changeUIntScale(Exs.dimmer.channel[1].bright_tbl, 0, 255, 0, 100), + Exs.dimmer.gate_lock); +#endif +} + +void ExsPacketProcess(void) +{ + uint8_t len = Exs.buffer[1]; + uint8_t cmd = Exs.buffer[2]; + + switch (cmd) + { + case EXS_GET_VALUES: +# 294 "S:/Development/Tasmota/tasmota/xdrv_30_exs_dimmer.ino" + if (len > 9) + { + Exs.dimmer.version_major = Exs.buffer[3]; + Exs.dimmer.version_minor = Exs.buffer[4]; + + + Exs.dimmer.channel[0].on = Exs.buffer[6]; + Exs.dimmer.channel[0].dimm = Exs.buffer[6]; + Exs.dimmer.channel[0].bright_tbl = Exs.buffer[7]; + + + Exs.dimmer.channel[1].on = Exs.buffer[9]; + Exs.dimmer.channel[1].dimm = Exs.buffer[9]; + Exs.dimmer.channel[1].bright_tbl = Exs.buffer[10]; + + Exs.dimmer.gate_lock = Exs.buffer[11]; + } + else +# 327 "S:/Development/Tasmota/tasmota/xdrv_30_exs_dimmer.ino" + { + Exs.dimmer.version_major = 1; + Exs.dimmer.version_minor = 0; + + + Exs.dimmer.channel[0].on = Exs.buffer[4] - 48; + Exs.dimmer.channel[0].dimm = Exs.buffer[4] - 48; + Exs.dimmer.channel[0].bright_tbl = Exs.buffer[5] - 48; + + + Exs.dimmer.channel[1].on = Exs.buffer[7] - 48; + Exs.dimmer.channel[1].dimm = Exs.buffer[7] - 48; + Exs.dimmer.channel[1].bright_tbl = Exs.buffer[8] - 48; + + Exs.dimmer.gate_lock = Exs.buffer[9] - 48; + } + + ExsDebugState(); + ExsSyncState(); + ExsDebugState(); + break; + default: + break; + } +} + + + +bool ExsModuleSelected(void) +{ + Settings.light_correction = 0; + Settings.flag.mqtt_serial = 0; + Settings.flag3.pwm_multi_channels = 1; + SetSeriallog(LOG_LEVEL_NONE); + + devices_present = +2; + light_type = LT_SERIAL2; + return true; +} + +bool ExsSetChannels(void) +{ +#ifdef EXS_DEBUG + snprintf_P(log_data, sizeof(log_data), PSTR("EXS: SetChannels: \"")); + for (int i = 0; i < XdrvMailbox.data_len; i++) + { + snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, ((uint8_t *)XdrvMailbox.data)[i]); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s\""), log_data); + AddLog(LOG_LEVEL_DEBUG_MORE); +#endif + + Exs.dimm[0] = ((uint8_t *)XdrvMailbox.data)[0]; + Exs.dimm[1] = ((uint8_t *)XdrvMailbox.data)[1]; + return ExsSyncState(); +} + +bool ExsSetPower(void) +{ + AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Set Power, Device %d, Power 0x%02x"), + active_device, XdrvMailbox.index); + + Exs.power = XdrvMailbox.index; + return ExsSyncState(); +} + +void EsxMcuStart(void) +{ + int retries = 3; + +#ifdef EXS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Request MCU configuration, PIN %d to Low"), pin[GPIO_EXS_ENABLE]); +#endif + + pinMode(pin[GPIO_EXS_ENABLE], OUTPUT); + digitalWrite(pin[GPIO_EXS_ENABLE], LOW); + + delay(1); + + while (ExsSerial->available()) + { + + ExsSerial->read(); + } +} + +void ExsInit(void) +{ +#ifdef EXS_DEBUG + AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Starting Tx %d Rx %d"), pin[GPIO_TXD], pin[GPIO_RXD]); +#endif + + Exs.buffer = (uint8_t *)malloc(EXS_BUFFER_SIZE); + if (Exs.buffer != nullptr) + { + ExsSerial = new TasmotaSerial(pin[GPIO_RXD], pin[GPIO_TXD], 2); + if (ExsSerial->begin(9600)) + { + if (ExsSerial->hardwareSerial()) + { + ClaimSerial(); + } + ExsSerial->flush(); + EsxMcuStart(); + ExsSendCmd(EXS_CH_LOCK, 0); + ExsSendCmd(EXS_GET_VALUES, 0); + } + } +} + +void ExsSerialInput(void) +{ + while (ExsSerial->available()) + { + yield(); + uint8_t serial_in_byte = ExsSerial->read(); + + AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Serial In Byte 0x%02x"), serial_in_byte); + + if (Exs.cmd_status == 0 && + serial_in_byte == 0x7B) + { + Exs.cmd_status = 1; + Exs.byte_counter = 0; + } + else if (Exs.byte_counter >= EXS_BUFFER_SIZE) + { + Exs.cmd_status = 0; + } + else if (Exs.cmd_status == 1) + { + Exs.buffer[Exs.byte_counter++] = serial_in_byte; + + if (Exs.byte_counter > 2 && Exs.byte_counter == Exs.buffer[1] + 2) + { + uint8_t crc = crc8(&Exs.buffer[2], Exs.buffer[1]); + + + Exs.cmd_status = 0; + +#ifdef EXS_DEBUG + snprintf_P(log_data, sizeof(log_data), PSTR("EXS: RX Packet: \"")); + for (uint32_t i = 0; i < Exs.byte_counter; i++) + { + snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, Exs.buffer[i]); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s\", CRC: 0x%02x"), log_data, crc); + AddLog(LOG_LEVEL_DEBUG_MORE); +#endif + + if (Exs.buffer[0] == crc) + { + ExsSerial->write(0xFF); + ExsPacketProcess(); + } + else + { + ExsSerial->write(0x00); + } + + } + } + } +} + + + + + +#ifdef EXS_MCU_CMNDS + +#define D_PRFX_EXS "Exs" +#define D_CMND_EXS_DIMM "Dimm" +#define D_CMND_EXS_DIMM_TBL "DimmTbl" +#define D_CMND_EXS_DIMM_VAL "DimmVal" +#define D_CMND_EXS_DIMMS "Dimms" +#define D_CMND_EXS_CH_LOCK "ChLock" +#define D_CMND_EXS_STATE "State" + +const char kExsCommands[] PROGMEM = D_PRFX_EXS "|" + D_CMND_EXS_DIMM "|" D_CMND_EXS_DIMM_TBL "|" D_CMND_EXS_DIMM_VAL "|" + D_CMND_EXS_DIMMS "|" D_CMND_EXS_CH_LOCK "|" + D_CMND_EXS_STATE; + +void (* const ExsCommand[])(void) PROGMEM = { + &CmndExsDimm, &CmndExsDimmTbl, &CmndExsDimmVal, + &CmndExsDimms, &CmndExsChLock, + &CmndExsState }; + +void CmndExsDimm(void) +{ + if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) && + (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1)) { + ExsSendCmd(EXS_DIMM_1_ON + 0x10 * (XdrvMailbox.index - 1) + + XdrvMailbox.payload ^ 1, 0); + } + CmndExsState(); +} + +void CmndExsDimmTbl(void) +{ + if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) && + (XdrvMailbox.payload > 0 || XdrvMailbox.payload <= 255)) { + ExsSendCmd(EXS_DIMM_1_TBL + 0x10 * (XdrvMailbox.index - 1), + XdrvMailbox.payload); + } + CmndExsState(); +} + +void CmndExsDimmVal(void) +{ + if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) && + (XdrvMailbox.payload > 0 || XdrvMailbox.payload <= 255)) { + ExsSendCmd(EXS_DIMM_1_VAL + 0x10 * (XdrvMailbox.index - 1), + XdrvMailbox.payload); + } + CmndExsState(); +} + +void CmndExsDimms(void) +{ + if (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1) { + ExsSendCmd(EXS_DIMMS_ON + XdrvMailbox.payload ^ 1, 0); + } + CmndExsState(); +} + +void CmndExsChLock(void) +{ + if (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1) { + ExsSendCmd(EXS_CH_LOCK, XdrvMailbox.payload); + } + CmndExsState(); +} + +void CmndExsState(void) +{ + ExsSendCmd(EXS_GET_VALUES, 0); + + + uint32_t snd_time = millis(); + while ((TimePassedSince(snd_time) < EXS_ACK_TIMEOUT) && + (!ExsSerial->available())) + ; + ExsSerialInput(); + + Response_P(PSTR("{\"" D_CMND_EXS_STATE "\":{")); + ResponseAppend_P(PSTR("\"McuVersion\":\"%d.%d\"," + "\"Channels\":["), + Exs.dimmer.version_major, Exs.dimmer.version_minor); + + for (uint32_t i = 0; i < 2; i++) { + if (i != 0) { + ResponseAppend_P(PSTR(",")); + } + ResponseAppend_P(PSTR("{\"On\":\"%d\"," + "\"BrightProz\":\"%d\"," + "\"BrightTab\":\"%d\"," + "\"Dimm\":\"%d\"}"), + Exs.dimmer.channel[i].on, + changeUIntScale(Exs.dimmer.channel[i].bright_tbl, 0, 255, 0, 100), + Exs.dimmer.channel[i].bright_tbl, + Exs.dimmer.channel[i].dimm); + } + ResponseAppend_P(PSTR("],")); + ResponseAppend_P(PSTR("\"GateLock\":\"%d\""), Exs.dimmer.gate_lock); + ResponseJsonEndEnd(); +} + +#endif + + + + + +bool Xdrv30(uint8_t function) +{ + bool result = false; + + if (EXS_DIMMER == my_module_type) + { + switch (function) + { + case FUNC_LOOP: + if (ExsSerial) + ExsSerialInput(); + break; + case FUNC_MODULE_INIT: + result = ExsModuleSelected(); + break; + case FUNC_INIT: + ExsInit(); + break; + case FUNC_SET_DEVICE_POWER: + result = ExsSetPower(); + break; + case FUNC_SET_CHANNELS: + result = ExsSetChannels(); + break; +#ifdef EXS_MCU_CMNDS + case FUNC_COMMAND: + result = DecodeCommand(kExsCommands, ExsCommand); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_31_tasmota_slave.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_31_tasmota_slave.ino" +#ifdef USE_TASMOTA_SLAVE + + + + +#define XDRV_31 31 + +#define CONST_STK_CRC_EOP 0x20 + +#define CMND_STK_GET_SYNC 0x30 +#define CMND_STK_SET_DEVICE 0x42 +#define CMND_STK_SET_DEVICE_EXT 0x45 +#define CMND_STK_ENTER_PROGMODE 0x50 +#define CMND_STK_LEAVE_PROGMODE 0x51 +#define CMND_STK_LOAD_ADDRESS 0x55 +#define CMND_STK_PROG_PAGE 0x64 + + + + + +#define CMND_START 0xFC +#define CMND_END 0xFD + +#define CMND_FEATURES 0x01 +#define CMND_JSON 0x02 +#define CMND_FUNC_EVERY_SECOND 0x03 +#define CMND_FUNC_EVERY_100_MSECOND 0x04 +#define CMND_SLAVE_SEND 0x05 +#define CMND_PUBLISH_TELE 0x06 +#define CMND_EXECUTE_CMND 0x07 + +#define PARAM_DATA_START 0xFE +#define PARAM_DATA_END 0xFF + +#include + + + + + +class SimpleHexParse { + public: + SimpleHexParse(void); + uint8_t parseLine(char *hexline); + uint8_t ptr_l = 0; + uint8_t ptr_h = 0; + bool PageIsReady = false; + bool firstrun = true; + bool EndOfFile = false; + uint8_t FlashPage[128]; + uint8_t FlashPageIdx = 0; + uint8_t layoverBuffer[16]; + uint8_t layoverIdx = 0; + uint8_t getByte(char *hexline, uint8_t idx); +}; + +SimpleHexParse::SimpleHexParse(void) +{ + +} + +uint8_t SimpleHexParse::parseLine(char *hexline) +{ + if (layoverIdx) { + memcpy(&FlashPage[0], &layoverBuffer[0], layoverIdx); + FlashPageIdx = layoverIdx; + layoverIdx = 0; + } + uint8_t len = getByte(hexline, 1); + uint8_t addr_h = getByte(hexline, 2); + uint8_t addr_l = getByte(hexline, 3); + uint8_t rectype = getByte(hexline, 4); + for (uint8_t idx = 0; idx < len; idx++) { + if (FlashPageIdx < 128) { + FlashPage[FlashPageIdx] = getByte(hexline, idx+5); + FlashPageIdx++; + } else { + layoverBuffer[layoverIdx] = getByte(hexline, idx+5); + layoverIdx++; + } + } + if (1 == rectype) { + EndOfFile = true; + while (FlashPageIdx < 128) { + FlashPage[FlashPageIdx] = 0xFF; + FlashPageIdx++; + } + } + if (FlashPageIdx == 128) { + if (firstrun) { + firstrun = false; + } else { + ptr_l += 0x40; + if (ptr_l == 0) { + ptr_l = 0; + ptr_h++; + } + } + firstrun = false; + PageIsReady = true; + } + return 0; +} + +uint8_t SimpleHexParse::getByte(char* hexline, uint8_t idx) +{ + char buff[3]; + buff[3] = '\0'; + memcpy(&buff, &hexline[(idx*2)-1], 2); + return strtol(buff, 0, 16); +} + + + + + +struct TSLAVE { + uint32_t spi_hex_size = 0; + uint32_t spi_sector_counter = 0; + uint8_t spi_sector_cursor = 0; + uint8_t inverted = LOW; + bool type = false; + bool flashing = false; + bool SerialEnabled = false; + uint8_t waitstate = 0; + bool unsupported = false; +} TSlave; + +typedef union { + uint32_t data; + struct { + uint32_t func_json_append : 1; + uint32_t func_every_second : 1; + uint32_t func_every_100_msecond : 1; + uint32_t func_slave_send : 1; + uint32_t spare4 : 1; + uint32_t spare5 : 1; + uint32_t spare6 : 1; + uint32_t spare7 : 1; + uint32_t spare8 : 1; + uint32_t spare9 : 1; + uint32_t spare10 : 1; + uint32_t spare11 : 1; + uint32_t spare12 : 1; + uint32_t spare13 : 1; + uint32_t spare14 : 1; + uint32_t spare15 : 1; + uint32_t spare16 : 1; + uint32_t spare17 : 1; + uint32_t spare18 : 1; + uint32_t spare19 : 1; + uint32_t spare20 : 1; + uint32_t spare21 : 1; + uint32_t spare22 : 1; + uint32_t spare23 : 1; + uint32_t spare24 : 1; + uint32_t spare25 : 1; + uint32_t spare26 : 1; + uint32_t spare27 : 1; + uint32_t spare28 : 1; + uint32_t spare29 : 1; + uint32_t spare30 : 1; + uint32_t spare31 : 1; + }; +} TSlaveFeatureCfg; + + + + + + +struct TSLAVE_FEATURES { + uint32_t features_version; + TSlaveFeatureCfg features; +} TSlaveSettings; + +struct TSLAVE_COMMAND { + uint8_t command; + uint8_t parameter; + uint8_t unused2; + uint8_t unused3; +} TSlaveCommand; + +TasmotaSerial *TasmotaSlave_Serial; + +uint32_t TasmotaSlave_FlashStart(void) +{ + return (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 2; +} + +uint8_t TasmotaSlave_UpdateInit(void) +{ + TSlave.spi_hex_size = 0; + TSlave.spi_sector_counter = TasmotaSlave_FlashStart(); + TSlave.spi_sector_cursor = 0; + return 0; +} + +void TasmotaSlave_Reset(void) +{ + if (TSlave.SerialEnabled) { + digitalWrite(pin[GPIO_TASMOTASLAVE_RST], !TSlave.inverted); + delay(1); + digitalWrite(pin[GPIO_TASMOTASLAVE_RST], TSlave.inverted); + delay(1); + digitalWrite(pin[GPIO_TASMOTASLAVE_RST], !TSlave.inverted); + delay(5); + } +} + +uint8_t TasmotaSlave_waitForSerialData(int dataCount, int timeout) +{ + int timer = 0; + while (timer < timeout) { + if (TasmotaSlave_Serial->available() >= dataCount) { + return 1; + } + delay(1); + timer++; + } + return 0; +} + +uint8_t TasmotaSlave_sendBytes(uint8_t* bytes, int count) +{ + TasmotaSlave_Serial->write(bytes, count); + TasmotaSlave_waitForSerialData(2, 250); + uint8_t sync = TasmotaSlave_Serial->read(); + uint8_t ok = TasmotaSlave_Serial->read(); + if ((sync == 0x14) && (ok == 0x10)) { + return 1; + } + return 0; +} + +uint8_t TasmotaSlave_execCmd(uint8_t cmd) +{ + uint8_t bytes[] = { cmd, CONST_STK_CRC_EOP }; + return TasmotaSlave_sendBytes(bytes, 2); +} + +uint8_t TasmotaSlave_execParam(uint8_t cmd, uint8_t* params, int count) +{ + uint8_t bytes[32]; + bytes[0] = cmd; + int i = 0; + while (i < count) { + bytes[i + 1] = params[i]; + i++; + } + bytes[i + 1] = CONST_STK_CRC_EOP; + return TasmotaSlave_sendBytes(bytes, i + 2); +} + +uint8_t TasmotaSlave_exitProgMode(void) +{ + return TasmotaSlave_execCmd(CMND_STK_LEAVE_PROGMODE); +} + +uint8_t TasmotaSlave_SetupFlash(void) +{ + uint8_t ProgParams[] = {0x86, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00}; + uint8_t ExtProgParams[] = {0x05, 0x04, 0xd7, 0xc2, 0x00}; + TasmotaSlave_Serial->begin(USE_TASMOTA_SLAVE_FLASH_SPEED); + if (TasmotaSlave_Serial->hardwareSerial()) { + ClaimSerial(); + } + + TasmotaSlave_Reset(); + + uint8_t timeout = 0; + uint8_t no_error = 0; + while (50 > timeout) { + if (TasmotaSlave_execCmd(CMND_STK_GET_SYNC)) { + timeout = 200; + no_error = 1; + } + timeout++; + delay(1); + } + if (no_error) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Found bootloader")); + } else { + no_error = 0; + AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Bootloader could not be found")); + } + if (no_error) { + if (TasmotaSlave_execParam(CMND_STK_SET_DEVICE, ProgParams, sizeof(ProgParams))) { + } else { + no_error = 0; + AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Could not configure device for programming (1)")); + } + } + if (no_error) { + if (TasmotaSlave_execParam(CMND_STK_SET_DEVICE_EXT, ExtProgParams, sizeof(ExtProgParams))) { + } else { + no_error = 0; + AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Could not configure device for programming (2)")); + } + } + if (no_error) { + if (TasmotaSlave_execCmd(CMND_STK_ENTER_PROGMODE)) { + } else { + no_error = 0; + AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Failed to put bootloader into programming mode")); + } + } + return no_error; +} + +uint8_t TasmotaSlave_loadAddress(uint8_t adrHi, uint8_t adrLo) +{ + uint8_t params[] = { adrLo, adrHi }; + return TasmotaSlave_execParam(CMND_STK_LOAD_ADDRESS, params, sizeof(params)); +} + +void TasmotaSlave_FlashPage(uint8_t addr_h, uint8_t addr_l, uint8_t* data) +{ + uint8_t Header[] = {CMND_STK_PROG_PAGE, 0x00, 0x80, 0x46}; + TasmotaSlave_loadAddress(addr_h, addr_l); + TasmotaSlave_Serial->write(Header, 4); + for (int i = 0; i < 128; i++) { + TasmotaSlave_Serial->write(data[i]); + } + TasmotaSlave_Serial->write(CONST_STK_CRC_EOP); + TasmotaSlave_waitForSerialData(2, 250); + TasmotaSlave_Serial->read(); + TasmotaSlave_Serial->read(); +} + +void TasmotaSlave_Flash(void) +{ + bool reading = true; + uint32_t read = 0; + uint32_t processed = 0; + char thishexline[50]; + uint8_t position = 0; + char* flash_buffer; + + SimpleHexParse hexParse = SimpleHexParse(); + + if (!TasmotaSlave_SetupFlash()) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Flashing aborted!")); + TSlave.flashing = false; + restart_flag = 2; + return; + } + + flash_buffer = new char[SPI_FLASH_SEC_SIZE]; + uint32_t flash_start = TasmotaSlave_FlashStart() * SPI_FLASH_SEC_SIZE; + while (reading) { + ESP.flashRead(flash_start + read, (uint32_t*)flash_buffer, SPI_FLASH_SEC_SIZE); + read = read + SPI_FLASH_SEC_SIZE; + if (read >= TSlave.spi_hex_size) { + reading = false; + } + for (uint32_t ca = 0; ca < SPI_FLASH_SEC_SIZE; ca++) { + processed++; + if ((processed <= TSlave.spi_hex_size) && (!hexParse.EndOfFile)) { + if (':' == flash_buffer[ca]) { + position = 0; + } + if (0x0D == flash_buffer[ca]) { + thishexline[position] = 0; + hexParse.parseLine(thishexline); + if (hexParse.PageIsReady) { + TasmotaSlave_FlashPage(hexParse.ptr_h, hexParse.ptr_l, hexParse.FlashPage); + hexParse.PageIsReady = false; + hexParse.FlashPageIdx = 0; + } + } else { + if (0x0A != flash_buffer[ca]) { + thishexline[position] = flash_buffer[ca]; + position++; + } + } + } + } + } + TasmotaSlave_exitProgMode(); + AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Flash done!")); + TSlave.flashing = false; + restart_flag = 2; +} + +void TasmotaSlave_SetFlagFlashing(bool value) +{ + TSlave.flashing = value; +} + +bool TasmotaSlave_GetFlagFlashing(void) +{ + return TSlave.flashing; +} + +void TasmotaSlave_WriteBuffer(uint8_t *buf, size_t size) +{ + if (0 == TSlave.spi_sector_cursor) { + ESP.flashEraseSector(TSlave.spi_sector_counter); + } + TSlave.spi_sector_cursor++; + ESP.flashWrite((TSlave.spi_sector_counter * SPI_FLASH_SEC_SIZE) + ((TSlave.spi_sector_cursor-1)*2048), (uint32_t*)buf, size); + TSlave.spi_hex_size = TSlave.spi_hex_size + size; + if (2 == TSlave.spi_sector_cursor) { + TSlave.spi_sector_cursor = 0; + TSlave.spi_sector_counter++; + } +} + +void TasmotaSlave_Init(void) +{ + if (TSlave.type) { + return; + } + if (10 > TSlave.waitstate) { + TSlave.waitstate++; + return; + } + if (!TSlave.SerialEnabled) { + if ((pin[GPIO_TASMOTASLAVE_RXD] < 99) && (pin[GPIO_TASMOTASLAVE_TXD] < 99) && + ((pin[GPIO_TASMOTASLAVE_RST] < 99) || (pin[GPIO_TASMOTASLAVE_RST_INV] < 99))) { + TasmotaSlave_Serial = new TasmotaSerial(pin[GPIO_TASMOTASLAVE_RXD], pin[GPIO_TASMOTASLAVE_TXD], 1, 0, 200); + if (TasmotaSlave_Serial->begin(USE_TASMOTA_SLAVE_SERIAL_SPEED)) { + if (TasmotaSlave_Serial->hardwareSerial()) { + ClaimSerial(); + } + TasmotaSlave_Serial->setTimeout(50); + if (pin[GPIO_TASMOTASLAVE_RST_INV] < 99) { + pin[GPIO_TASMOTASLAVE_RST] = pin[GPIO_TASMOTASLAVE_RST_INV]; + pin[GPIO_TASMOTASLAVE_RST_INV] = 99; + TSlave.inverted = HIGH; + } + pinMode(pin[GPIO_TASMOTASLAVE_RST], OUTPUT); + TSlave.SerialEnabled = true; + TasmotaSlave_Reset(); + AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Enabled")); + } + } + } + if (TSlave.SerialEnabled) { + TasmotaSlave_sendCmnd(CMND_FEATURES, 0); + char buffer[32]; + TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_START), buffer, sizeof(buffer)); + uint8_t len = TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_END), buffer, sizeof(buffer)); + memcpy(&TSlaveSettings, &buffer, sizeof(TSlaveSettings)); + if (20191129 == TSlaveSettings.features_version) { + TSlave.type = true; + AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Version %u"), TSlaveSettings.features_version); + } else { + if ((!TSlave.unsupported) && (TSlaveSettings.features_version > 0)) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Version %u not supported!"), TSlaveSettings.features_version); + TSlave.unsupported = true; + } + } + } +} + +void TasmotaSlave_Show(void) +{ + if ((TSlave.type) && (TSlaveSettings.features.func_json_append)) { + char buffer[100]; + TasmotaSlave_sendCmnd(CMND_JSON, 0); + TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_START), buffer, sizeof(buffer)-1); + uint8_t len = TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_END), buffer, sizeof(buffer)-1); + buffer[len] = '\0'; + ResponseAppend_P(PSTR(",\"TasmotaSlave\":%s"), buffer); + } +} + +void TasmotaSlave_sendCmnd(uint8_t cmnd, uint8_t param) +{ + TSlaveCommand.command = cmnd; + TSlaveCommand.parameter = param; + char buffer[sizeof(TSlaveCommand)+2]; + buffer[0] = CMND_START; + memcpy(&buffer[1], &TSlaveCommand, sizeof(TSlaveCommand)); + buffer[sizeof(TSlaveCommand)+1] = CMND_END; + for (uint8_t ca = 0; ca < sizeof(buffer); ca++) { + TasmotaSlave_Serial->write(buffer[ca]); + } +} + +#define D_PRFX_SLAVE "Slave" +#define D_CMND_SLAVE_RESET "Reset" +#define D_CMND_SLAVE_SEND "Send" + +const char kTasmotaSlaveCommands[] PROGMEM = D_PRFX_SLAVE "|" + D_CMND_SLAVE_RESET "|" D_CMND_SLAVE_SEND; + +void (* const TasmotaSlaveCommand[])(void) PROGMEM = { + &CmndTasmotaSlaveReset, &CmndTasmotaSlaveSend }; + +void CmndTasmotaSlaveReset(void) +{ + TasmotaSlave_Reset(); + TSlave.type = false; + TSlave.waitstate = 7; + TSlave.unsupported = false; + ResponseCmndDone(); +} + +void CmndTasmotaSlaveSend(void) +{ + if (0 < XdrvMailbox.data_len) { + TasmotaSlave_sendCmnd(CMND_SLAVE_SEND, XdrvMailbox.data_len); + TasmotaSlave_Serial->write(char(PARAM_DATA_START)); + for (uint8_t idx = 0; idx < XdrvMailbox.data_len; idx++) { + TasmotaSlave_Serial->write(XdrvMailbox.data[idx]); + } + TasmotaSlave_Serial->write(char(PARAM_DATA_END)); + } + ResponseCmndDone(); +} + +void TasmotaSlave_ProcessIn(void) +{ + uint8_t cmnd = TasmotaSlave_Serial->read(); + switch (cmnd) { + case CMND_START: + TasmotaSlave_waitForSerialData(sizeof(TSlaveCommand),50); + uint8_t buffer[sizeof(TSlaveCommand)]; + for (uint8_t idx = 0; idx < sizeof(TSlaveCommand); idx++) { + buffer[idx] = TasmotaSlave_Serial->read(); + } + TasmotaSlave_Serial->read(); + memcpy(&TSlaveCommand, &buffer, sizeof(TSlaveCommand)); + char inbuf[TSlaveCommand.parameter+1]; + TasmotaSlave_waitForSerialData(TSlaveCommand.parameter, 50); + TasmotaSlave_Serial->read(); + for (uint8_t idx = 0; idx < TSlaveCommand.parameter; idx++) { + inbuf[idx] = TasmotaSlave_Serial->read(); + } + TasmotaSlave_Serial->read(); + inbuf[TSlaveCommand.parameter] = '\0'; + + if (CMND_PUBLISH_TELE == TSlaveCommand.command) { + Response_P(PSTR("{\"TasmotaSlave\":")); + ResponseAppend_P("%s", inbuf); + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data); + XdrvRulesProcess(); + } + if (CMND_EXECUTE_CMND == TSlaveCommand.command) { + ExecuteCommand(inbuf, SRC_IGNORE); + } + break; + default: + break; + } +} + + + + + + +bool Xdrv31(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_100_MSECOND: + if (TSlave.type) { + if (TasmotaSlave_Serial->available()) { + TasmotaSlave_ProcessIn(); + } + if (TSlaveSettings.features.func_every_100_msecond) { + TasmotaSlave_sendCmnd(CMND_FUNC_EVERY_100_MSECOND, 0); + } + } + break; + case FUNC_EVERY_SECOND: + if ((TSlave.type) && (TSlaveSettings.features.func_every_second)) { + TasmotaSlave_sendCmnd(CMND_FUNC_EVERY_SECOND, 0); + } + TasmotaSlave_Init(); + break; + case FUNC_JSON_APPEND: + if ((TSlave.type) && (TSlaveSettings.features.func_json_append)) { + TasmotaSlave_Show(); + } + break; + case FUNC_COMMAND: + result = DecodeCommand(kTasmotaSlaveCommands, TasmotaSlaveCommand); + break; + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_32_hotplug.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_32_hotplug.ino" +#ifdef USE_HOTPLUG + + + + + + + +#define XDRV_32 32 + +const uint32_t HOTPLUG_MAX = 254; + +const char kHotPlugCommands[] PROGMEM = "|" + D_CMND_HOTPLUG; + +void (* const HotPlugCommand[])(void) PROGMEM = { + &CmndHotPlugTime }; + +struct { + + bool enabled = false; + uint8_t timeout = 0; +} Hotplug; + +void HotPlugInit(void) +{ + + if (Settings.hotplug_scan == 0xFF) { Settings.hotplug_scan = 0; } + if (Settings.hotplug_scan != 0) { + Hotplug.enabled = true; + Hotplug.timeout = 1; + } else + Hotplug.enabled = false; +} + +void HotPlugEverySecond(void) +{ + if (Hotplug.enabled) { + if (Hotplug.timeout == 0) { + XsnsCall(FUNC_HOTPLUG_SCAN); + Hotplug.timeout = Settings.hotplug_scan; + } + Hotplug.timeout--; + } +} + + + + + +void CmndHotPlugTime(void) +{ + if (XdrvMailbox.payload <= HOTPLUG_MAX) { + Settings.hotplug_scan = XdrvMailbox.payload; + HotPlugInit(); + } + ResponseCmndNumber(Settings.hotplug_scan); +} + + + + + +bool Xdrv32(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_SECOND: + HotPlugEverySecond(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kHotPlugCommands, HotPlugCommand); + break; + case FUNC_PRE_INIT: + HotPlugInit(); + break; + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_33_nrf24l01.ino" +# 30 "S:/Development/Tasmota/tasmota/xdrv_33_nrf24l01.ino" +#ifdef USE_SPI +#ifdef USE_NRF24 + + + + + + + +#define XDRV_33 33 + +#define MOSI 13 +#define MISO 12 +#define SCK 14 + +#include +#include + +const char NRF24type[] PROGMEM = "NRF24"; + +const char HTTP_NRF24[] PROGMEM = + "{s}%sL01%c: " "{m}started{e}"; + +struct { + uint8_t chipType = 0; +} NRF24; + + + +RF24 NRF24radio; + +bool NRF24initRadio() +{ + NRF24radio.begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_DC]); + NRF24radio.powerUp(); + + if(NRF24radio.isChipConnected()){ + DEBUG_DRIVER_LOG(PSTR("NRF24 chip connected")); + return true; + } + DEBUG_DRIVER_LOG(PSTR("NRF24 chip NOT !!!! connected")); + return false; +} + +bool NRF24Detect(void) +{ + if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_DC]<99)){ + SPI.pins(SCK,MOSI,MISO,-1); + if(NRF24initRadio()){ + NRF24.chipType = 32; + AddLog_P2(LOG_LEVEL_INFO,PSTR("NRF24L01 initialized")); + if(NRF24radio.isPVariant()){ + NRF24.chipType = 43; + AddLog_P2(LOG_LEVEL_INFO,PSTR("NRF24L01+ detected")); + } + return true; + } + } + return false; +} + + + + + +bool Xdrv33(uint8_t function) +{ + bool result = false; + + if (FUNC_INIT == function) { + result = NRF24Detect(); + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_34_wemos_motor_v1.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_34_wemos_motor_v1.ino" +#ifdef USE_I2C +#ifdef USE_WEMOS_MOTOR_V1 +# 45 "S:/Development/Tasmota/tasmota/xdrv_34_wemos_motor_v1.ino" +#define XDRV_34 34 +#define XI2C_44 44 + +#ifndef WEMOS_MOTOR_V1_ADDR +#define WEMOS_MOTOR_V1_ADDR 0x30 +#endif +#ifndef WEMOS_MOTOR_V1_FREQ +#define WEMOS_MOTOR_V1_FREQ 1000 +#endif + +#define MOTOR_A 0 +#define MOTOR_B 1 + +#define SHORT_BRAKE 0 +#define CCW 1 +#define CW 2 +#define STOP 3 +#define STANDBY 4 + +struct WMOTORV1 { + bool detected = false; + uint8_t motor; +} WMotorV1; + +void WMotorV1Detect(void) +{ + if (I2cSetDevice(WEMOS_MOTOR_V1_ADDR)) { + WMotorV1.detected = true; + I2cSetActiveFound(WEMOS_MOTOR_V1_ADDR, "WEMOS_MOTOR_V1"); + WMotorV1Reset(); + } +} + +void WMotorV1Reset(void) +{ + + WMotorV1SetFrequency(WEMOS_MOTOR_V1_FREQ); +} + +void WMotorV1SetFrequency(uint32_t freq) +{ + Wire.beginTransmission(WEMOS_MOTOR_V1_ADDR); + Wire.write(((byte)(freq >> 16)) & (byte)0x0f); + Wire.write((byte)(freq >> 16)); + Wire.write((byte)(freq >> 8)); + Wire.write((byte)freq); + Wire.endTransmission(); + +} + +void WMotorV1SetMotor(uint8_t motor, uint8_t dir, float pwm_val) +{ + Wire.beginTransmission(WEMOS_MOTOR_V1_ADDR); + Wire.write(motor | (byte)0x10); + Wire.write(dir); + + uint16_t _pwm_val = uint16_t(pwm_val * 100); + if (_pwm_val > 10000) { + _pwm_val = 10000; + } + + Wire.write((byte)(_pwm_val >> 8)); + Wire.write((byte)_pwm_val); + Wire.endTransmission(); + +} + +bool WMotorV1Command(void) +{ + uint8_t args_count = 0; + + if (XdrvMailbox.data_len > 0) { + args_count = 1; + } else { + return false; + } + + for (uint32_t idx = 0; idx < XdrvMailbox.data_len; idx++) { + if (' ' == XdrvMailbox.data[idx]) { + XdrvMailbox.data[idx] = ','; + } + if (',' == XdrvMailbox.data[idx]) { + args_count++; + } + } + UpperCase(XdrvMailbox.data, XdrvMailbox.data); + + char *command = strtok(XdrvMailbox.data, ","); + + if (strcmp(command, "RESET") == 0) { + WMotorV1Reset(); + Response_P(PSTR("{\"WEMOS_MOTOR_V1\":{\"RESET\":\"OK\"}}")); + return true; + } + + if (strcmp(command, "SETMOTOR") == 0) { + if (args_count >= 3) { + + int motor = atoi(strtok(NULL, ",")); + int dir = atoi(strtok(NULL, ",")); + int duty = 100; + if (args_count == 4) { + duty = atoi(strtok(NULL, ",")); + } + + WMotorV1SetMotor(motor, dir, duty); + Response_P(PSTR("{\"WEMOS_MOTOR_V1\":{\"SETMOTOR\":\"OK\"}}")); + return true; + } + } + return false; +} + + + + + +bool Xdrv34(uint8_t function) +{ + if (!I2cEnabled(XI2C_44)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + WMotorV1Detect(); + } + else if (WMotorV1.detected) { + switch (function) { + case FUNC_COMMAND_DRIVER: + if (XI2C_44 == XdrvMailbox.index) { + result = WMotorV1Command(); + } + break; + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_99_debug.ino" +# 22 "S:/Development/Tasmota/tasmota/xdrv_99_debug.ino" +#ifdef DEBUG_THEO +#ifndef USE_DEBUG_DRIVER +#define USE_DEBUG_DRIVER +#endif +#endif + +#ifdef USE_DEBUG_DRIVER + + + + + + +#define XDRV_99 99 + +#ifndef CPU_LOAD_CHECK +#define CPU_LOAD_CHECK 1 +#endif + + + + + +#define D_CMND_CFGDUMP "CfgDump" +#define D_CMND_CFGPEEK "CfgPeek" +#define D_CMND_CFGPOKE "CfgPoke" +#define D_CMND_CFGSHOW "CfgShow" +#define D_CMND_CFGXOR "CfgXor" +#define D_CMND_CPUCHECK "CpuChk" +#define D_CMND_EXCEPTION "Exception" +#define D_CMND_FLASHDUMP "FlashDump" +#define D_CMND_FLASHMODE "FlashMode" +#define D_CMND_FREEMEM "FreeMem" +#define D_CMND_HELP "Help" +#define D_CMND_RTCDUMP "RtcDump" +#define D_CMND_SETSENSOR "SetSensor" +#define D_CMND_I2CWRITE "I2CWrite" +#define D_CMND_I2CREAD "I2CRead" +#define D_CMND_I2CSTRETCH "I2CStretch" +#define D_CMND_I2CCLOCK "I2CClock" + +const char kDebugCommands[] PROGMEM = "|" + D_CMND_CFGDUMP "|" D_CMND_CFGPEEK "|" D_CMND_CFGPOKE "|" +#ifdef USE_WEBSERVER + D_CMND_CFGXOR "|" +#endif + D_CMND_CPUCHECK "|" +#ifdef DEBUG_THEO + D_CMND_EXCEPTION "|" +#endif + D_CMND_FLASHDUMP "|" D_CMND_FLASHMODE "|" D_CMND_FREEMEM"|" D_CMND_HELP "|" D_CMND_RTCDUMP "|" D_CMND_SETSENSOR "|" +#ifdef USE_I2C + D_CMND_I2CWRITE "|" D_CMND_I2CREAD "|" D_CMND_I2CSTRETCH "|" D_CMND_I2CCLOCK +#endif + ; + +void (* const DebugCommand[])(void) PROGMEM = { + &CmndCfgDump, &CmndCfgPeek, &CmndCfgPoke, +#ifdef USE_WEBSERVER + &CmndCfgXor, +#endif + &CmndCpuCheck, +#ifdef DEBUG_THEO + &CmndException, +#endif + &CmndFlashDump, &CmndFlashMode, &CmndFreemem, &CmndHelp, &CmndRtcDump, &CmndSetSensor, +#ifdef USE_I2C + &CmndI2cWrite, &CmndI2cRead, &CmndI2cStretch, &CmndI2cClock +#endif + }; + +uint32_t CPU_loops = 0; +uint32_t CPU_last_millis = 0; +uint32_t CPU_last_loop_time = 0; +uint8_t CPU_load_check = 0; +uint8_t CPU_show_freemem = 0; + + + +#ifdef DEBUG_THEO +void ExceptionTest(uint8_t type) +{ +# 145 "S:/Development/Tasmota/tasmota/xdrv_99_debug.ino" + if (1 == type) { + char svalue[10]; + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), 7); + } +# 159 "S:/Development/Tasmota/tasmota/xdrv_99_debug.ino" + if (2 == type) { + while(1) delay(1000); + } +} + +#endif + + + +void CpuLoadLoop(void) +{ + CPU_last_loop_time = millis(); + if (CPU_load_check && CPU_last_millis) { + CPU_loops ++; + if ((CPU_last_millis + (CPU_load_check *1000)) <= CPU_last_loop_time) { +#if defined(F_CPU) && (F_CPU == 160000000L) + int CPU_load = 100 - ( (CPU_loops*(1 + 30*sleep)) / (CPU_load_check *800) ); + CPU_loops = CPU_loops / CPU_load_check; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, CPU %d%%(160MHz), Loops/sec %d"), ESP.getFreeHeap(), CPU_load, CPU_loops); +#else + int CPU_load = 100 - ( (CPU_loops*(1 + 30*sleep)) / (CPU_load_check *400) ); + CPU_loops = CPU_loops / CPU_load_check; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, CPU %d%%(80MHz), Loops/sec %d"), ESP.getFreeHeap(), CPU_load, CPU_loops); +#endif + CPU_last_millis = CPU_last_loop_time; + CPU_loops = 0; + } + } +} + + + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) + + + +extern "C" { +#include + extern cont_t g_cont; +} + +void DebugFreeMem(void) +{ + register uint32_t *sp asm("a1"); + + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, FreeStack %d (%s)"), ESP.getFreeHeap(), 4 * (sp - g_cont.stack), XdrvMailbox.data); +} + +#else + + + + +extern "C" { +#include + extern cont_t* g_pcont; +} + +void DebugFreeMem(void) +{ + register uint32_t *sp asm("a1"); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, FreeStack %d (%s)"), ESP.getFreeHeap(), 4 * (sp - g_pcont->stack), XdrvMailbox.data); +} + +#endif + + + +void DebugRtcDump(char* parms) +{ + #define CFG_COLS 16 + + uint16_t idx; + uint16_t maxrow; + uint16_t row; + uint16_t col; + char *p; +# 246 "S:/Development/Tasmota/tasmota/xdrv_99_debug.ino" + uint8_t buffer[768]; + + system_rtc_mem_read(0, (uint32_t*)&buffer, sizeof(buffer)); + + maxrow = ((sizeof(buffer)+CFG_COLS)/CFG_COLS); + + uint16_t srow = strtol(parms, &p, 16) / CFG_COLS; + uint16_t mrow = strtol(p, &p, 10); + + + + if (0 == mrow) { + mrow = 8; + } + if (srow > maxrow) { + srow = maxrow - mrow; + } + if (mrow < (maxrow - srow)) { + maxrow = srow + mrow; + } + + for (row = srow; row < maxrow; row++) { + idx = row * CFG_COLS; + snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), idx); + for (col = 0; col < CFG_COLS; col++) { + if (!(col%4)) { + snprintf_P(log_data, sizeof(log_data), PSTR("%s "), log_data); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[idx + col]); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data); + for (col = 0; col < CFG_COLS; col++) { + + + + snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' '); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s|"), log_data); + AddLog(LOG_LEVEL_INFO); + } +} + + + +void DebugCfgDump(char* parms) +{ + #define CFG_COLS 16 + + uint16_t idx; + uint16_t maxrow; + uint16_t row; + uint16_t col; + char *p; + + uint8_t *buffer = (uint8_t *) &Settings; + maxrow = ((sizeof(SYSCFG)+CFG_COLS)/CFG_COLS); + + uint16_t srow = strtol(parms, &p, 16) / CFG_COLS; + uint16_t mrow = strtol(p, &p, 10); + + + + if (0 == mrow) { + mrow = 8; + } + if (srow > maxrow) { + srow = maxrow - mrow; + } + if (mrow < (maxrow - srow)) { + maxrow = srow + mrow; + } + + for (row = srow; row < maxrow; row++) { + idx = row * CFG_COLS; + snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), idx); + for (col = 0; col < CFG_COLS; col++) { + if (!(col%4)) { + snprintf_P(log_data, sizeof(log_data), PSTR("%s "), log_data); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[idx + col]); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data); + for (col = 0; col < CFG_COLS; col++) { + + + + snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' '); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s|"), log_data); + AddLog(LOG_LEVEL_INFO); + delay(1); + } +} + +void DebugCfgPeek(char* parms) +{ + char *p; + + uint16_t address = strtol(parms, &p, 16); + if (address > sizeof(SYSCFG)) address = sizeof(SYSCFG) -4; + address = (address >> 2) << 2; + + uint8_t *buffer = (uint8_t *) &Settings; + uint8_t data8 = buffer[address]; + uint16_t data16 = (buffer[address +1] << 8) + buffer[address]; + uint32_t data32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + data16; + + snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), address); + for (uint32_t i = 0; i < 4; i++) { + snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[address +i]); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data); + for (uint32_t i = 0; i < 4; i++) { + snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[address +i] > 0x20) && (buffer[address +i] < 0x7F)) ? (char)buffer[address +i] : ' '); + } + snprintf_P(log_data, sizeof(log_data), PSTR("%s| 0x%02X (%d), 0x%04X (%d), 0x%0LX (%lu)"), log_data, data8, data8, data16, data16, data32, data32); + AddLog(LOG_LEVEL_INFO); +} + +void DebugCfgPoke(char* parms) +{ + char *p; + + uint16_t address = strtol(parms, &p, 16); + if (address > sizeof(SYSCFG)) address = sizeof(SYSCFG) -4; + address = (address >> 2) << 2; + + uint32_t data = strtol(p, &p, 16); + + uint8_t *buffer = (uint8_t *) &Settings; + uint32_t data32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + (buffer[address +1] << 8) + buffer[address]; + + uint8_t *nbuffer = (uint8_t *) &data; + for (uint32_t i = 0; i < 4; i++) { buffer[address +i] = nbuffer[+i]; } + + uint32_t ndata32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + (buffer[address +1] << 8) + buffer[address]; + + AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: 0x%0LX (%lu) poked to 0x%0LX (%lu)"), address, data32, data32, ndata32, ndata32); +} + +void SetFlashMode(uint8_t mode) +{ + uint8_t *_buffer; + uint32_t address; + + address = 0; + _buffer = new uint8_t[FLASH_SECTOR_SIZE]; + + if (ESP.flashRead(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE)) { + if (_buffer[2] != mode) { + _buffer[2] = mode; + if (ESP.flashEraseSector(address / FLASH_SECTOR_SIZE)) { + ESP.flashWrite(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE); + } + } + } + delete[] _buffer; +} + + + + + +void CmndHelp(void) +{ + AddLog_P(LOG_LEVEL_INFO, PSTR("HLP: "), kDebugCommands); + ResponseCmndDone(); +} + +void CmndRtcDump(void) +{ + DebugRtcDump(XdrvMailbox.data); + ResponseCmndDone(); +} + +void CmndCfgDump(void) +{ + DebugCfgDump(XdrvMailbox.data); + ResponseCmndDone(); +} + +void CmndCfgPeek(void) +{ + DebugCfgPeek(XdrvMailbox.data); + ResponseCmndDone(); +} + +void CmndCfgPoke(void) +{ + DebugCfgPoke(XdrvMailbox.data); + ResponseCmndDone(); +} + +#ifdef USE_WEBSERVER +void CmndCfgXor(void) +{ + if (XdrvMailbox.data_len > 0) { + Web.config_xor_on_set = XdrvMailbox.payload; + } + ResponseCmndNumber(Web.config_xor_on_set); +} +#endif + +#ifdef DEBUG_THEO +void CmndException(void) +{ + if (XdrvMailbox.data_len > 0) { ExceptionTest(XdrvMailbox.payload); } + ResponseCmndDone(); +} +#endif + +void CmndCpuCheck(void) +{ + if (XdrvMailbox.data_len > 0) { + CPU_load_check = XdrvMailbox.payload; + CPU_last_millis = CPU_last_loop_time; + } + ResponseCmndNumber(CPU_load_check); +} + +void CmndFreemem(void) +{ + if (XdrvMailbox.data_len > 0) { + CPU_show_freemem = XdrvMailbox.payload; + } + ResponseCmndNumber(CPU_show_freemem); +} + +void CmndSetSensor(void) +{ + if (XdrvMailbox.index < MAX_XSNS_DRIVERS) { + if (XdrvMailbox.payload >= 0) { + bitWrite(Settings.sensors[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1); + if (1 == XdrvMailbox.payload) { + restart_flag = 2; + } + } + Response_P(PSTR("{\"" D_CMND_SETSENSOR "\":")); + XsnsSensorState(); + ResponseJsonEnd(); + } +} + +void CmndFlashMode(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { + SetFlashMode(XdrvMailbox.payload); + } + ResponseCmndNumber(ESP.getFlashChipMode()); +} + +uint32_t DebugSwap32(uint32_t x) { + return ((x << 24) & 0xff000000 ) | + ((x << 8) & 0x00ff0000 ) | + ((x >> 8) & 0x0000ff00 ) | + ((x >> 24) & 0x000000ff ); +} + +void CmndFlashDump(void) +{ + + + + const uint32_t flash_start = 0x40200000; + const uint8_t bytes_per_cols = 0x20; + const uint32_t max = (SPIFFS_END + 5) * SPI_FLASH_SEC_SIZE; + + uint32_t start = flash_start; + uint32_t rows = 8; + + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= (max - bytes_per_cols))) { + start += (XdrvMailbox.payload &0x7FFFFFFC); + + char *p; + uint32_t is_payload = strtol(XdrvMailbox.data, &p, 16); + rows = strtol(p, &p, 10); + if (0 == rows) { rows = 8; } + } + uint32_t end = start + (rows * bytes_per_cols); + if ((end - flash_start) > max) { + end = flash_start + max; + } + + for (uint32_t pos = start; pos < end; pos += bytes_per_cols) { + uint32_t* values = (uint32_t*)(pos); + AddLog_P2(LOG_LEVEL_INFO, PSTR("%06X: %08X %08X %08X %08X %08X %08X %08X %08X"), pos - flash_start, + DebugSwap32(values[0]), DebugSwap32(values[1]), DebugSwap32(values[2]), DebugSwap32(values[3]), + DebugSwap32(values[4]), DebugSwap32(values[5]), DebugSwap32(values[6]), DebugSwap32(values[7])); + } + ResponseCmndDone(); +} + +#ifdef USE_I2C +void CmndI2cWrite(void) +{ + + if (i2c_flg) { + char* parms = XdrvMailbox.data; + uint8_t buffer[100]; + uint32_t index = 0; + + char *p; + char *data = strtok_r(parms, " ,", &p); + while (data != NULL && index < sizeof(buffer)) { + buffer[index++] = strtol(data, nullptr, 16); + data = strtok_r(nullptr, " ,", &p); + } + + if (index > 1) { + AddLogBuffer(LOG_LEVEL_INFO, buffer, index); + + Wire.beginTransmission(buffer[0]); + for (uint32_t i = 1; i < index; i++) { + Wire.write(buffer[i]); + } + int result = Wire.endTransmission(); + AddLog_P2(LOG_LEVEL_INFO, PSTR("I2C: Result %d"), result); + } + } + ResponseCmndDone(); +} + +void CmndI2cRead(void) +{ + + if (i2c_flg) { + char* parms = XdrvMailbox.data; + uint8_t buffer[100]; + uint32_t index = 0; + + char *p; + char *data = strtok_r(parms, " ,", &p); + while (data != NULL && index < sizeof(buffer)) { + buffer[index++] = strtol(data, nullptr, 16); + data = strtok_r(nullptr, " ,", &p); + } + + if (index > 0) { + uint8_t size = 1; + if (index > 1) { + size = buffer[1]; + } + Wire.requestFrom(buffer[0], size); + index = 0; + while (Wire.available() && index < sizeof(buffer)) { + buffer[index++] = Wire.read(); + } + if (index > 0) { + AddLogBuffer(LOG_LEVEL_INFO, buffer, index); + } + } + } + ResponseCmndDone(); +} + +void CmndI2cStretch(void) +{ + if (i2c_flg && (XdrvMailbox.payload > 0)) { + Wire.setClockStretchLimit(XdrvMailbox.payload); + } + ResponseCmndDone(); +} + +void CmndI2cClock(void) +{ + if (i2c_flg && (XdrvMailbox.payload > 0)) { + Wire.setClock(XdrvMailbox.payload); + } + ResponseCmndDone(); +} +#endif + + + + + +bool Xdrv99(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_LOOP: + CpuLoadLoop(); + break; + case FUNC_FREE_MEM: + if (CPU_show_freemem) { DebugFreeMem(); } + break; + case FUNC_PRE_INIT: + CPU_last_millis = millis(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kDebugCommands, DebugCommand); + break; + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xdrv_interface.ino" +# 20 "S:/Development/Tasmota/tasmota/xdrv_interface.ino" +#ifdef XFUNC_PTR_IN_ROM +bool (* const xdrv_func_ptr[])(uint8_t) PROGMEM = { +#else +bool (* const xdrv_func_ptr[])(uint8_t) = { +#endif + +#ifdef XDRV_01 + &Xdrv01, +#endif + +#ifdef XDRV_02 + &Xdrv02, +#endif + +#ifdef XDRV_03 + &Xdrv03, +#endif + +#ifdef XDRV_04 + &Xdrv04, +#endif + +#ifdef XDRV_05 + &Xdrv05, +#endif + +#ifdef XDRV_06 + &Xdrv06, +#endif + +#ifdef XDRV_07 + &Xdrv07, +#endif + +#ifdef XDRV_08 + &Xdrv08, +#endif + +#ifdef XDRV_09 + &Xdrv09, +#endif + +#ifdef XDRV_10 + &Xdrv10, +#endif + +#ifdef XDRV_11 + &Xdrv11, +#endif + +#ifdef XDRV_12 + &Xdrv12, +#endif + +#ifdef XDRV_13 + &Xdrv13, +#endif + +#ifdef XDRV_14 + &Xdrv14, +#endif + +#ifdef XDRV_15 + &Xdrv15, +#endif + +#ifdef XDRV_16 + &Xdrv16, +#endif + +#ifdef XDRV_17 + &Xdrv17, +#endif + +#ifdef XDRV_18 + &Xdrv18, +#endif + +#ifdef XDRV_19 + &Xdrv19, +#endif + +#ifdef XDRV_20 + &Xdrv20, +#endif + +#ifdef XDRV_21 + &Xdrv21, +#endif + +#ifdef XDRV_22 + &Xdrv22, +#endif + +#ifdef XDRV_23 + &Xdrv23, +#endif + +#ifdef XDRV_24 + &Xdrv24, +#endif + +#ifdef XDRV_25 + &Xdrv25, +#endif + +#ifdef XDRV_26 + &Xdrv26, +#endif + +#ifdef XDRV_27 + &Xdrv27, +#endif + +#ifdef XDRV_28 + &Xdrv28, +#endif + +#ifdef XDRV_29 + &Xdrv29, +#endif + +#ifdef XDRV_30 + &Xdrv30, +#endif + +#ifdef XDRV_31 + &Xdrv31, +#endif + +#ifdef XDRV_32 + &Xdrv32, +#endif + +#ifdef XDRV_33 + &Xdrv33, +#endif + +#ifdef XDRV_34 + &Xdrv34, +#endif + +#ifdef XDRV_35 + &Xdrv35, +#endif + +#ifdef XDRV_36 + &Xdrv36, +#endif + +#ifdef XDRV_37 + &Xdrv37, +#endif + +#ifdef XDRV_38 + &Xdrv38, +#endif + +#ifdef XDRV_39 + &Xdrv39, +#endif + +#ifdef XDRV_40 + &Xdrv40, +#endif + +#ifdef XDRV_41 + &Xdrv41, +#endif + +#ifdef XDRV_42 + &Xdrv42, +#endif + +#ifdef XDRV_43 + &Xdrv43, +#endif + +#ifdef XDRV_44 + &Xdrv44, +#endif + +#ifdef XDRV_45 + &Xdrv45, +#endif + +#ifdef XDRV_46 + &Xdrv46, +#endif + +#ifdef XDRV_47 + &Xdrv47, +#endif + +#ifdef XDRV_48 + &Xdrv48, +#endif + +#ifdef XDRV_49 + &Xdrv49, +#endif + +#ifdef XDRV_50 + &Xdrv50, +#endif + +#ifdef XDRV_51 + &Xdrv51, +#endif + +#ifdef XDRV_52 + &Xdrv52, +#endif + +#ifdef XDRV_53 + &Xdrv53, +#endif + +#ifdef XDRV_54 + &Xdrv54, +#endif + +#ifdef XDRV_55 + &Xdrv55, +#endif + +#ifdef XDRV_56 + &Xdrv56, +#endif + +#ifdef XDRV_57 + &Xdrv57, +#endif + +#ifdef XDRV_58 + &Xdrv58, +#endif + +#ifdef XDRV_59 + &Xdrv59, +#endif + +#ifdef XDRV_60 + &Xdrv60, +#endif + +#ifdef XDRV_61 + &Xdrv61, +#endif + +#ifdef XDRV_62 + &Xdrv62, +#endif + +#ifdef XDRV_63 + &Xdrv63, +#endif + +#ifdef XDRV_64 + &Xdrv64, +#endif + +#ifdef XDRV_65 + &Xdrv65, +#endif + +#ifdef XDRV_66 + &Xdrv66, +#endif + +#ifdef XDRV_67 + &Xdrv67, +#endif + +#ifdef XDRV_68 + &Xdrv68, +#endif + +#ifdef XDRV_69 + &Xdrv69, +#endif + +#ifdef XDRV_70 + &Xdrv70, +#endif + +#ifdef XDRV_71 + &Xdrv71, +#endif + +#ifdef XDRV_72 + &Xdrv72, +#endif + +#ifdef XDRV_73 + &Xdrv73, +#endif + +#ifdef XDRV_74 + &Xdrv74, +#endif + +#ifdef XDRV_75 + &Xdrv75, +#endif + +#ifdef XDRV_76 + &Xdrv76, +#endif + +#ifdef XDRV_77 + &Xdrv77, +#endif + +#ifdef XDRV_78 + &Xdrv78, +#endif + +#ifdef XDRV_79 + &Xdrv79, +#endif + +#ifdef XDRV_80 + &Xdrv80, +#endif + +#ifdef XDRV_81 + &Xdrv81, +#endif + +#ifdef XDRV_82 + &Xdrv82, +#endif + +#ifdef XDRV_83 + &Xdrv83, +#endif + +#ifdef XDRV_84 + &Xdrv84, +#endif + +#ifdef XDRV_85 + &Xdrv85, +#endif + +#ifdef XDRV_86 + &Xdrv86, +#endif + +#ifdef XDRV_87 + &Xdrv87, +#endif + +#ifdef XDRV_88 + &Xdrv88, +#endif + +#ifdef XDRV_89 + &Xdrv89, +#endif + +#ifdef XDRV_90 + &Xdrv90, +#endif + +#ifdef XDRV_91 + &Xdrv91, +#endif + +#ifdef XDRV_92 + &Xdrv92, +#endif + +#ifdef XDRV_93 + &Xdrv93, +#endif + +#ifdef XDRV_94 + &Xdrv94, +#endif + +#ifdef XDRV_95 + &Xdrv95, +#endif + +#ifdef XDRV_96 + &Xdrv96, +#endif + +#ifdef XDRV_97 + &Xdrv97, +#endif + +#ifdef XDRV_98 + &Xdrv98, +#endif + +#ifdef XDRV_99 + &Xdrv99 +#endif +}; + +const uint8_t xdrv_present = sizeof(xdrv_func_ptr) / sizeof(xdrv_func_ptr[0]); + + + + + +#ifdef XFUNC_PTR_IN_ROM +const uint8_t kXdrvList[] PROGMEM = { +#else +const uint8_t kXdrvList[] = { +#endif + +#ifdef XDRV_01 + XDRV_01, +#endif + +#ifdef XDRV_02 + XDRV_02, +#endif + +#ifdef XDRV_03 + XDRV_03, +#endif + +#ifdef XDRV_04 + XDRV_04, +#endif + +#ifdef XDRV_05 + XDRV_05, +#endif + +#ifdef XDRV_06 + XDRV_06, +#endif + +#ifdef XDRV_07 + XDRV_07, +#endif + +#ifdef XDRV_08 + XDRV_08, +#endif + +#ifdef XDRV_09 + XDRV_09, +#endif + +#ifdef XDRV_10 + XDRV_10, +#endif + +#ifdef XDRV_11 + XDRV_11, +#endif + +#ifdef XDRV_12 + XDRV_12, +#endif + +#ifdef XDRV_13 + XDRV_13, +#endif + +#ifdef XDRV_14 + XDRV_14, +#endif + +#ifdef XDRV_15 + XDRV_15, +#endif + +#ifdef XDRV_16 + XDRV_16, +#endif + +#ifdef XDRV_17 + XDRV_17, +#endif + +#ifdef XDRV_18 + XDRV_18, +#endif + +#ifdef XDRV_19 + XDRV_19, +#endif + +#ifdef XDRV_20 + XDRV_20, +#endif + +#ifdef XDRV_21 + XDRV_21, +#endif + +#ifdef XDRV_22 + XDRV_22, +#endif + +#ifdef XDRV_23 + XDRV_23, +#endif + +#ifdef XDRV_24 + XDRV_24, +#endif + +#ifdef XDRV_25 + XDRV_25, +#endif + +#ifdef XDRV_26 + XDRV_26, +#endif + +#ifdef XDRV_27 + XDRV_27, +#endif + +#ifdef XDRV_28 + XDRV_28, +#endif + +#ifdef XDRV_29 + XDRV_29, +#endif + +#ifdef XDRV_30 + XDRV_30, +#endif + +#ifdef XDRV_31 + XDRV_31, +#endif + +#ifdef XDRV_32 + XDRV_32, +#endif + +#ifdef XDRV_33 + XDRV_33, +#endif + +#ifdef XDRV_34 + XDRV_34, +#endif + +#ifdef XDRV_35 + XDRV_35, +#endif + +#ifdef XDRV_36 + XDRV_36, +#endif + +#ifdef XDRV_37 + XDRV_37, +#endif + +#ifdef XDRV_38 + XDRV_38, +#endif + +#ifdef XDRV_39 + XDRV_39, +#endif + +#ifdef XDRV_40 + XDRV_40, +#endif + +#ifdef XDRV_41 + XDRV_41, +#endif + +#ifdef XDRV_42 + XDRV_42, +#endif + +#ifdef XDRV_43 + XDRV_43, +#endif + +#ifdef XDRV_44 + XDRV_44, +#endif + +#ifdef XDRV_45 + XDRV_45, +#endif + +#ifdef XDRV_46 + XDRV_46, +#endif + +#ifdef XDRV_47 + XDRV_47, +#endif + +#ifdef XDRV_48 + XDRV_48, +#endif + +#ifdef XDRV_49 + XDRV_49, +#endif + +#ifdef XDRV_50 + XDRV_50, +#endif + +#ifdef XDRV_51 + XDRV_51, +#endif + +#ifdef XDRV_52 + XDRV_52, +#endif + +#ifdef XDRV_53 + XDRV_53, +#endif + +#ifdef XDRV_54 + XDRV_54, +#endif + +#ifdef XDRV_55 + XDRV_55, +#endif + +#ifdef XDRV_56 + XDRV_56, +#endif + +#ifdef XDRV_57 + XDRV_57, +#endif + +#ifdef XDRV_58 + XDRV_58, +#endif + +#ifdef XDRV_59 + XDRV_59, +#endif + +#ifdef XDRV_60 + XDRV_60, +#endif + +#ifdef XDRV_61 + XDRV_61, +#endif + +#ifdef XDRV_62 + XDRV_62, +#endif + +#ifdef XDRV_63 + XDRV_63, +#endif + +#ifdef XDRV_64 + XDRV_64, +#endif + +#ifdef XDRV_65 + XDRV_65, +#endif + +#ifdef XDRV_66 + XDRV_66, +#endif + +#ifdef XDRV_67 + XDRV_67, +#endif + +#ifdef XDRV_68 + XDRV_68, +#endif + +#ifdef XDRV_69 + XDRV_69, +#endif + +#ifdef XDRV_70 + XDRV_70, +#endif + +#ifdef XDRV_71 + XDRV_71, +#endif + +#ifdef XDRV_72 + XDRV_72, +#endif + +#ifdef XDRV_73 + XDRV_73, +#endif + +#ifdef XDRV_74 + XDRV_74, +#endif + +#ifdef XDRV_75 + XDRV_75, +#endif + +#ifdef XDRV_76 + XDRV_76, +#endif + +#ifdef XDRV_77 + XDRV_77, +#endif + +#ifdef XDRV_78 + XDRV_78, +#endif + +#ifdef XDRV_79 + XDRV_79, +#endif + +#ifdef XDRV_80 + XDRV_80, +#endif + +#ifdef XDRV_81 + XDRV_81, +#endif + +#ifdef XDRV_82 + XDRV_82, +#endif + +#ifdef XDRV_83 + XDRV_83, +#endif + +#ifdef XDRV_84 + XDRV_84, +#endif + +#ifdef XDRV_85 + XDRV_85, +#endif + +#ifdef XDRV_86 + XDRV_86, +#endif + +#ifdef XDRV_87 + XDRV_87, +#endif + +#ifdef XDRV_88 + XDRV_88, +#endif + +#ifdef XDRV_89 + XDRV_89, +#endif + +#ifdef XDRV_90 + XDRV_90, +#endif + +#ifdef XDRV_91 + XDRV_91, +#endif + +#ifdef XDRV_92 + XDRV_92, +#endif + +#ifdef XDRV_93 + XDRV_93, +#endif + +#ifdef XDRV_94 + XDRV_94, +#endif + +#ifdef XDRV_95 + XDRV_95, +#endif + +#ifdef XDRV_96 + XDRV_96, +#endif + +#ifdef XDRV_97 + XDRV_97, +#endif + +#ifdef XDRV_98 + XDRV_98, +#endif + +#ifdef XDRV_99 + XDRV_99 +#endif +}; + + + +void XsnsDriverState(void) +{ + ResponseAppend_P(PSTR(",\"Drivers\":\"")); + for (uint32_t i = 0; i < sizeof(kXdrvList); i++) { +#ifdef XFUNC_PTR_IN_ROM + uint32_t driverid = pgm_read_byte(kXdrvList + i); +#else + uint32_t driverid = kXdrvList[i]; +#endif + ResponseAppend_P(PSTR("%s%d"), (i) ? "," : "", driverid); + } + ResponseAppend_P(PSTR("\"")); +} + + + +bool XdrvRulesProcess(void) +{ + return XdrvCallDriver(10, FUNC_RULES_PROCESS); +} + +#ifdef USE_DEBUG_DRIVER +void ShowFreeMem(const char *where) +{ + char stemp[30]; + snprintf_P(stemp, sizeof(stemp), where); + XdrvMailbox.data = stemp; + XdrvCall(FUNC_FREE_MEM); +} +#endif + + + + + +bool XdrvCallDriver(uint32_t driver, uint8_t Function) +{ + for (uint32_t x = 0; x < xdrv_present; x++) { +#ifdef XFUNC_PTR_IN_ROM + uint32_t listed = pgm_read_byte(kXdrvList + x); +#else + uint32_t listed = kXdrvList[x]; +#endif + if (driver == listed) { + return xdrv_func_ptr[x](Function); + } + } + return false; +} + + + + + +bool XdrvCall(uint8_t Function) +{ + bool result = false; + + DEBUG_TRACE_LOG(PSTR("DRV: %d"), Function); + + for (uint32_t x = 0; x < xdrv_present; x++) { + result = xdrv_func_ptr[x](Function); + + if (result && ((FUNC_COMMAND == Function) || + (FUNC_COMMAND_DRIVER == Function) || + (FUNC_MQTT_DATA == Function) || + (FUNC_RULES_PROCESS == Function) || + (FUNC_BUTTON_PRESSED == Function) || + (FUNC_SERIAL == Function) || + (FUNC_MODULE_INIT == Function) || + (FUNC_SET_CHANNELS == Function) || + (FUNC_PIN_STATE == Function) || + (FUNC_SET_DEVICE_POWER == Function) + )) { + break; + } + } + + return result; +} +# 1 "S:/Development/Tasmota/tasmota/xdsp_01_lcd.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_01_lcd.ino" +#ifdef USE_I2C +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_LCD + +#define XDSP_01 1 +#define XI2C_03 3 + +#define LCD_ADDRESS1 0x27 +#define LCD_ADDRESS2 0x3F + +#include +#include + +LiquidCrystal_I2C *lcd; + + + +void LcdInitMode(void) +{ + lcd->init(); + lcd->clear(); +} + +void LcdInit(uint8_t mode) +{ + switch(mode) { + case DISPLAY_INIT_MODE: + LcdInitMode(); +#ifdef USE_DISPLAY_MODES1TO5 + DisplayClearScreenBuffer(); +#endif + break; + case DISPLAY_INIT_PARTIAL: + case DISPLAY_INIT_FULL: + break; + } +} + +void LcdInitDriver(void) +{ + if (!Settings.display_model) { + if (I2cSetDevice(LCD_ADDRESS1)) { + Settings.display_address[0] = LCD_ADDRESS1; + Settings.display_model = XDSP_01; + } + else if (I2cSetDevice(LCD_ADDRESS2)) { + Settings.display_address[0] = LCD_ADDRESS2; + Settings.display_model = XDSP_01; + } + } + + if (XDSP_01 == Settings.display_model) { + I2cSetActiveFound(Settings.display_address[0], "LCD"); + + Settings.display_width = Settings.display_cols[0]; + Settings.display_height = Settings.display_rows; + lcd = new LiquidCrystal_I2C(Settings.display_address[0], Settings.display_cols[0], Settings.display_rows); + +#ifdef USE_DISPLAY_MODES1TO5 + DisplayAllocScreenBuffer(); +#endif + + LcdInitMode(); + } +} + +void LcdDrawStringAt(void) +{ + if (dsp_flag) { + dsp_x--; + dsp_y--; + } + lcd->setCursor(dsp_x, dsp_y); + lcd->print(dsp_str); +} + +void LcdDisplayOnOff(uint8_t on) +{ + if (on) { + lcd->backlight(); + } else { + lcd->noBacklight(); + } +} + + + +#ifdef USE_DISPLAY_MODES1TO5 + +void LcdCenter(uint8_t row, char* txt) +{ + char line[Settings.display_cols[0] +2]; + + int len = strlen(txt); + int offset = 0; + if (len >= Settings.display_cols[0]) { + len = Settings.display_cols[0]; + } else { + offset = (Settings.display_cols[0] - len) / 2; + } + memset(line, 0x20, Settings.display_cols[0]); + line[Settings.display_cols[0]] = 0; + for (uint32_t i = 0; i < len; i++) { + line[offset +i] = txt[i]; + } + lcd->setCursor(0, row); + lcd->print(line); +} + +bool LcdPrintLog(void) +{ + bool result = false; + + disp_refresh--; + if (!disp_refresh) { + disp_refresh = Settings.display_refresh; + if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } + + char* txt = DisplayLogBuffer('\337'); + if (txt != nullptr) { + uint8_t last_row = Settings.display_rows -1; + + for (uint32_t i = 0; i < last_row; i++) { + strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); + lcd->setCursor(0, i); + lcd->print(disp_screen_buffer[i +1]); + } + strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); + DisplayFillScreen(last_row); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); + + lcd->setCursor(0, last_row); + lcd->print(disp_screen_buffer[last_row]); + + result = true; + } + } + return result; +} + +void LcdTime(void) +{ + char line[Settings.display_cols[0] +1]; + + snprintf_P(line, sizeof(line), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); + LcdCenter(0, line); + snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); + LcdCenter(1, line); +} + +void LcdRefresh(void) +{ + if (Settings.display_mode) { + switch (Settings.display_mode) { + case 1: + LcdTime(); + break; + case 2: + case 4: + LcdPrintLog(); + break; + case 3: + case 5: { + if (!LcdPrintLog()) { LcdTime(); } + break; + } + } + } +} + +#endif + + + + + +bool Xdsp01(uint8_t function) +{ + if (!I2cEnabled(XI2C_03)) { return false; } + + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + LcdInitDriver(); + } + else if (XDSP_01 == Settings.display_model) { + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; + case FUNC_DISPLAY_INIT: + LcdInit(dsp_init); + break; + case FUNC_DISPLAY_POWER: + LcdDisplayOnOff(disp_power); + break; + case FUNC_DISPLAY_CLEAR: + lcd->clear(); + break; +# 238 "S:/Development/Tasmota/tasmota/xdsp_01_lcd.ino" + case FUNC_DISPLAY_DRAW_STRING: + LcdDrawStringAt(); + break; + case FUNC_DISPLAY_ONOFF: + LcdDisplayOnOff(dsp_on); + break; + + +#ifdef USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_EVERY_SECOND: + LcdRefresh(); + break; +#endif + } + } + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_02_ssd1306.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_02_ssd1306.ino" +#ifdef USE_I2C +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_SSD1306 + +#define XDSP_02 2 +#define XI2C_04 4 + +#define OLED_RESET 4 + +#define SPRINT(A) char str[32];sprintf(str,"val: %d ",A);Serial.println((char*)str); + +#define OLED_ADDRESS1 0x3C +#define OLED_ADDRESS2 0x3D + +#define OLED_BUFFER_COLS 40 +#define OLED_BUFFER_ROWS 16 + +#define OLED_FONT_WIDTH 6 +#define OLED_FONT_HEIGTH 8 + +#include +#include +#include + +Adafruit_SSD1306 *oled1306; + +extern uint8_t *buffer; + + + +void SSD1306InitDriver(void) +{ + if (!Settings.display_model) { + if (I2cSetDevice(OLED_ADDRESS1)) { + Settings.display_address[0] = OLED_ADDRESS1; + Settings.display_model = XDSP_02; + } + else if (I2cSetDevice(OLED_ADDRESS2)) { + Settings.display_address[0] = OLED_ADDRESS2; + Settings.display_model = XDSP_02; + } + } + + if (XDSP_02 == Settings.display_model) { + I2cSetActiveFound(Settings.display_address[0], "SSD1306"); + + if ((Settings.display_width != 64) && (Settings.display_width != 96) && (Settings.display_width != 128)) { + Settings.display_width = 128; + } + if ((Settings.display_height != 16) && (Settings.display_height != 32) && (Settings.display_height != 48) && (Settings.display_height != 64)) { + Settings.display_height = 64; + } + + uint8_t reset_pin = -1; + if (pin[GPIO_OLED_RESET] < 99) { + reset_pin = pin[GPIO_OLED_RESET]; + } + + + if (buffer) { free(buffer); } + buffer = (unsigned char*)calloc((Settings.display_width * Settings.display_height) / 8,1); + if (!buffer) { return; } + + + + oled1306 = new Adafruit_SSD1306(Settings.display_width, Settings.display_height, &Wire, reset_pin); + oled1306->begin(SSD1306_SWITCHCAPVCC, Settings.display_address[0], reset_pin >= 0); + renderer = oled1306; + renderer->DisplayInit(DISPLAY_INIT_MODE, Settings.display_size, Settings.display_rotate, Settings.display_font); + renderer->setTextColor(1,0); + +#ifdef SHOW_SPLASH + renderer->setTextFont(0); + renderer->setTextSize(2); + renderer->setCursor(20,20); + renderer->println(F("SSD1306")); + renderer->Updateframe(); + renderer->DisplayOnff(1); +#endif + + } +} + + +#ifdef USE_DISPLAY_MODES1TO5 + +void Ssd1306PrintLog(void) +{ + disp_refresh--; + if (!disp_refresh) { + disp_refresh = Settings.display_refresh; + if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } + + char* txt = DisplayLogBuffer('\370'); + if (txt != NULL) { + uint8_t last_row = Settings.display_rows -1; + + renderer->clearDisplay(); + renderer->setTextSize(Settings.display_size); + renderer->setCursor(0,0); + for (byte i = 0; i < last_row; i++) { + strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); + renderer->println(disp_screen_buffer[i]); + } + strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); + DisplayFillScreen(last_row); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); + + renderer->println(disp_screen_buffer[last_row]); + renderer->Updateframe(); + } + } +} + +void Ssd1306Time(void) +{ + char line[12]; + + renderer->clearDisplay(); + renderer->setTextSize(Settings.display_size); + renderer->setTextFont(Settings.display_font); + renderer->setCursor(0, 0); + snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); + renderer->println(line); + snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); + renderer->println(line); + renderer->Updateframe(); +} + +void Ssd1306Refresh(void) +{ + if (!renderer) return; + + if (Settings.display_mode) { + switch (Settings.display_mode) { + case 1: + Ssd1306Time(); + break; + case 2: + case 3: + case 4: + case 5: + Ssd1306PrintLog(); + break; + } + } +} + +#endif + + + + + +bool Xdsp02(byte function) +{ + if (!I2cEnabled(XI2C_04)) { return false; } + + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + SSD1306InitDriver(); + } + else if (XDSP_02 == Settings.display_model) { + switch (function) { +#ifdef USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_EVERY_SECOND: + Ssd1306Refresh(); + break; +#endif + case FUNC_DISPLAY_MODEL: + result = true; + break; + } + } + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_03_matrix.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_03_matrix.ino" +#ifdef USE_I2C +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_MATRIX + +#define XDSP_03 3 +#define XI2C_05 5 + +#define MTX_MAX_SCREEN_BUFFER 80 + +#include +#include +#include + +Adafruit_8x8matrix *matrix[8]; +uint8_t mtx_matrices = 0; +uint8_t mtx_state = 0; +uint8_t mtx_counter = 0; +int16_t mtx_x = 0; +int16_t mtx_y = 0; + + +char *mtx_buffer = nullptr; + +uint8_t mtx_mode = 0; +uint8_t mtx_loop = 0; +uint8_t mtx_done = 0; + + + +void MatrixWrite(void) +{ + for (uint32_t i = 0; i < mtx_matrices; i++) { + matrix[i]->writeDisplay(); + } +} + +void MatrixClear(void) +{ + for (uint32_t i = 0; i < mtx_matrices; i++) { + matrix[i]->clear(); + } + MatrixWrite(); +} + +void MatrixFixed(char* txt) +{ + for (uint32_t i = 0; i < mtx_matrices; i++) { + matrix[i]->clear(); + matrix[i]->setCursor(-i *8, 0); + matrix[i]->print(txt); + matrix[i]->setBrightness(Settings.display_dimmer); + } + MatrixWrite(); +} + +void MatrixCenter(char* txt) +{ + int offset; + + int len = strlen(txt); + offset = (len < 8) ? offset = ((mtx_matrices *8) - (len *6)) / 2 : 0; + for (uint32_t i = 0; i < mtx_matrices; i++) { + matrix[i]->clear(); + matrix[i]->setCursor(-(i *8)+offset, 0); + matrix[i]->print(txt); + matrix[i]->setBrightness(Settings.display_dimmer); + } + MatrixWrite(); +} + +void MatrixScrollLeft(char* txt, int loop) +{ + switch (mtx_state) { + case 1: + mtx_state = 2; + + mtx_x = 8 * mtx_matrices; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), txt); + + disp_refresh = Settings.display_refresh; + case 2: + disp_refresh--; + if (!disp_refresh) { + disp_refresh = Settings.display_refresh; + for (uint32_t i = 0; i < mtx_matrices; i++) { + matrix[i]->clear(); + matrix[i]->setCursor(mtx_x - i *8, 0); + matrix[i]->print(txt); + matrix[i]->setBrightness(Settings.display_dimmer); + } + MatrixWrite(); + + mtx_x--; + int16_t len = strlen(txt); + if (mtx_x < -(len *6)) { mtx_state = loop; } + } + break; + } +} + +void MatrixScrollUp(char* txt, int loop) +{ + int wordcounter = 0; + char tmpbuf[200]; + char *words[100]; + + + + char separators[] = " /"; + + switch (mtx_state) { + case 1: + mtx_state = 2; + + mtx_y = 8; + mtx_counter = 0; + disp_refresh = Settings.display_refresh; + case 2: + disp_refresh--; + if (!disp_refresh) { + disp_refresh = Settings.display_refresh; + strlcpy(tmpbuf, txt, sizeof(tmpbuf)); + char *p = strtok(tmpbuf, separators); + while (p != nullptr && wordcounter < 40) { + words[wordcounter++] = p; + p = strtok(nullptr, separators); + } + for (uint32_t i = 0; i < mtx_matrices; i++) { + matrix[i]->clear(); + for (uint32_t j = 0; j < wordcounter; j++) { + matrix[i]->setCursor(-i *8, mtx_y + (j *8)); + matrix[i]->println(words[j]); + } + matrix[i]->setBrightness(Settings.display_dimmer); + } + MatrixWrite(); + if (((mtx_y %8) == 0) && mtx_counter) { + mtx_counter--; + } else { + mtx_y--; + mtx_counter = STATES * 1; + } + if (mtx_y < -(wordcounter *8)) { mtx_state = loop; } + } + break; + } +} + + + +void MatrixInitMode(void) +{ + for (uint32_t i = 0; i < mtx_matrices; i++) { + matrix[i]->setRotation(Settings.display_rotate); + matrix[i]->setBrightness(Settings.display_dimmer); + matrix[i]->blinkRate(0); + matrix[i]->setTextWrap(false); + + + matrix[i]->cp437(true); + } + MatrixClear(); +} + +void MatrixInit(uint8_t mode) +{ + switch(mode) { + case DISPLAY_INIT_MODE: + MatrixInitMode(); + break; + case DISPLAY_INIT_PARTIAL: + case DISPLAY_INIT_FULL: + break; + } +} + +void MatrixInitDriver(void) +{ + mtx_buffer = (char*)(malloc(MTX_MAX_SCREEN_BUFFER)); + if (mtx_buffer != nullptr) { + if (!Settings.display_model) { + if (I2cSetDevice(Settings.display_address[1])) { + Settings.display_model = XDSP_03; + } + } + + if (XDSP_03 == Settings.display_model) { + mtx_state = 1; + for (mtx_matrices = 0; mtx_matrices < 8; mtx_matrices++) { + if (Settings.display_address[mtx_matrices]) { + I2cSetActiveFound(Settings.display_address[mtx_matrices], "8x8Matrix"); + matrix[mtx_matrices] = new Adafruit_8x8matrix(); + matrix[mtx_matrices]->begin(Settings.display_address[mtx_matrices]); + } else { + break; + } + } + + Settings.display_width = mtx_matrices * 8; + Settings.display_height = 8; + + MatrixInitMode(); + } + } +} + +void MatrixOnOff(void) +{ + if (!disp_power) { MatrixClear(); } +} + +void MatrixDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag) +{ + strlcpy(mtx_buffer, str, MTX_MAX_SCREEN_BUFFER); + mtx_mode = x &1; + mtx_loop = y &1; + if (!mtx_state) { mtx_state = 1; } +} + + + +#ifdef USE_DISPLAY_MODES1TO5 + +void MatrixPrintLog(uint8_t direction) +{ + char* txt = (!mtx_done) ? DisplayLogBuffer('\370') : mtx_buffer; + if (txt != nullptr) { + if (!mtx_state) { mtx_state = 1; } + + if (!mtx_done) { + + uint8_t space = 0; + uint8_t max_cols = (disp_log_buffer_cols < MTX_MAX_SCREEN_BUFFER) ? disp_log_buffer_cols : MTX_MAX_SCREEN_BUFFER; + mtx_buffer[0] = '\0'; + uint8_t i = 0; + while ((txt[i] != '\0') && (i < max_cols)) { + if (txt[i] == ' ') { + space++; + } else { + space = 0; + } + if (space < 2) { + strncat(mtx_buffer, (const char*)txt +i, (strlen(mtx_buffer) < MTX_MAX_SCREEN_BUFFER -1) ? 1 : 0); + } + i++; + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), mtx_buffer); + + mtx_done = 1; + } + + if (direction) { + MatrixScrollUp(mtx_buffer, 0); + } else { + MatrixScrollLeft(mtx_buffer, 0); + } + if (!mtx_state) { mtx_done = 0; } + } else { + char disp_time[9]; + + snprintf_P(disp_time, sizeof(disp_time), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); + MatrixFixed(disp_time); + } +} + +#endif + +void MatrixRefresh(void) +{ + if (disp_power) { + switch (Settings.display_mode) { + case 0: { + switch (mtx_mode) { + case 0: + MatrixScrollLeft(mtx_buffer, mtx_loop); + break; + case 1: + MatrixScrollUp(mtx_buffer, mtx_loop); + break; + } + break; + } +#ifdef USE_DISPLAY_MODES1TO5 + case 2: { + char disp_date[9]; + snprintf_P(disp_date, sizeof(disp_date), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year -2000); + MatrixFixed(disp_date); + break; + } + case 3: { + char disp_day[10]; + snprintf_P(disp_day, sizeof(disp_day), PSTR("%d %s"), RtcTime.day_of_month, RtcTime.name_of_month); + MatrixCenter(disp_day); + break; + } + case 4: + MatrixPrintLog(0); + break; + case 1: + case 5: + MatrixPrintLog(1); + break; +#endif + } + } +} + + + + + +bool Xdsp03(uint8_t function) +{ + if (!I2cEnabled(XI2C_05)) { return false; } + + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + MatrixInitDriver(); + } + else if (XDSP_03 == Settings.display_model) { + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; + case FUNC_DISPLAY_INIT: + MatrixInit(dsp_init); + break; + case FUNC_DISPLAY_EVERY_50_MSECOND: + MatrixRefresh(); + break; + case FUNC_DISPLAY_POWER: + MatrixOnOff(); + break; + case FUNC_DISPLAY_DRAW_STRING: + MatrixDrawStringAt(dsp_x, dsp_y, dsp_str, dsp_color, dsp_flag); + break; + } + } + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_04_ili9341.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_04_ili9341.ino" +#ifdef USE_SPI +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_ILI9341 + +#define XDSP_04 4 + +#define TFT_TOP 16 +#define TFT_BOTTOM 16 +#define TFT_FONT_WIDTH 6 +#define TFT_FONT_HEIGTH 8 + +#include +#include +#include + +Adafruit_ILI9341 *tft; + +uint16_t tft_scroll; + + + +void Ili9341InitMode(void) +{ + tft->setRotation(Settings.display_rotate); + tft->invertDisplay(0); + tft->fillScreen(ILI9341_BLACK); + tft->setTextWrap(false); + tft->cp437(true); + if (!Settings.display_mode) { + tft->setCursor(0, 0); + tft->setTextColor(ILI9341_WHITE, ILI9341_BLACK); + tft->setTextSize(1); + } else { + tft->setScrollMargins(TFT_TOP, TFT_BOTTOM); + tft->setCursor(0, 0); + tft->setTextColor(ILI9341_YELLOW, ILI9341_BLACK); + tft->setTextSize(2); + + + tft_scroll = TFT_TOP; + } +} + +void Ili9341Init(uint8_t mode) +{ + switch(mode) { + case DISPLAY_INIT_MODE: + Ili9341InitMode(); +#ifdef USE_DISPLAY_MODES1TO5 + if (Settings.display_rotate) { + DisplayClearScreenBuffer(); + } +#endif + break; + case DISPLAY_INIT_PARTIAL: + case DISPLAY_INIT_FULL: + break; + } +} + +void Ili9341InitDriver(void) +{ + if (!Settings.display_model) { + Settings.display_model = XDSP_04; + } + + if (XDSP_04 == Settings.display_model) { + if (Settings.display_width != ILI9341_TFTWIDTH) { + Settings.display_width = ILI9341_TFTWIDTH; + } + if (Settings.display_height != ILI9341_TFTHEIGHT) { + Settings.display_height = ILI9341_TFTHEIGHT; + } + tft = new Adafruit_ILI9341(pin[GPIO_SPI_CS], pin[GPIO_SPI_DC]); + tft->begin(); + +#ifdef USE_DISPLAY_MODES1TO5 + if (Settings.display_rotate) { + DisplayAllocScreenBuffer(); + } +#endif + + Ili9341InitMode(); + } +} + +void Ili9341Clear(void) +{ + tft->fillScreen(ILI9341_BLACK); + tft->setCursor(0, 0); +} + +void Ili9341DrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag) +{ + uint16_t active_color = ILI9341_WHITE; + + tft->setTextSize(Settings.display_size); + if (!flag) { + tft->setCursor(x, y); + } else { + tft->setCursor((x-1) * TFT_FONT_WIDTH * Settings.display_size, (y-1) * TFT_FONT_HEIGTH * Settings.display_size); + } + if (color) { active_color = color; } + tft->setTextColor(active_color, ILI9341_BLACK); + tft->println(str); +} + +void Ili9341DisplayOnOff(uint8_t on) +{ + + + if (pin[GPIO_BACKLIGHT] < 99) { + pinMode(pin[GPIO_BACKLIGHT], OUTPUT); + digitalWrite(pin[GPIO_BACKLIGHT], on); + } +} + +void Ili9341OnOff(void) +{ + Ili9341DisplayOnOff(disp_power); +} + + + +#ifdef USE_DISPLAY_MODES1TO5 + +void Ili9341PrintLog(void) +{ + disp_refresh--; + if (!disp_refresh) { + disp_refresh = Settings.display_refresh; + if (Settings.display_rotate) { + if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } + } + + char* txt = DisplayLogBuffer('\370'); + if (txt != nullptr) { + uint8_t size = Settings.display_size; + uint16_t theight = size * TFT_FONT_HEIGTH; + + tft->setTextSize(size); + tft->setTextColor(ILI9341_CYAN, ILI9341_BLACK); + if (!Settings.display_rotate) { + tft->setCursor(0, tft_scroll); + tft->fillRect(0, tft_scroll, tft->width(), theight, ILI9341_BLACK); + tft->print(txt); + tft_scroll += theight; + if (tft_scroll >= (tft->height() - TFT_BOTTOM)) { + tft_scroll = TFT_TOP; + } + tft->scrollTo(tft_scroll); + } else { + uint8_t last_row = Settings.display_rows -1; + + tft_scroll = theight; + tft->setCursor(0, tft_scroll); + for (uint32_t i = 0; i < last_row; i++) { + strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); + + tft->print(disp_screen_buffer[i]); + tft_scroll += theight; + tft->setCursor(0, tft_scroll); + delay(1); + } + strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); + DisplayFillScreen(last_row); + tft->print(disp_screen_buffer[last_row]); + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), txt); + } + } +} + +void Ili9341Refresh(void) +{ + if (Settings.display_mode) { + char tftdt[Settings.display_cols[0] +1]; + char date4[11]; + char space[Settings.display_cols[0] - 17]; + char time[9]; + + tft->setTextSize(2); + tft->setTextColor(ILI9341_YELLOW, ILI9341_RED); + tft->setCursor(0, 0); + + snprintf_P(date4, sizeof(date4), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); + memset(space, 0x20, sizeof(space)); + space[sizeof(space) -1] = '\0'; + snprintf_P(time, sizeof(time), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); + snprintf_P(tftdt, sizeof(tftdt), PSTR("%s%s%s"), date4, space, time); + + tft->print(tftdt); + + switch (Settings.display_mode) { + case 1: + case 2: + case 3: + case 4: + case 5: + Ili9341PrintLog(); + break; + } + } +} + +#endif + + + + + +bool Xdsp04(uint8_t function) +{ + bool result = false; + + if (spi_flg) { + if (FUNC_DISPLAY_INIT_DRIVER == function) { + Ili9341InitDriver(); + } + else if (XDSP_04 == Settings.display_model) { + + if (!dsp_color) { dsp_color = ILI9341_WHITE; } + + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; + case FUNC_DISPLAY_INIT: + Ili9341Init(dsp_init); + break; + case FUNC_DISPLAY_POWER: + Ili9341OnOff(); + break; + case FUNC_DISPLAY_CLEAR: + Ili9341Clear(); + break; + case FUNC_DISPLAY_DRAW_HLINE: + tft->writeFastHLine(dsp_x, dsp_y, dsp_len, dsp_color); + break; + case FUNC_DISPLAY_DRAW_VLINE: + tft->writeFastVLine(dsp_x, dsp_y, dsp_len, dsp_color); + break; + case FUNC_DISPLAY_DRAW_LINE: + tft->writeLine(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color); + break; + case FUNC_DISPLAY_DRAW_CIRCLE: + tft->drawCircle(dsp_x, dsp_y, dsp_rad, dsp_color); + break; + case FUNC_DISPLAY_FILL_CIRCLE: + tft->fillCircle(dsp_x, dsp_y, dsp_rad, dsp_color); + break; + case FUNC_DISPLAY_DRAW_RECTANGLE: + tft->drawRect(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color); + break; + case FUNC_DISPLAY_FILL_RECTANGLE: + tft->fillRect(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color); + break; + + + + case FUNC_DISPLAY_TEXT_SIZE: + tft->setTextSize(Settings.display_size); + break; + case FUNC_DISPLAY_FONT_SIZE: + + break; + case FUNC_DISPLAY_DRAW_STRING: + Ili9341DrawStringAt(dsp_x, dsp_y, dsp_str, dsp_color, dsp_flag); + break; + case FUNC_DISPLAY_ONOFF: + Ili9341DisplayOnOff(dsp_on); + break; + case FUNC_DISPLAY_ROTATION: + tft->setRotation(Settings.display_rotate); + break; +#ifdef USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_EVERY_SECOND: + Ili9341Refresh(); + break; +#endif + } + } + } + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_05_epaper_29.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_05_epaper_29.ino" +#ifdef USE_SPI +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_EPAPER_29 + +#define XDSP_05 5 + +#define EPD_TOP 12 +#define EPD_FONT_HEIGTH 12 + +#define COLORED 1 +#define UNCOLORED 0 + + + +#define USE_TINY_FONT + +#include +#include + + +extern uint8_t *buffer; +uint16_t epd_scroll; + +Epd *epd; + + + +void EpdInitDriver29() +{ + if (!Settings.display_model) { + Settings.display_model = XDSP_05; + } + + if (XDSP_05 == Settings.display_model) { + if (Settings.display_width != EPD_WIDTH) { + Settings.display_width = EPD_WIDTH; + } + if (Settings.display_height != EPD_HEIGHT) { + Settings.display_height = EPD_HEIGHT; + } + + + if (buffer) free(buffer); + buffer=(unsigned char*)calloc((EPD_WIDTH * EPD_HEIGHT) / 8,1); + if (!buffer) return; + + + epd = new Epd(EPD_WIDTH,EPD_HEIGHT); + + + if ((pin[GPIO_SPI_CS] < 99) && (pin[GPIO_SPI_CLK] < 99) && (pin[GPIO_SPI_MOSI] < 99)) { + epd->Begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EPD: HardSPI CS %d, CLK %d, MOSI %d"),pin[GPIO_SPI_CS], pin[GPIO_SPI_CLK], pin[GPIO_SPI_MOSI]); + } + else if ((pin[GPIO_SSPI_CS] < 99) && (pin[GPIO_SSPI_SCLK] < 99) && (pin[GPIO_SSPI_MOSI] < 99)) { + epd->Begin(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EPD: SoftSPI CS %d, CLK %d, MOSI %d"),pin[GPIO_SSPI_CS], pin[GPIO_SSPI_SCLK], pin[GPIO_SSPI_MOSI]); + } else { + free(buffer); + return; + } + + renderer = epd; + epd->Init(DISPLAY_INIT_FULL); + epd->Init(DISPLAY_INIT_PARTIAL); + renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); + + renderer->setTextColor(1,0); + +#ifdef SHOW_SPLASH + + renderer->setTextFont(1); + renderer->DrawStringAt(50, 50, "Waveshare E-Paper Display!", COLORED,0); + renderer->Updateframe(); + delay(1000); + renderer->fillScreen(0); +#endif + + } +} + + + + + + + +#ifdef USE_DISPLAY_MODES1TO5 +#define EPD_FONT_HEIGTH 12 +void EpdPrintLog29(void) +{ + + disp_refresh--; + if (!disp_refresh) { + disp_refresh = Settings.display_refresh; + + if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } + + + char* txt = DisplayLogBuffer('\040'); + if (txt != nullptr) { + uint8_t size = Settings.display_size; + uint16_t theight = size * EPD_FONT_HEIGTH; + + renderer->setTextFont(size); + uint8_t last_row = Settings.display_rows -1; + + + epd_scroll = 0; + for (uint32_t i = 0; i < last_row; i++) { + strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); + renderer->DrawStringAt(0, epd_scroll, disp_screen_buffer[i], COLORED, 0); + epd_scroll += theight; + } + strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); + DisplayFillScreen(last_row); + renderer->DrawStringAt(0, epd_scroll, disp_screen_buffer[last_row], COLORED, 0); + + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), txt); + } + } +} + +void EpdRefresh29(void) +{ + if (Settings.display_mode) { + + if (!renderer) return; +# 165 "S:/Development/Tasmota/tasmota/xdsp_05_epaper_29.ino" + switch (Settings.display_mode) { + case 1: + case 2: + case 3: + case 4: + case 5: + EpdPrintLog29(); + renderer->Updateframe(); + break; + } + + + } +} + +#endif + + + + + +bool Xdsp05(uint8_t function) +{ + bool result = false; + if (FUNC_DISPLAY_INIT_DRIVER == function) { + EpdInitDriver29(); + } + else if (XDSP_05 == Settings.display_model) { + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; +#ifdef USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_EVERY_SECOND: + EpdRefresh29(); + break; +#endif + } + } + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_06_epaper_42.ino" +# 21 "S:/Development/Tasmota/tasmota/xdsp_06_epaper_42.ino" +#ifdef USE_SPI +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_EPAPER_42 + +#define XDSP_06 6 + +#define COLORED42 1 +#define UNCOLORED42 0 + + + +#define USE_TINY_FONT + +#include +#include + +extern uint8_t *buffer; + +Epd42 *epd42; + + + + +void EpdInitDriver42() +{ + if (!Settings.display_model) { + Settings.display_model = XDSP_06; + } + + if (XDSP_06 == Settings.display_model) { + + if (Settings.display_width != EPD_WIDTH42) { + Settings.display_width = EPD_WIDTH42; + } + if (Settings.display_height != EPD_HEIGHT42) { + Settings.display_height = EPD_HEIGHT42; + } + + + if (buffer) free(buffer); + buffer=(unsigned char*)calloc((EPD_WIDTH42 * EPD_HEIGHT42) / 8,1); + if (!buffer) return; + + + epd42 = new Epd42(EPD_WIDTH42,EPD_HEIGHT42); + + #ifdef USE_SPI + if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)) { + epd42->Begin(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]); + } else { + free(buffer); + return; + } + #else + if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)) { + epd42->Begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]); + } else { + free(buffer); + return; + } + #endif + + renderer = epd42; + + epd42->Init(); + + renderer->fillScreen(0); + + + epd42->Init(DISPLAY_INIT_FULL); + + renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); + + epd42->ClearFrame(); + renderer->Updateframe(); + delay(3000); + renderer->setTextColor(1,0); + +#ifdef SHOW_SPLASH + + renderer->setTextFont(2); + renderer->DrawStringAt(50, 140, "Waveshare E-Paper!", COLORED42,0); + renderer->Updateframe(); + delay(350); + renderer->fillScreen(0); +#endif + + } +} + + + + + + + +#ifdef USE_DISPLAY_MODES1TO5 + +void EpdRefresh42() +{ + if (Settings.display_mode) { + + } +} + +#endif + + + + + + +bool Xdsp06(uint8_t function) +{ + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + EpdInitDriver42(); + } + else if (XDSP_06 == Settings.display_model) { + + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; + +#ifdef USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_EVERY_SECOND: + EpdRefresh42(); + break; +#endif + } + } + return result; +} + + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_07_sh1106.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_07_sh1106.ino" +#ifdef USE_I2C +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_SH1106 + +#define OLED_RESET 4 + +#define SPRINT(A) char str[32];sprintf(str,"val: %d ",A);Serial.println((char*)str); + +extern uint8_t *buffer; + +#define XDSP_07 7 +#define XI2C_06 6 + +#define OLED_ADDRESS1 0x3C +#define OLED_ADDRESS2 0x3D + +#define OLED_BUFFER_COLS 40 +#define OLED_BUFFER_ROWS 16 + +#define OLED_FONT_WIDTH 6 +#define OLED_FONT_HEIGTH 8 + +#include +#include +#include + +Adafruit_SH1106 *oled1106; + + + + +void SH1106InitDriver() +{ + if (!Settings.display_model) { + if (I2cSetDevice(OLED_ADDRESS1)) { + Settings.display_address[0] = OLED_ADDRESS1; + Settings.display_model = XDSP_07; + } + else if (I2cSetDevice(OLED_ADDRESS2)) { + Settings.display_address[0] = OLED_ADDRESS2; + Settings.display_model = XDSP_07; + } + } + + if (XDSP_07 == Settings.display_model) { + I2cSetActiveFound(Settings.display_address[0], "SH1106"); + + if (Settings.display_width != SH1106_LCDWIDTH) { + Settings.display_width = SH1106_LCDWIDTH; + } + if (Settings.display_height != SH1106_LCDHEIGHT) { + Settings.display_height = SH1106_LCDHEIGHT; + } + + + if (buffer) free(buffer); + buffer=(unsigned char*)calloc((SH1106_LCDWIDTH * SH1106_LCDHEIGHT) / 8,1); + if (!buffer) return; + + + oled1106 = new Adafruit_SH1106(SH1106_LCDWIDTH,SH1106_LCDHEIGHT); + renderer=oled1106; + renderer->Begin(SH1106_SWITCHCAPVCC, Settings.display_address[0],0); + renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); + renderer->setTextColor(1,0); + +#ifdef SHOW_SPLASH + renderer->setTextFont(0); + renderer->setTextSize(2); + renderer->setCursor(20,20); + renderer->println(F("SH1106")); + renderer->Updateframe(); + renderer->DisplayOnff(1); +#endif + } +} + + + +#ifdef USE_DISPLAY_MODES1TO5 + +void SH1106PrintLog(void) +{ + disp_refresh--; + if (!disp_refresh) { + disp_refresh = Settings.display_refresh; + if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } + + char* txt = DisplayLogBuffer('\370'); + if (txt != NULL) { + uint8_t last_row = Settings.display_rows -1; + + renderer->clearDisplay(); + renderer->setTextSize(Settings.display_size); + renderer->setCursor(0,0); + for (byte i = 0; i < last_row; i++) { + strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); + renderer->println(disp_screen_buffer[i]); + } + strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); + DisplayFillScreen(last_row); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); + + renderer->println(disp_screen_buffer[last_row]); + renderer->Updateframe(); + } + } +} + +void SH1106Time(void) +{ + char line[12]; + + renderer->clearDisplay(); + renderer->setTextSize(Settings.display_size); + renderer->setTextFont(Settings.display_font); + renderer->setCursor(0, 0); + snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); + renderer->println(line); + snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); + renderer->println(line); + renderer->Updateframe(); +} + +void SH1106Refresh(void) +{ + if (!renderer) return; + if (Settings.display_mode) { + switch (Settings.display_mode) { + case 1: + SH1106Time(); + break; + case 2: + case 3: + case 4: + case 5: + SH1106PrintLog(); + break; + } + } +} + +#endif + + + + + +bool Xdsp07(uint8_t function) +{ + if (!I2cEnabled(XI2C_06)) { return false; } + + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + SH1106InitDriver(); + } + else if (XDSP_07 == Settings.display_model) { + + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; +#ifdef USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_EVERY_SECOND: + SH1106Refresh(); + break; +#endif + } + } + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_08_ILI9488.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_08_ILI9488.ino" +#ifdef USE_SPI +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_ILI9488 + +#define XDSP_08 8 +#define XI2C_38 38 + +#define COLORED 1 +#define UNCOLORED 0 + + +#define FT6236_address 0x38 + + + +#define USE_TINY_FONT + + +#include +#include + +TouchLocation ili9488_pLoc; +uint8_t ili9488_ctouch_counter = 0; + + +#define BACKPLANE_PIN 2 + +extern uint8_t *buffer; +extern uint8_t color_type; +ILI9488 *ili9488; + +#ifdef USE_TOUCH_BUTTONS +extern VButton *buttons[]; +#endif + +extern const uint16_t picture[]; +uint8_t FT6236_found; + + + +void ILI9488_InitDriver() +{ + if (!Settings.display_model) { + Settings.display_model = XDSP_08; + } + + if (XDSP_08 == Settings.display_model) { + + if (Settings.display_width != ILI9488_TFTWIDTH) { + Settings.display_width = ILI9488_TFTWIDTH; + } + if (Settings.display_height != ILI9488_TFTHEIGHT) { + Settings.display_height = ILI9488_TFTHEIGHT; + } + + + buffer=NULL; + + + fg_color = ILI9488_WHITE; + bg_color = ILI9488_BLACK; + + uint8_t bppin=BACKPLANE_PIN; + if (pin[GPIO_BACKLIGHT]<99) { + bppin=pin[GPIO_BACKLIGHT]; + } + + + if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)){ + ili9488 = new ILI9488(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK],bppin); + } else { + if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)) { + ili9488 = new ILI9488(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK],bppin); + } else { + return; + } + } + + SPI.begin(); + ili9488->begin(); + renderer = ili9488; + renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); + +#ifdef SHOW_SPLASH + + renderer->setTextFont(2); + renderer->setTextColor(ILI9488_WHITE,ILI9488_BLACK); + renderer->DrawStringAt(50, 50, "ILI9488 TFT Display!", ILI9488_WHITE,0); + delay(1000); + + +#endif + + color_type = COLOR_COLOR; + + + if (I2cEnabled(XI2C_38) && I2cSetDevice(FT6236_address)) { + FT6236begin(FT6236_address); + FT6236_found=1; + I2cSetActiveFound(FT6236_address, "FT6236"); + } else { + FT6236_found=0; + } + + } +} + +#ifdef USE_TOUCH_BUTTONS +void ILI9488_MQTT(uint8_t count,const char *cp) { + ResponseTime_P(PSTR(",\"RA8876\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7); + MqttPublishTeleSensor(); +} + +void ILI9488_RDW_BUTT(uint32_t count,uint32_t pwr) { + buttons[count]->xdrawButton(pwr); + if (pwr) buttons[count]->vpower|=0x80; + else buttons[count]->vpower&=0x7f; +} + +void FT6236Check() { +uint16_t temp; +uint8_t rbutt=0,vbutt=0; +ili9488_ctouch_counter++; +if (2 == ili9488_ctouch_counter) { + + ili9488_ctouch_counter=0; + if (FT6236readTouchLocation(&ili9488_pLoc,1)) { + + if (renderer) { + uint8_t rot=renderer->getRotation(); + switch (rot) { + case 0: + temp=ili9488_pLoc.y; + ili9488_pLoc.y=renderer->height()-ili9488_pLoc.x; + ili9488_pLoc.x=temp; + break; + case 1: + break; + case 2: + break; + case 3: + temp=ili9488_pLoc.y; + ili9488_pLoc.y=ili9488_pLoc.x; + ili9488_pLoc.x=renderer->width()-temp; + break; + } + + for (uint8_t count=0; countvpower&0x7f; + if (buttons[count]->contains(ili9488_pLoc.x,ili9488_pLoc.y)) { + + buttons[count]->press(true); + if (buttons[count]->justPressed()) { + if (!bflags) { + uint8_t pwr=bitRead(power,rbutt); + if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) { + ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON); + ILI9488_RDW_BUTT(count,!pwr); + } + } else { + + const char *cp; + if (bflags==1) { + + buttons[count]->vpower^=0x80; + cp="TBT"; + } else { + + buttons[count]->vpower|=0x80; + cp="PBT"; + } + buttons[count]->xdrawButton(buttons[count]->vpower&0x80); + ILI9488_MQTT(count,cp); + } + } + } + if (!bflags) { + rbutt++; + } else { + vbutt++; + } + } + } + } + } else { + + for (uint8_t count=0; countvpower&0x7f; + buttons[count]->press(false); + if (buttons[count]->justReleased()) { + uint8_t bflags=buttons[count]->vpower&0x7f; + if (bflags>0) { + if (bflags>1) { + + buttons[count]->vpower&=0x7f; + ILI9488_MQTT(count,"PBT"); + } + buttons[count]->xdrawButton(buttons[count]->vpower&0x80); + } + } + if (!bflags) { + + uint8_t pwr=bitRead(power,rbutt); + uint8_t vpwr=(buttons[count]->vpower&0x80)>>7; + if (pwr!=vpwr) { + ILI9488_RDW_BUTT(count,pwr); + } + rbutt++; + } + } + } + ili9488_pLoc.x=0; + ili9488_pLoc.y=0; + } +} +} +#endif + + + + +bool Xdsp08(uint8_t function) +{ + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + ILI9488_InitDriver(); + } + else if (XDSP_08 == Settings.display_model) { + + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; + case FUNC_DISPLAY_EVERY_50_MSECOND: +#ifdef USE_TOUCH_BUTTONS + if (FT6236_found) FT6236Check(); +#endif + break; + } + } + + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_09_SSD1351.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_09_SSD1351.ino" +#ifdef USE_SPI +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_SSD1351 + +#define XDSP_09 9 + +#define COLORED 1 +#define UNCOLORED 0 + + + + +#define USE_TINY_FONT + +#include + +extern uint8_t *buffer; +extern uint8_t color_type; +SSD1351 *ssd1351; + + + +void SSD1351_InitDriver() { + if (!Settings.display_model) { + Settings.display_model = XDSP_09; + } + + if (XDSP_09 == Settings.display_model) { + + if (Settings.display_width != SSD1351_WIDTH) { + Settings.display_width = SSD1351_WIDTH; + } + if (Settings.display_height != SSD1351_HEIGHT) { + Settings.display_height = SSD1351_HEIGHT; + } + + buffer=0; + + + fg_color = SSD1351_WHITE; + bg_color = SSD1351_BLACK; + + + if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)){ + ssd1351 = new SSD1351(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]); + } else { + if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)){ + ssd1351 = new SSD1351(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]); + } else { + return; + } + } + + delay(100); + SPI.begin(); + ssd1351->begin(); + renderer = ssd1351; + renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); + renderer->dim(Settings.display_dimmer); + +#ifdef SHOW_SPLASH + + renderer->setTextFont(2); + renderer->setTextColor(SSD1351_WHITE,SSD1351_BLACK); + renderer->DrawStringAt(10, 60, "SSD1351", SSD1351_RED,0); + delay(1000); + +#endif + color_type = COLOR_COLOR; + } +} + +#ifdef USE_DISPLAY_MODES1TO5 + +void SSD1351PrintLog(void) +{ + disp_refresh--; + if (!disp_refresh) { + disp_refresh = Settings.display_refresh; + if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } + + char* txt = DisplayLogBuffer('\370'); + if (txt != NULL) { + uint8_t last_row = Settings.display_rows -1; + + renderer->clearDisplay(); + renderer->setTextSize(Settings.display_size); + renderer->setCursor(0,0); + for (byte i = 0; i < last_row; i++) { + strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); + renderer->println(disp_screen_buffer[i]); + } + strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); + DisplayFillScreen(last_row); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); + + renderer->println(disp_screen_buffer[last_row]); + renderer->Updateframe(); + } + } +} + +void SSD1351Time(void) +{ + char line[12]; + + renderer->clearDisplay(); + renderer->setTextSize(2); + renderer->setCursor(0, 0); + snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); + renderer->println(line); + snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); + renderer->println(line); + renderer->Updateframe(); +} + +void SSD1351Refresh(void) +{ + if (Settings.display_mode) { + switch (Settings.display_mode) { + case 1: + SSD1351Time(); + break; + case 2: + case 3: + case 4: + case 5: + SSD1351PrintLog(); + break; + } + } +} + +#endif + + + + +bool Xdsp09(uint8_t function) +{ + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + SSD1351_InitDriver(); + } + else if (XDSP_09 == Settings.display_model) { + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; +#ifdef USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_EVERY_SECOND: + SSD1351Refresh(); + break; +#endif + } + } + return result; +} +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_10_RA8876.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_10_RA8876.ino" +#ifdef USE_SPI +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_RA8876 + +#define XDSP_10 10 +#define XI2C_39 39 + +#define COLORED 1 +#define UNCOLORED 0 + + +#define FT5316_address 0x38 + + + +#define USE_TINY_FONT + +#include +#include + +TouchLocation ra8876_pLoc; +uint8_t ra8876_ctouch_counter = 0; + +#ifdef USE_TOUCH_BUTTONS +extern VButton *buttons[]; +#endif + +extern uint8_t *buffer; +extern uint8_t color_type; +RA8876 *ra8876; + +uint8_t FT5316_found; + + +void RA8876_InitDriver() +{ + if (!Settings.display_model) { + Settings.display_model = XDSP_10; + } + + if (XDSP_10 == Settings.display_model) { + + if (Settings.display_width != RA8876_TFTWIDTH) { + Settings.display_width = RA8876_TFTWIDTH; + } + if (Settings.display_height != RA8876_TFTHEIGHT) { + Settings.display_height = RA8876_TFTHEIGHT; + } + buffer=0; + + + fg_color = RA8876_WHITE; + bg_color = RA8876_BLACK; + + + if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]==13) && (pin[GPIO_SSPI_MISO]==12) && (pin[GPIO_SSPI_SCLK]==14)) { + ra8876 = new RA8876(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_MISO],pin[GPIO_SSPI_SCLK],pin[GPIO_BACKLIGHT]); + } else { + if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]==13) && (pin[GPIO_SPI_MISO]==12) && (pin[GPIO_SPI_CLK]==14)) { + ra8876 = new RA8876(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_MISO],pin[GPIO_SPI_CLK],pin[GPIO_BACKLIGHT]); + } else { + return; + } + } + + ra8876->begin(); + renderer = ra8876; + renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); + renderer->dim(Settings.display_dimmer); + + +#ifdef SHOW_SPLASH + + renderer->setTextFont(2); + renderer->setTextColor(RA8876_WHITE,RA8876_BLACK); + renderer->DrawStringAt(600, 300, "RA8876", RA8876_RED,0); + delay(1000); + +#endif + color_type = COLOR_COLOR; + + if (I2cEnabled(XI2C_39) && I2cSetDevice(FT5316_address)) { + FT6236begin(FT5316_address); + FT5316_found=1; + I2cSetActiveFound(FT5316_address, "FT5316"); + } else { + FT5316_found=0; + } + + } +} + +#ifdef USE_TOUCH_BUTTONS +void RA8876_MQTT(uint8_t count,const char *cp) { + ResponseTime_P(PSTR(",\"RA8876\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7); + MqttPublishTeleSensor(); +} + +void RA8876_RDW_BUTT(uint32_t count,uint32_t pwr) { + buttons[count]->xdrawButton(pwr); + if (pwr) buttons[count]->vpower|=0x80; + else buttons[count]->vpower&=0x7f; +} + + +void FT5316Check() { +uint16_t temp; +uint8_t rbutt=0,vbutt=0; +ra8876_ctouch_counter++; +if (2 == ra8876_ctouch_counter) { + + ra8876_ctouch_counter=0; + + if (FT6236readTouchLocation(&ra8876_pLoc,1)) { + ra8876_pLoc.x=ra8876_pLoc.x*RA8876_TFTWIDTH/800; + ra8876_pLoc.y=ra8876_pLoc.y*RA8876_TFTHEIGHT/480; + + + if (renderer) { + + + ra8876_pLoc.x=RA8876_TFTWIDTH-ra8876_pLoc.x; + ra8876_pLoc.y=RA8876_TFTHEIGHT-ra8876_pLoc.y; +# 170 "S:/Development/Tasmota/tasmota/xdsp_10_RA8876.ino" + for (uint8_t count=0; countvpower&0x7f; + if (buttons[count]->contains(ra8876_pLoc.x,ra8876_pLoc.y)) { + + buttons[count]->press(true); + if (buttons[count]->justPressed()) { + if (!bflags) { + + uint8_t pwr=bitRead(power,rbutt); + if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) { + ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON); + RA8876_RDW_BUTT(count,!pwr); + } + } else { + + const char *cp; + if (bflags==1) { + + buttons[count]->vpower^=0x80; + cp="TBT"; + } else { + + buttons[count]->vpower|=0x80; + cp="PBT"; + } + buttons[count]->xdrawButton(buttons[count]->vpower&0x80); + RA8876_MQTT(count,cp); + } + } + } + if (!bflags) { + rbutt++; + } else { + vbutt++; + } + } + } + } + } else { + + for (uint8_t count=0; countvpower&0x7f; + buttons[count]->press(false); + if (buttons[count]->justReleased()) { + if (bflags>0) { + if (bflags>1) { + + buttons[count]->vpower&=0x7f; + RA8876_MQTT(count,"PBT"); + } + buttons[count]->xdrawButton(buttons[count]->vpower&0x80); + } + } + if (!bflags) { + + uint8_t pwr=bitRead(power,rbutt); + uint8_t vpwr=(buttons[count]->vpower&0x80)>>7; + if (pwr!=vpwr) { + RA8876_RDW_BUTT(count,pwr); + } + rbutt++; + } + } + } + ra8876_pLoc.x=0; + ra8876_pLoc.y=0; + } +} +} +#endif +# 426 "S:/Development/Tasmota/tasmota/xdsp_10_RA8876.ino" +bool Xdsp10(uint8_t function) +{ + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + RA8876_InitDriver(); + } + else if (XDSP_10 == Settings.display_model) { + switch (function) { + case FUNC_DISPLAY_MODEL: + result = true; + break; + case FUNC_DISPLAY_EVERY_50_MSECOND: +#ifdef USE_TOUCH_BUTTONS + if (FT5316_found) FT5316Check(); +#endif + break; + } + } + return result; +} +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xdsp_interface.ino" +# 20 "S:/Development/Tasmota/tasmota/xdsp_interface.ino" +#ifdef USE_DISPLAY + +#ifdef XFUNC_PTR_IN_ROM +bool (* const xdsp_func_ptr[])(uint8_t) PROGMEM = { +#else +bool (* const xdsp_func_ptr[])(uint8_t) = { +#endif + +#ifdef XDSP_01 + &Xdsp01, +#endif + +#ifdef XDSP_02 + &Xdsp02, +#endif + +#ifdef XDSP_03 + &Xdsp03, +#endif + +#ifdef XDSP_04 + &Xdsp04, +#endif + +#ifdef XDSP_05 + &Xdsp05, +#endif + +#ifdef XDSP_06 + &Xdsp06, +#endif + +#ifdef XDSP_07 + &Xdsp07, +#endif + +#ifdef XDSP_08 + &Xdsp08, +#endif + +#ifdef XDSP_09 + &Xdsp09, +#endif + +#ifdef XDSP_10 + &Xdsp10, +#endif + +#ifdef XDSP_11 + &Xdsp11, +#endif + +#ifdef XDSP_12 + &Xdsp12, +#endif + +#ifdef XDSP_13 + &Xdsp13, +#endif + +#ifdef XDSP_14 + &Xdsp14, +#endif + +#ifdef XDSP_15 + &Xdsp15, +#endif + +#ifdef XDSP_16 + &Xdsp16 +#endif +}; + +const uint8_t xdsp_present = sizeof(xdsp_func_ptr) / sizeof(xdsp_func_ptr[0]); +# 117 "S:/Development/Tasmota/tasmota/xdsp_interface.ino" +uint8_t XdspPresent(void) +{ + return xdsp_present; +} + +bool XdspCall(uint8_t Function) +{ + bool result = false; + + DEBUG_TRACE_LOG(PSTR("DSP: %d"), Function); + + for (uint32_t x = 0; x < xdsp_present; x++) { + result = xdsp_func_ptr[x](Function); + + if (result && (FUNC_DISPLAY_MODEL == Function)) { + break; + } + } + + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xlgt_01_ws2812.ino" +# 20 "S:/Development/Tasmota/tasmota/xlgt_01_ws2812.ino" +#ifdef USE_LIGHT +#ifdef USE_WS2812 +# 38 "S:/Development/Tasmota/tasmota/xlgt_01_ws2812.ino" +#define XLGT_01 1 + +const uint8_t WS2812_SCHEMES = 8; + +const char kWs2812Commands[] PROGMEM = "|" + D_CMND_LED "|" D_CMND_PIXELS "|" D_CMND_ROTATION "|" D_CMND_WIDTH ; + +void (* const Ws2812Command[])(void) PROGMEM = { + &CmndLed, &CmndPixels, &CmndRotation, &CmndWidth }; + +#include + +#if (USE_WS2812_CTYPE == NEO_GRB) + typedef NeoGrbFeature selectedNeoFeatureType; +#elif (USE_WS2812_CTYPE == NEO_BRG) + typedef NeoBrgFeature selectedNeoFeatureType; +#elif (USE_WS2812_CTYPE == NEO_RBG) + typedef NeoRbgFeature selectedNeoFeatureType; +#elif (USE_WS2812_CTYPE == NEO_RGBW) + typedef NeoRgbwFeature selectedNeoFeatureType; +#elif (USE_WS2812_CTYPE == NEO_GRBW) + typedef NeoGrbwFeature selectedNeoFeatureType; +#else + typedef NeoRgbFeature selectedNeoFeatureType; +#endif + +#ifdef USE_WS2812_DMA + + +#if (USE_WS2812_HARDWARE == NEO_HW_WS2812X) + typedef NeoEsp8266DmaWs2812xMethod selectedNeoSpeedType; +#elif (USE_WS2812_HARDWARE == NEO_HW_SK6812) + typedef NeoEsp8266DmaSk6812Method selectedNeoSpeedType; +#elif (USE_WS2812_HARDWARE == NEO_HW_APA106) + typedef NeoEsp8266DmaApa106Method selectedNeoSpeedType; +#else + typedef NeoEsp8266Dma800KbpsMethod selectedNeoSpeedType; +#endif + +#else + + +#if (USE_WS2812_HARDWARE == NEO_HW_WS2812X) + typedef NeoEsp8266BitBangWs2812xMethod selectedNeoSpeedType; +#elif (USE_WS2812_HARDWARE == NEO_HW_SK6812) + typedef NeoEsp8266BitBangSk6812Method selectedNeoSpeedType; +#else + typedef NeoEsp8266BitBang800KbpsMethod selectedNeoSpeedType; +#endif + +#endif + +NeoPixelBus *strip = nullptr; + +struct WsColor { + uint8_t red, green, blue; +}; + +struct ColorScheme { + WsColor* colors; + uint8_t count; +}; + +WsColor kIncandescent[2] = { 255,140,20, 0,0,0 }; +WsColor kRgb[3] = { 255,0,0, 0,255,0, 0,0,255 }; +WsColor kChristmas[2] = { 255,0,0, 0,255,0 }; +WsColor kHanukkah[2] = { 0,0,255, 255,255,255 }; +WsColor kwanzaa[3] = { 255,0,0, 0,0,0, 0,255,0 }; +WsColor kRainbow[7] = { 255,0,0, 255,128,0, 255,255,0, 0,255,0, 0,0,255, 128,0,255, 255,0,255 }; +WsColor kFire[3] = { 255,0,0, 255,102,0, 255,192,0 }; +ColorScheme kSchemes[WS2812_SCHEMES -1] = { + kIncandescent, 2, + kRgb, 3, + kChristmas, 2, + kHanukkah, 2, + kwanzaa, 3, + kRainbow, 7, + kFire, 3 }; + +uint8_t kWidth[5] = { + 1, + 2, + 4, + 8, + 255 }; +uint8_t kWsRepeat[5] = { + 8, + 6, + 4, + 2, + 1 }; + +struct WS2812 { + uint8_t show_next = 1; + uint8_t scheme_offset = 0; + bool suspend_update = false; +} Ws2812; + + + +void Ws2812StripShow(void) +{ +#if (USE_WS2812_CTYPE > NEO_3LED) + RgbwColor c; +#else + RgbColor c; +#endif + + if (Settings.light_correction) { + for (uint32_t i = 0; i < Settings.light_pixels; i++) { + c = strip->GetPixelColor(i); + c.R = ledGamma(c.R); + c.G = ledGamma(c.G); + c.B = ledGamma(c.B); +#if (USE_WS2812_CTYPE > NEO_3LED) + c.W = ledGamma(c.W); +#endif + strip->SetPixelColor(i, c); + } + } + strip->Show(); +} + +int mod(int a, int b) +{ + int ret = a % b; + if (ret < 0) ret += b; + return ret; +} + +void Ws2812UpdatePixelColor(int position, struct WsColor hand_color, float offset) +{ +#if (USE_WS2812_CTYPE > NEO_3LED) + RgbwColor color; +#else + RgbColor color; +#endif + + uint32_t mod_position = mod(position, (int)Settings.light_pixels); + + color = strip->GetPixelColor(mod_position); + float dimmer = 100 / (float)Settings.light_dimmer; + color.R = tmin(color.R + ((hand_color.red / dimmer) * offset), 255); + color.G = tmin(color.G + ((hand_color.green / dimmer) * offset), 255); + color.B = tmin(color.B + ((hand_color.blue / dimmer) * offset), 255); + strip->SetPixelColor(mod_position, color); +} + +void Ws2812UpdateHand(int position, uint32_t index) +{ + uint32_t width = Settings.light_width; + if (index < WS_MARKER) { width = Settings.ws_width[index]; } + if (!width) { return; } + + position = (position + Settings.light_rotation) % Settings.light_pixels; + + if (Settings.flag.ws_clock_reverse) { + position = Settings.light_pixels -position; + } + WsColor hand_color = { Settings.ws_color[index][WS_RED], Settings.ws_color[index][WS_GREEN], Settings.ws_color[index][WS_BLUE] }; + + Ws2812UpdatePixelColor(position, hand_color, 1); + + uint32_t range = ((width -1) / 2) +1; + for (uint32_t h = 1; h < range; h++) { + float offset = (float)(range - h) / (float)range; + Ws2812UpdatePixelColor(position -h, hand_color, offset); + Ws2812UpdatePixelColor(position +h, hand_color, offset); + } +} + +void Ws2812Clock(void) +{ + strip->ClearTo(0); + int clksize = 60000 / (int)Settings.light_pixels; + + Ws2812UpdateHand((RtcTime.second * 1000) / clksize, WS_SECOND); + Ws2812UpdateHand((RtcTime.minute * 1000) / clksize, WS_MINUTE); + Ws2812UpdateHand((((RtcTime.hour % 12) * 5000) + ((RtcTime.minute * 1000) / 12 )) / clksize, WS_HOUR); + if (Settings.ws_color[WS_MARKER][WS_RED] + Settings.ws_color[WS_MARKER][WS_GREEN] + Settings.ws_color[WS_MARKER][WS_BLUE]) { + for (uint32_t i = 0; i < 12; i++) { + Ws2812UpdateHand((i * 5000) / clksize, WS_MARKER); + } + } + + Ws2812StripShow(); +} + +void Ws2812GradientColor(uint32_t schemenr, struct WsColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i) +{ + + + + + ColorScheme scheme = kSchemes[schemenr]; + uint32_t curRange = i / range; + uint32_t rangeIndex = i % range; + uint32_t colorIndex = rangeIndex / gradRange; + uint32_t start = colorIndex; + uint32_t end = colorIndex +1; + if (curRange % 2 != 0) { + start = (scheme.count -1) - start; + end = (scheme.count -1) - end; + } + float dimmer = 100 / (float)Settings.light_dimmer; + float fmyRed = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].red, scheme.colors[end].red) / dimmer; + float fmyGrn = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].green, scheme.colors[end].green) / dimmer; + float fmyBlu = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].blue, scheme.colors[end].blue) / dimmer; + mColor->red = (uint8_t)fmyRed; + mColor->green = (uint8_t)fmyGrn; + mColor->blue = (uint8_t)fmyBlu; +} + +void Ws2812Gradient(uint32_t schemenr) +{ + + + + + +#if (USE_WS2812_CTYPE > NEO_3LED) + RgbwColor c; + c.W = 0; +#else + RgbColor c; +#endif + + ColorScheme scheme = kSchemes[schemenr]; + if (scheme.count < 2) { return; } + + uint32_t repeat = kWsRepeat[Settings.light_width]; + uint32_t range = (uint32_t)ceil((float)Settings.light_pixels / (float)repeat); + uint32_t gradRange = (uint32_t)ceil((float)range / (float)(scheme.count - 1)); + uint32_t speed = ((Settings.light_speed * 2) -1) * (STATES / 10); + uint32_t offset = speed > 0 ? Light.strip_timer_counter / speed : 0; + + WsColor oldColor, currentColor; + Ws2812GradientColor(schemenr, &oldColor, range, gradRange, offset); + currentColor = oldColor; + for (uint32_t i = 0; i < Settings.light_pixels; i++) { + if (kWsRepeat[Settings.light_width] > 1) { + Ws2812GradientColor(schemenr, ¤tColor, range, gradRange, i +offset); + } + if (Settings.light_speed > 0) { + + c.R = map(Light.strip_timer_counter % speed, 0, speed, oldColor.red, currentColor.red); + c.G = map(Light.strip_timer_counter % speed, 0, speed, oldColor.green, currentColor.green); + c.B = map(Light.strip_timer_counter % speed, 0, speed, oldColor.blue, currentColor.blue); + } + else { + + c.R = currentColor.red; + c.G = currentColor.green; + c.B = currentColor.blue; + } + strip->SetPixelColor(i, c); + oldColor = currentColor; + } + Ws2812StripShow(); +} + +void Ws2812Bars(uint32_t schemenr) +{ + + + + + +#if (USE_WS2812_CTYPE > NEO_3LED) + RgbwColor c; + c.W = 0; +#else + RgbColor c; +#endif + + ColorScheme scheme = kSchemes[schemenr]; + + uint32_t maxSize = Settings.light_pixels / scheme.count; + if (kWidth[Settings.light_width] > maxSize) { maxSize = 0; } + + uint32_t speed = ((Settings.light_speed * 2) -1) * (STATES / 10); + uint32_t offset = (speed > 0) ? Light.strip_timer_counter / speed : 0; + + WsColor mcolor[scheme.count]; + memcpy(mcolor, scheme.colors, sizeof(mcolor)); + float dimmer = 100 / (float)Settings.light_dimmer; + for (uint32_t i = 0; i < scheme.count; i++) { + float fmyRed = (float)mcolor[i].red / dimmer; + float fmyGrn = (float)mcolor[i].green / dimmer; + float fmyBlu = (float)mcolor[i].blue / dimmer; + mcolor[i].red = (uint8_t)fmyRed; + mcolor[i].green = (uint8_t)fmyGrn; + mcolor[i].blue = (uint8_t)fmyBlu; + } + uint32_t colorIndex = offset % scheme.count; + for (uint32_t i = 0; i < Settings.light_pixels; i++) { + if (maxSize) { colorIndex = ((i + offset) % (scheme.count * kWidth[Settings.light_width])) / kWidth[Settings.light_width]; } + c.R = mcolor[colorIndex].red; + c.G = mcolor[colorIndex].green; + c.B = mcolor[colorIndex].blue; + strip->SetPixelColor(i, c); + } + Ws2812StripShow(); +} + +void Ws2812Clear(void) +{ + strip->ClearTo(0); + strip->Show(); + Ws2812.show_next = 1; +} + +void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white) +{ +#if (USE_WS2812_CTYPE > NEO_3LED) + RgbwColor lcolor; + lcolor.W = white; +#else + RgbColor lcolor; +#endif + + lcolor.R = red; + lcolor.G = green; + lcolor.B = blue; + if (led) { + strip->SetPixelColor(led -1, lcolor); + } else { + + for (uint32_t i = 0; i < Settings.light_pixels; i++) { + strip->SetPixelColor(i, lcolor); + } + } + + if (!Ws2812.suspend_update) { + strip->Show(); + Ws2812.show_next = 1; + } +} + +char* Ws2812GetColor(uint32_t led, char* scolor) +{ + uint8_t sl_ledcolor[4]; + + #if (USE_WS2812_CTYPE > NEO_3LED) + RgbwColor lcolor = strip->GetPixelColor(led -1); + sl_ledcolor[3] = lcolor.W; + #else + RgbColor lcolor = strip->GetPixelColor(led -1); + #endif + sl_ledcolor[0] = lcolor.R; + sl_ledcolor[1] = lcolor.G; + sl_ledcolor[2] = lcolor.B; + scolor[0] = '\0'; + for (uint32_t i = 0; i < Light.subtype; i++) { + if (Settings.flag.decimal_text) { + snprintf_P(scolor, 25, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", sl_ledcolor[i]); + } else { + snprintf_P(scolor, 25, PSTR("%s%02X"), scolor, sl_ledcolor[i]); + } + } + return scolor; +} + + + + + +void Ws2812ForceSuspend (void) +{ + Ws2812.suspend_update = true; +} + +void Ws2812ForceUpdate (void) +{ + Ws2812.suspend_update = false; + strip->Show(); + Ws2812.show_next = 1; +} + + + +bool Ws2812SetChannels(void) +{ + uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; + + Ws2812SetColor(0, cur_col[0], cur_col[1], cur_col[2], cur_col[3]); + + return true; +} + +void Ws2812ShowScheme(void) +{ + uint32_t scheme = Settings.light_scheme - Ws2812.scheme_offset; + + switch (scheme) { + case 0: + if ((1 == state_250mS) || (Ws2812.show_next)) { + Ws2812Clock(); + Ws2812.show_next = 0; + } + break; + default: + if (1 == Settings.light_fade) { + Ws2812Gradient(scheme -1); + } else { + Ws2812Bars(scheme -1); + } + Ws2812.show_next = 1; + break; + } +} + +void Ws2812ModuleSelected(void) +{ + if (pin[GPIO_WS2812] < 99) { + + + strip = new NeoPixelBus(WS2812_MAX_LEDS, pin[GPIO_WS2812]); + strip->Begin(); + + Ws2812Clear(); + + Ws2812.scheme_offset = Light.max_scheme +1; + Light.max_scheme += WS2812_SCHEMES; + +#if (USE_WS2812_CTYPE > NEO_3LED) + light_type = LT_RGBW; +#else + light_type = LT_RGB; +#endif + light_flg = XLGT_01; + } +} + + + +void CmndLed(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Settings.light_pixels)) { + if (XdrvMailbox.data_len > 0) { + char *p; + uint16_t idx = XdrvMailbox.index; + Ws2812ForceSuspend(); + for (char *color = strtok_r(XdrvMailbox.data, " ", &p); color; color = strtok_r(nullptr, " ", &p)) { + if (LightColorEntry(color, strlen(color))) { + Ws2812SetColor(idx, Light.entry_color[0], Light.entry_color[1], Light.entry_color[2], Light.entry_color[3]); + idx++; + if (idx > Settings.light_pixels) { break; } + } else { + break; + } + } + Ws2812ForceUpdate(); + } + char scolor[LIGHT_COLOR_SIZE]; + ResponseCmndIdxChar(Ws2812GetColor(XdrvMailbox.index, scolor)); + } +} + +void CmndPixels(void) +{ + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= WS2812_MAX_LEDS)) { + Settings.light_pixels = XdrvMailbox.payload; + Settings.light_rotation = 0; + Ws2812Clear(); + Light.update = true; + } + ResponseCmndNumber(Settings.light_pixels); +} + +void CmndRotation(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < Settings.light_pixels)) { + Settings.light_rotation = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.light_rotation); +} + +void CmndWidth(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) { + if (1 == XdrvMailbox.index) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 4)) { + Settings.light_width = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.light_width); + } else { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32)) { + Settings.ws_width[XdrvMailbox.index -2] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Settings.ws_width[XdrvMailbox.index -2]); + } + } +} + + + + + +bool Xlgt01(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_SET_CHANNELS: + result = Ws2812SetChannels(); + break; + case FUNC_SET_SCHEME: + Ws2812ShowScheme(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kWs2812Commands, Ws2812Command); + break; + case FUNC_MODULE_INIT: + Ws2812ModuleSelected(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xlgt_02_my92x1.ino" +# 20 "S:/Development/Tasmota/tasmota/xlgt_02_my92x1.ino" +#ifdef USE_LIGHT +#ifdef USE_MY92X1 + + + + +#define XLGT_02 2 + +struct MY92X1 { + uint8_t pdi_pin = 0; + uint8_t pdcki_pin = 0; + uint8_t model = 0; +} My92x1; + +extern "C" { + void os_delay_us(unsigned int); +} + +void LightDiPulse(uint8_t times) +{ + for (uint32_t i = 0; i < times; i++) { + digitalWrite(My92x1.pdi_pin, HIGH); + digitalWrite(My92x1.pdi_pin, LOW); + } +} + +void LightDckiPulse(uint8_t times) +{ + for (uint32_t i = 0; i < times; i++) { + digitalWrite(My92x1.pdcki_pin, HIGH); + digitalWrite(My92x1.pdcki_pin, LOW); + } +} + +void LightMy92x1Write(uint8_t data) +{ + for (uint32_t i = 0; i < 4; i++) { + digitalWrite(My92x1.pdcki_pin, LOW); + digitalWrite(My92x1.pdi_pin, (data & 0x80)); + digitalWrite(My92x1.pdcki_pin, HIGH); + data = data << 1; + digitalWrite(My92x1.pdi_pin, (data & 0x80)); + digitalWrite(My92x1.pdcki_pin, LOW); + digitalWrite(My92x1.pdi_pin, LOW); + data = data << 1; + } +} + +void LightMy92x1Init(void) +{ + uint8_t chips[3] = { 1, 2, 2 }; + + LightDckiPulse(chips[My92x1.model] * 32); + os_delay_us(12); + + + LightDiPulse(12); + os_delay_us(12); + for (uint32_t n = 0; n < chips[My92x1.model]; n++) { + LightMy92x1Write(0x18); + } + os_delay_us(12); + + + LightDiPulse(16); + os_delay_us(12); +} + +void LightMy92x1Duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c) +{ + uint8_t channels[3] = { 4, 6, 6 }; + + uint8_t duty[3][6] = {{ duty_r, duty_g, duty_b, duty_w, 0, 0 }, + { duty_w, duty_c, 0, duty_g, duty_r, duty_b }, + { duty_r, duty_g, duty_b, duty_w, duty_w, duty_w }}; + + os_delay_us(12); + for (uint32_t channel = 0; channel < channels[My92x1.model]; channel++) { + LightMy92x1Write(duty[My92x1.model][channel]); + } + os_delay_us(12); + LightDiPulse(8); + os_delay_us(12); +} + + + +bool My92x1SetChannels(void) +{ + uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; + + LightMy92x1Duty(cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]); + + return true; +} + +void My92x1ModuleSelected(void) +{ + if ((pin[GPIO_DCKI] < 99) && (pin[GPIO_DI] < 99)) { + My92x1.pdi_pin = pin[GPIO_DI]; + My92x1.pdcki_pin = pin[GPIO_DCKI]; + + pinMode(My92x1.pdi_pin, OUTPUT); + pinMode(My92x1.pdcki_pin, OUTPUT); + digitalWrite(My92x1.pdi_pin, LOW); + digitalWrite(My92x1.pdcki_pin, LOW); + + My92x1.model = 2; + light_type = LT_RGBW; + if (AILIGHT == my_module_type) { + My92x1.model = 0; + + } + else if (SONOFF_B1 == my_module_type) { + My92x1.model = 1; + light_type = LT_RGBWC; + } + + LightMy92x1Init(); + + light_flg = XLGT_02; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: MY29x1 Found")); + } +} + + + + + +bool Xlgt02(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_SET_CHANNELS: + result = My92x1SetChannels(); + break; + case FUNC_MODULE_INIT: + My92x1ModuleSelected(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xlgt_03_sm16716.ino" +# 20 "S:/Development/Tasmota/tasmota/xlgt_03_sm16716.ino" +#ifdef USE_LIGHT +#ifdef USE_SM16716 + + + + + + + +#define XLGT_03 3 + +#define D_LOG_SM16716 "SM16716: " + +struct SM16716 { + uint8_t pin_clk = 0; + uint8_t pin_dat = 0; + uint8_t pin_sel = 0; + bool enabled = false; +} Sm16716; + +void SM16716_SendBit(uint8_t v) +{ + + + + + + digitalWrite(Sm16716.pin_dat, (v != 0) ? HIGH : LOW); + + digitalWrite(Sm16716.pin_clk, HIGH); + + digitalWrite(Sm16716.pin_clk, LOW); +} + +void SM16716_SendByte(uint8_t v) +{ + uint8_t mask; + + for (mask = 0x80; mask; mask >>= 1) { + SM16716_SendBit(v & mask); + } +} + +void SM16716_Update(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b) +{ + if (Sm16716.pin_sel < 99) { + bool should_enable = (duty_r | duty_g | duty_b); + if (!Sm16716.enabled && should_enable) { + DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color on")); + Sm16716.enabled = true; + digitalWrite(Sm16716.pin_sel, HIGH); + + + delayMicroseconds(1000); + SM16716_Init(); + } + else if (Sm16716.enabled && !should_enable) { + DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color off")); + Sm16716.enabled = false; + digitalWrite(Sm16716.pin_sel, LOW); + } + } + DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "Update; rgb=%02x%02x%02x"), duty_r, duty_g, duty_b); + + + SM16716_SendBit(1); + SM16716_SendByte(duty_r); + SM16716_SendByte(duty_g); + SM16716_SendByte(duty_b); + + + + + + SM16716_SendBit(0); + SM16716_SendByte(0); + SM16716_SendByte(0); + SM16716_SendByte(0); +} +# 111 "S:/Development/Tasmota/tasmota/xlgt_03_sm16716.ino" +void SM16716_Init(void) +{ + for (uint32_t t_init = 0; t_init < 50; ++t_init) { + SM16716_SendBit(0); + } +} + + + +bool Sm16716SetChannels(void) +{ +# 132 "S:/Development/Tasmota/tasmota/xlgt_03_sm16716.ino" + uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; + + SM16716_Update(cur_col[0], cur_col[1], cur_col[2]); + + return true; +} + +void Sm16716ModuleSelected(void) +{ + if ((pin[GPIO_SM16716_CLK] < 99) && (pin[GPIO_SM16716_DAT] < 99)) { + Sm16716.pin_clk = pin[GPIO_SM16716_CLK]; + Sm16716.pin_dat = pin[GPIO_SM16716_DAT]; + Sm16716.pin_sel = pin[GPIO_SM16716_SEL]; +# 157 "S:/Development/Tasmota/tasmota/xlgt_03_sm16716.ino" + pinMode(Sm16716.pin_clk, OUTPUT); + digitalWrite(Sm16716.pin_clk, LOW); + + pinMode(Sm16716.pin_dat, OUTPUT); + digitalWrite(Sm16716.pin_dat, LOW); + + if (Sm16716.pin_sel < 99) { + pinMode(Sm16716.pin_sel, OUTPUT); + digitalWrite(Sm16716.pin_sel, LOW); + + } else { + + SM16716_Init(); + } + + LightPwmOffset(LST_RGB); + light_type += LST_RGB; + light_flg = XLGT_03; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: SM16716 Found")); + } +} + + + + + +bool Xlgt03(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_SET_CHANNELS: + result = Sm16716SetChannels(); + break; + case FUNC_MODULE_INIT: + Sm16716ModuleSelected(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xlgt_04_sm2135.ino" +# 20 "S:/Development/Tasmota/tasmota/xlgt_04_sm2135.ino" +#ifdef USE_LIGHT +#ifdef USE_SM2135 + + + + + + +#define XLGT_04 4 + +#define SM2135_ADDR_MC 0xC0 +#define SM2135_ADDR_CH 0xC1 +#define SM2135_ADDR_R 0xC2 +#define SM2135_ADDR_G 0xC3 +#define SM2135_ADDR_B 0xC4 +#define SM2135_ADDR_C 0xC5 +#define SM2135_ADDR_W 0xC6 + +#define SM2135_RGB 0x00 +#define SM2135_CW 0x80 + +#define SM2135_10MA 0x00 +#define SM2135_15MA 0x01 +#define SM2135_20MA 0x02 +#define SM2135_25MA 0x03 +#define SM2135_30MA 0x04 +#define SM2135_35MA 0x05 +#define SM2135_40MA 0x06 +#define SM2135_45MA 0x07 +#define SM2135_50MA 0x08 +#define SM2135_55MA 0x09 +#define SM2135_60MA 0x0A + + +const uint8_t SM2135_CURRENT = (SM2135_20MA << 4) | SM2135_15MA; + +struct SM2135 { + uint8_t clk = 0; + uint8_t data = 0; +} Sm2135; + +uint8_t Sm2135Write(uint8_t data) +{ + for (uint32_t i = 0; i < 8; i++) { + digitalWrite(Sm2135.clk, LOW); + digitalWrite(Sm2135.data, (data & 0x80)); + digitalWrite(Sm2135.clk, HIGH); + data = data << 1; + } + digitalWrite(Sm2135.clk, LOW); + digitalWrite(Sm2135.data, HIGH); + pinMode(Sm2135.data, INPUT); + digitalWrite(Sm2135.clk, HIGH); + uint8_t ack = digitalRead(Sm2135.data); + pinMode(Sm2135.data, OUTPUT); + return ack; +} + +void Sm2135Send(uint8_t *buffer, uint8_t size) +{ + digitalWrite(Sm2135.data, LOW); + for (uint32_t i = 0; i < size; i++) { + Sm2135Write(buffer[i]); + } + digitalWrite(Sm2135.clk, LOW); + digitalWrite(Sm2135.clk, HIGH); + digitalWrite(Sm2135.data, HIGH); +} + + + +bool Sm2135SetChannels(void) +{ + uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; + uint8_t data[6]; + + if ((0 == cur_col[0]) && (0 == cur_col[1]) && (0 == cur_col[2])) { +# 106 "S:/Development/Tasmota/tasmota/xlgt_04_sm2135.ino" + data[0] = SM2135_ADDR_MC; + data[1] = SM2135_CURRENT; + data[2] = SM2135_CW; + Sm2135Send(data, 3); + delay(1); + data[0] = SM2135_ADDR_C; + data[1] = cur_col[4]; + data[2] = cur_col[3]; + Sm2135Send(data, 3); + } else { + + + + + + + + data[0] = SM2135_ADDR_MC; + data[1] = SM2135_CURRENT; + data[2] = SM2135_RGB; + data[3] = cur_col[1]; + data[4] = cur_col[0]; + data[5] = cur_col[2]; + Sm2135Send(data, 6); + } + + return true; +} + +void Sm2135ModuleSelected(void) +{ + if ((pin[GPIO_SM2135_CLK] < 99) && (pin[GPIO_SM2135_DAT] < 99)) { + Sm2135.clk = pin[GPIO_SM2135_CLK]; + Sm2135.data = pin[GPIO_SM2135_DAT]; + + pinMode(Sm2135.data, OUTPUT); + digitalWrite(Sm2135.data, HIGH); + pinMode(Sm2135.clk, OUTPUT); + digitalWrite(Sm2135.clk, HIGH); + + light_type = LT_RGBWC; + light_flg = XLGT_04; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: SM2135 Found")); + } +} + + + + + +bool Xlgt04(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_SET_CHANNELS: + result = Sm2135SetChannels(); + break; + case FUNC_MODULE_INIT: + Sm2135ModuleSelected(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xlgt_05_sonoff_l1.ino" +# 20 "S:/Development/Tasmota/tasmota/xlgt_05_sonoff_l1.ino" +#ifdef USE_LIGHT +#ifdef USE_SONOFF_L1 + + + + +#define XLGT_05 5 + +#define SONOFF_L1_BUFFER_SIZE 140 + +#define SONOFF_L1_MODE_COLORFUL 1 +#define SONOFF_L1_MODE_COLORFUL_GRADIENT 2 +#define SONOFF_L1_MODE_COLORFUL_BREATH 3 +#define SONOFF_L1_MODE_DIY_GRADIENT 4 +#define SONOFF_L1_MODE_DIY_PULSE 5 +#define SONOFF_L1_MODE_DIY_BREATH 6 +#define SONOFF_L1_MODE_DIY_STROBE 7 +#define SONOFF_L1_MODE_RGB_GRADIENT 8 +#define SONOFF_L1_MODE_RGB_PULSE 9 +#define SONOFF_L1_MODE_RGB_BREATH 10 +#define SONOFF_L1_MODE_RGB_STROBE 11 +#define SONOFF_L1_MODE_SYNC_TO_MUSIC 12 + +struct SNFL1 { + uint32_t unlock = 0; + bool receive_ready = true; +} Snfl1; + + + +void SnfL1Send(const char *buffer) +{ + + + Serial.print(buffer); + Serial.write(0x1B); + Serial.flush(); +} + +void SnfL1SerialSendOk(void) +{ + char buffer[16]; + snprintf_P(buffer, sizeof(buffer), PSTR("AT+SEND=ok")); + + SnfL1Send(buffer); +} + +bool SnfL1SerialInput(void) +{ + if (serial_in_byte != 0x1B) { + if (serial_in_byte_counter >= 140) { + serial_in_byte_counter = 0; + } + if (serial_in_byte_counter || (!serial_in_byte_counter && ('A' == serial_in_byte))) { + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + } + } else { + serial_in_buffer[serial_in_byte_counter++] = 0x00; + + + + + + + if (!strncmp(serial_in_buffer +3, "RESULT", 6)) { + Snfl1.receive_ready = true; + } + else if (!strncmp(serial_in_buffer +3, "UPDATE", 6)) { + char cmnd_dimmer[20]; + char cmnd_color[20]; + char *end_str; + char *string = serial_in_buffer +10; + char *token = strtok_r(string, ",", &end_str); + + bool color_updated[3] = { false, false, false }; + uint8_t current_color[3]; + memcpy(current_color, Settings.light_color, 3); + + bool switch_state = false; + bool is_power_change = false; + bool is_color_change = false; + bool is_brightness_change = false; + + while (token != nullptr) { + char* end_token; + char* token2 = strtok_r(token, ":", &end_token); + char* token3 = strtok_r(nullptr, ":", &end_token); + + if (!strncmp(token2, "\"sequence\"", 10)) { + + + + token = nullptr; + } + + else if (!strncmp(token2, "\"switch\"", 8)) { + switch_state = !strncmp(token3, "\"on\"", 4) ? true : false; + + + + is_power_change = (switch_state != Light.power); + } + + else if (!strncmp(token2, "\"color", 6)) { + char color_channel_name = token2[6]; + int color_index; + switch(color_channel_name) + { + case 'R': color_index = 0; + break; + case 'G': color_index = 1; + break; + case 'B': color_index = 2; + break; + } + int color_value = atoi(token3); + current_color[color_index] = color_value; + color_updated[color_index] = true; + + bool all_color_channels_updated = color_updated[0] && color_updated[1] && color_updated[2]; + if (all_color_channels_updated) { + + + + + + is_color_change = (Light.power && (memcmp(current_color, Settings.light_color, 3) != 0)); + } + snprintf_P(cmnd_color, sizeof(cmnd_color), PSTR(D_CMND_COLOR "2 %02x%02x%02x"), current_color[0], current_color[1], current_color[2]); + } + + else if (!strncmp(token2, "\"bright\"", 8)) { + uint8_t dimmer = atoi(token3); + + + + is_brightness_change = (Light.power && (dimmer > 0) && (dimmer != Settings.light_dimmer)); + snprintf_P(cmnd_dimmer, sizeof(cmnd_dimmer), PSTR(D_CMND_DIMMER " %d"), dimmer); + } + + token = strtok_r(nullptr, ",", &end_str); + } + + if (is_power_change) { + if (Settings.light_scheme > 0) { + if (!switch_state) { + char cmnd_scheme[20]; + snprintf_P(cmnd_scheme, sizeof(cmnd_scheme), PSTR(D_CMND_SCHEME " 0")); + ExecuteCommand(cmnd_scheme, SRC_SWITCH); + } + } else { + ExecuteCommandPower(1, switch_state, SRC_SWITCH); + } + } + else if (is_brightness_change) { + ExecuteCommand(cmnd_dimmer, SRC_SWITCH); + } + else if (Light.power && is_color_change) { + if (0 == Settings.light_scheme) { + if (Settings.light_fade) { + char cmnd_fade[20]; + snprintf_P(cmnd_fade, sizeof(cmnd_fade), PSTR(D_CMND_FADE " 0")); + ExecuteCommand(cmnd_fade, SRC_SWITCH); + } + ExecuteCommand(cmnd_color, SRC_SWITCH); + } + } + } + + SnfL1SerialSendOk(); + + return true; + } + serial_in_byte = 0; + return false; +} + + + +bool SnfL1SetChannels(void) +{ + if (Snfl1.receive_ready || TimeReached(Snfl1.unlock)) { + + uint8_t *scale_col = (uint8_t*)XdrvMailbox.topic; + + char buffer[140]; + snprintf_P(buffer, sizeof(buffer), PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"light_type\":1,\"colorR\":%d,\"colorG\":%d,\"colorB\":%d,\"bright\":%d,\"mode\":%d"), + LocalTime(), millis()%1000, + Light.power ? "on" : "off", + scale_col[0], scale_col[1], scale_col[2], + light_state.getDimmer(), + SONOFF_L1_MODE_COLORFUL); + + SnfL1Send(buffer); + + Snfl1.unlock = millis() + 500; + Snfl1.receive_ready = false; + } + return true; +} + +void SnfL1ModuleSelected(void) +{ + if (SONOFF_L1 == my_module_type) { + if ((pin[GPIO_RXD] < 99) && (pin[GPIO_TXD] < 99)) { + SetSerial(19200, TS_SERIAL_8N1); + + light_type = LT_RGB; + light_flg = XLGT_05; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: Sonoff L1 Found")); + } + } +} + + + + + +bool Xlgt05(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_SERIAL: + result = SnfL1SerialInput(); + break; + case FUNC_SET_CHANNELS: + result = SnfL1SetChannels(); + break; + case FUNC_MODULE_INIT: + SnfL1ModuleSelected(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xlgt_interface.ino" +# 20 "S:/Development/Tasmota/tasmota/xlgt_interface.ino" +#ifdef USE_LIGHT + +#ifdef XFUNC_PTR_IN_ROM +bool (* const xlgt_func_ptr[])(uint8_t) PROGMEM = { +#else +bool (* const xlgt_func_ptr[])(uint8_t) = { +#endif + +#ifdef XLGT_01 + &Xlgt01, +#endif + +#ifdef XLGT_02 + &Xlgt02, +#endif + +#ifdef XLGT_03 + &Xlgt03, +#endif + +#ifdef XLGT_04 + &Xlgt04, +#endif + +#ifdef XLGT_05 + &Xlgt05, +#endif + +#ifdef XLGT_06 + &Xlgt06, +#endif + +#ifdef XLGT_07 + &Xlgt07, +#endif + +#ifdef XLGT_08 + &Xlgt08, +#endif + +#ifdef XLGT_09 + &Xlgt09, +#endif + +#ifdef XLGT_10 + &Xlgt10, +#endif + +#ifdef XLGT_11 + &Xlgt11, +#endif + +#ifdef XLGT_12 + &Xlgt12, +#endif + +#ifdef XLGT_13 + &Xlgt13, +#endif + +#ifdef XLGT_14 + &Xlgt14, +#endif + +#ifdef XLGT_15 + &Xlgt15, +#endif + +#ifdef XLGT_16 + &Xlgt16 +#endif +}; + +const uint8_t xlgt_present = sizeof(xlgt_func_ptr) / sizeof(xlgt_func_ptr[0]); + +uint8_t xlgt_active = 0; + +bool XlgtCall(uint8_t function) +{ + DEBUG_TRACE_LOG(PSTR("LGT: %d"), function); + + if (FUNC_MODULE_INIT == function) { + for (uint32_t x = 0; x < xlgt_present; x++) { + xlgt_func_ptr[x](function); + if (light_flg) { + xlgt_active = x; + return true; + } + } + } + else if (light_flg) { + return xlgt_func_ptr[xlgt_active](function); + } + return false; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_01_hlw8012.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_01_hlw8012.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_HLW8012 + + + + + + +#define XNRG_01 1 + + +#define HLW_PREF 10000 +#define HLW_UREF 2200 +#define HLW_IREF 4545 + + +#define HJL_PREF 1362 +#define HJL_UREF 822 +#define HJL_IREF 3300 + +#define HLW_POWER_PROBE_TIME 10 +#define HLW_SAMPLE_COUNT 10 + + + +struct HLW { +#ifdef HLW_DEBUG + unsigned long debug[HLW_SAMPLE_COUNT]; +#endif + unsigned long cf_pulse_length = 0; + unsigned long cf_pulse_last_time = 0; + unsigned long cf_power_pulse_length = 0; + + unsigned long cf1_pulse_length = 0; + unsigned long cf1_pulse_last_time = 0; + unsigned long cf1_summed_pulse_length = 0; + unsigned long cf1_pulse_counter = 0; + unsigned long cf1_voltage_pulse_length = 0; + unsigned long cf1_current_pulse_length = 0; + + unsigned long energy_period_counter = 0; + + unsigned long power_ratio = 0; + unsigned long voltage_ratio = 0; + unsigned long current_ratio = 0; + + uint8_t model_type = 0; + uint8_t cf1_timer = 0; + uint8_t power_retry = 0; + bool select_ui_flag = false; + bool ui_flag = true; + bool load_off = true; +} Hlw; + + +#ifndef USE_WS2812_DMA +void HlwCfInterrupt(void) ICACHE_RAM_ATTR; +void HlwCf1Interrupt(void) ICACHE_RAM_ATTR; +#endif + +void HlwCfInterrupt(void) +{ + unsigned long us = micros(); + + if (Hlw.load_off) { + Hlw.cf_pulse_last_time = us; + Hlw.load_off = false; + } else { + Hlw.cf_pulse_length = us - Hlw.cf_pulse_last_time; + Hlw.cf_pulse_last_time = us; + Hlw.energy_period_counter++; + } + Energy.data_valid[0] = 0; +} + +void HlwCf1Interrupt(void) +{ + unsigned long us = micros(); + + Hlw.cf1_pulse_length = us - Hlw.cf1_pulse_last_time; + Hlw.cf1_pulse_last_time = us; + if ((Hlw.cf1_timer > 2) && (Hlw.cf1_timer < 8)) { + Hlw.cf1_summed_pulse_length += Hlw.cf1_pulse_length; +#ifdef HLW_DEBUG + Hlw.debug[Hlw.cf1_pulse_counter] = Hlw.cf1_pulse_length; +#endif + Hlw.cf1_pulse_counter++; + if (HLW_SAMPLE_COUNT == Hlw.cf1_pulse_counter) { + Hlw.cf1_timer = 8; + } + } + Energy.data_valid[0] = 0; +} + + + +void HlwEvery200ms(void) +{ + unsigned long cf1_pulse_length = 0; + unsigned long hlw_w = 0; + unsigned long hlw_u = 0; + unsigned long hlw_i = 0; + + if (micros() - Hlw.cf_pulse_last_time > (HLW_POWER_PROBE_TIME * 1000000)) { + Hlw.cf_pulse_length = 0; + Hlw.load_off = true; + } + Hlw.cf_power_pulse_length = Hlw.cf_pulse_length; + + if (Hlw.cf_power_pulse_length && Energy.power_on && !Hlw.load_off) { + hlw_w = (Hlw.power_ratio * Settings.energy_power_calibration) / Hlw.cf_power_pulse_length ; + Energy.active_power[0] = (float)hlw_w / 10; + Hlw.power_retry = 1; + } else { + if (Hlw.power_retry) { + Hlw.power_retry--; + } else { + Energy.active_power[0] = 0; + } + } + + if (pin[GPIO_NRG_CF1] < 99) { + Hlw.cf1_timer++; + if (Hlw.cf1_timer >= 8) { + Hlw.cf1_timer = 0; + Hlw.select_ui_flag = (Hlw.select_ui_flag) ? false : true; + DigitalWrite(GPIO_NRG_SEL, Hlw.select_ui_flag); + + if (Hlw.cf1_pulse_counter) { + cf1_pulse_length = Hlw.cf1_summed_pulse_length / Hlw.cf1_pulse_counter; + } + +#ifdef HLW_DEBUG + + char stemp[100]; + stemp[0] = '\0'; + for (uint32_t i = 0; i < Hlw.cf1_pulse_counter; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("%s %d"), stemp, Hlw.debug[i]); + } + for (uint32_t i = 0; i < Hlw.cf1_pulse_counter; i++) { + for (uint32_t j = i + 1; j < Hlw.cf1_pulse_counter; j++) { + if (Hlw.debug[i] > Hlw.debug[j]) { + std::swap(Hlw.debug[i], Hlw.debug[j]); + } + } + } + unsigned long median = Hlw.debug[(Hlw.cf1_pulse_counter +1) / 2]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NRG: power %d, ui %d, cnt %d, smpl%s, sum %d, mean %d, median %d"), + Hlw.cf_power_pulse_length , Hlw.select_ui_flag, Hlw.cf1_pulse_counter, stemp, Hlw.cf1_summed_pulse_length, cf1_pulse_length, median); +#endif + + if (Hlw.select_ui_flag == Hlw.ui_flag) { + Hlw.cf1_voltage_pulse_length = cf1_pulse_length; + + if (Hlw.cf1_voltage_pulse_length && Energy.power_on) { + hlw_u = (Hlw.voltage_ratio * Settings.energy_voltage_calibration) / Hlw.cf1_voltage_pulse_length ; + Energy.voltage[0] = (float)hlw_u / 10; + } else { + Energy.voltage[0] = 0; + } + + } else { + Hlw.cf1_current_pulse_length = cf1_pulse_length; + + if (Hlw.cf1_current_pulse_length && Energy.active_power[0]) { + hlw_i = (Hlw.current_ratio * Settings.energy_current_calibration) / Hlw.cf1_current_pulse_length; + Energy.current[0] = (float)hlw_i / 1000; + } else { + Energy.current[0] = 0; + } + + } + Hlw.cf1_summed_pulse_length = 0; + Hlw.cf1_pulse_counter = 0; + } + } +} + +void HlwEverySecond(void) +{ + if (Energy.data_valid[0] > ENERGY_WATCHDOG) { + Hlw.cf1_voltage_pulse_length = 0; + Hlw.cf1_current_pulse_length = 0; + Hlw.cf_power_pulse_length = 0; + } else { + unsigned long hlw_len; + + if (Hlw.energy_period_counter) { + hlw_len = 10000 / Hlw.energy_period_counter; + Hlw.energy_period_counter = 0; + if (hlw_len) { + Energy.kWhtoday_delta += ((Hlw.power_ratio * Settings.energy_power_calibration) / hlw_len) / 36; + EnergyUpdateToday(); + } + } + } +} + +void HlwSnsInit(void) +{ + if (!Settings.energy_power_calibration || (4975 == Settings.energy_power_calibration)) { + Settings.energy_power_calibration = HLW_PREF_PULSE; + Settings.energy_voltage_calibration = HLW_UREF_PULSE; + Settings.energy_current_calibration = HLW_IREF_PULSE; + } + + if (Hlw.model_type) { + Hlw.power_ratio = HJL_PREF; + Hlw.voltage_ratio = HJL_UREF; + Hlw.current_ratio = HJL_IREF; + } else { + Hlw.power_ratio = HLW_PREF; + Hlw.voltage_ratio = HLW_UREF; + Hlw.current_ratio = HLW_IREF; + } + + if (pin[GPIO_NRG_SEL] < 99) { + pinMode(pin[GPIO_NRG_SEL], OUTPUT); + digitalWrite(pin[GPIO_NRG_SEL], Hlw.select_ui_flag); + } + if (pin[GPIO_NRG_CF1] < 99) { + pinMode(pin[GPIO_NRG_CF1], INPUT_PULLUP); + attachInterrupt(pin[GPIO_NRG_CF1], HlwCf1Interrupt, FALLING); + } + pinMode(pin[GPIO_HLW_CF], INPUT_PULLUP); + attachInterrupt(pin[GPIO_HLW_CF], HlwCfInterrupt, FALLING); +} + +void HlwDrvInit(void) +{ + Hlw.model_type = 0; + if (pin[GPIO_HJL_CF] < 99) { + pin[GPIO_HLW_CF] = pin[GPIO_HJL_CF]; + pin[GPIO_HJL_CF] = 99; + Hlw.model_type = 1; + } + + if (pin[GPIO_HLW_CF] < 99) { + + Hlw.ui_flag = true; + if (pin[GPIO_NRG_SEL_INV] < 99) { + pin[GPIO_NRG_SEL] = pin[GPIO_NRG_SEL_INV]; + pin[GPIO_NRG_SEL_INV] = 99; + Hlw.ui_flag = false; + } + + if (pin[GPIO_NRG_CF1] < 99) { + if (99 == pin[GPIO_NRG_SEL]) { + Energy.current_available = false; + } + } else { + Energy.current_available = false; + Energy.voltage_available = false; + } + + energy_flg = XNRG_01; + } +} + +bool HlwCommand(void) +{ + bool serviced = true; + + if ((CMND_POWERCAL == Energy.command_code) || (CMND_VOLTAGECAL == Energy.command_code) || (CMND_CURRENTCAL == Energy.command_code)) { + + } + else if (CMND_POWERSET == Energy.command_code) { + if (XdrvMailbox.data_len && Hlw.cf_power_pulse_length ) { + Settings.energy_power_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data) * 10) * Hlw.cf_power_pulse_length ) / Hlw.power_ratio; + } + } + else if (CMND_VOLTAGESET == Energy.command_code) { + if (XdrvMailbox.data_len && Hlw.cf1_voltage_pulse_length ) { + Settings.energy_voltage_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data) * 10) * Hlw.cf1_voltage_pulse_length ) / Hlw.voltage_ratio; + } + } + else if (CMND_CURRENTSET == Energy.command_code) { + if (XdrvMailbox.data_len && Hlw.cf1_current_pulse_length) { + Settings.energy_current_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data)) * Hlw.cf1_current_pulse_length) / Hlw.current_ratio; + } + } + else serviced = false; + + return serviced; +} + + + + + +bool Xnrg01(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_200_MSECOND: + HlwEvery200ms(); + break; + case FUNC_ENERGY_EVERY_SECOND: + HlwEverySecond(); + break; + case FUNC_COMMAND: + result = HlwCommand(); + break; + case FUNC_INIT: + HlwSnsInit(); + break; + case FUNC_PRE_INIT: + HlwDrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_02_cse7766.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_02_cse7766.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_CSE7766 + + + + + + + +#define XNRG_02 2 + +#define CSE_MAX_INVALID_POWER 128 + +#define CSE_NOT_CALIBRATED 0xAA + +#define CSE_PULSES_NOT_INITIALIZED -1 + +#define CSE_PREF 1000 +#define CSE_UREF 100 + +#define CSE_BUFFER_SIZE 25 + +#include + +TasmotaSerial *CseSerial = nullptr; + +struct CSE { + long voltage_cycle = 0; + long current_cycle = 0; + long power_cycle = 0; + long power_cycle_first = 0; + long cf_pulses = 0; + long cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; + + int byte_counter = 0; + uint8_t *rx_buffer = nullptr; + uint8_t power_invalid = 0; + bool received = false; +} Cse; + +void CseReceived(void) +{ + + + + + + + uint8_t header = Cse.rx_buffer[0]; + if ((header & 0xFC) == 0xFC) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware")); + return; + } + + + if (HLW_UREF_PULSE == Settings.energy_voltage_calibration) { + long voltage_coefficient = 191200; + if (CSE_NOT_CALIBRATED != header) { + voltage_coefficient = Cse.rx_buffer[2] << 16 | Cse.rx_buffer[3] << 8 | Cse.rx_buffer[4]; + } + Settings.energy_voltage_calibration = voltage_coefficient / CSE_UREF; + } + if (HLW_IREF_PULSE == Settings.energy_current_calibration) { + long current_coefficient = 16140; + if (CSE_NOT_CALIBRATED != header) { + current_coefficient = Cse.rx_buffer[8] << 16 | Cse.rx_buffer[9] << 8 | Cse.rx_buffer[10]; + } + Settings.energy_current_calibration = current_coefficient; + } + if (HLW_PREF_PULSE == Settings.energy_power_calibration) { + long power_coefficient = 5364000; + if (CSE_NOT_CALIBRATED != header) { + power_coefficient = Cse.rx_buffer[14] << 16 | Cse.rx_buffer[15] << 8 | Cse.rx_buffer[16]; + } + Settings.energy_power_calibration = power_coefficient / CSE_PREF; + } + + uint8_t adjustement = Cse.rx_buffer[20]; + Cse.voltage_cycle = Cse.rx_buffer[5] << 16 | Cse.rx_buffer[6] << 8 | Cse.rx_buffer[7]; + Cse.current_cycle = Cse.rx_buffer[11] << 16 | Cse.rx_buffer[12] << 8 | Cse.rx_buffer[13]; + Cse.power_cycle = Cse.rx_buffer[17] << 16 | Cse.rx_buffer[18] << 8 | Cse.rx_buffer[19]; + Cse.cf_pulses = Cse.rx_buffer[21] << 8 | Cse.rx_buffer[22]; + + if (Energy.power_on) { + if (adjustement & 0x40) { + Energy.voltage[0] = (float)(Settings.energy_voltage_calibration * CSE_UREF) / (float)Cse.voltage_cycle; + } + if (adjustement & 0x10) { + Cse.power_invalid = 0; + if ((header & 0xF2) == 0xF2) { + Energy.active_power[0] = 0; + } else { + if (0 == Cse.power_cycle_first) { Cse.power_cycle_first = Cse.power_cycle; } + if (Cse.power_cycle_first != Cse.power_cycle) { + Cse.power_cycle_first = -1; + Energy.active_power[0] = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)Cse.power_cycle; + } else { + Energy.active_power[0] = 0; + } + } + } else { + if (Cse.power_invalid < Settings.param[P_CSE7766_INVALID_POWER]) { + Cse.power_invalid++; + } else { + Cse.power_cycle_first = 0; + Energy.active_power[0] = 0; + } + } + if (adjustement & 0x20) { + if (0 == Energy.active_power[0]) { + Energy.current[0] = 0; + } else { + Energy.current[0] = (float)Settings.energy_current_calibration / (float)Cse.current_cycle; + } + } + } else { + Cse.power_cycle_first = 0; + Energy.voltage[0] = 0; + Energy.active_power[0] = 0; + Energy.current[0] = 0; + } +} + +bool CseSerialInput(void) +{ + while (CseSerial->available()) { + yield(); + uint8_t serial_in_byte = CseSerial->read(); + + if (Cse.received) { + Cse.rx_buffer[Cse.byte_counter++] = serial_in_byte; + if (24 == Cse.byte_counter) { + + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, Cse.rx_buffer, 24); + + uint8_t checksum = 0; + for (uint32_t i = 2; i < 23; i++) { checksum += Cse.rx_buffer[i]; } + if (checksum == Cse.rx_buffer[23]) { + Energy.data_valid[0] = 0; + CseReceived(); + Cse.received = false; + return true; + } else { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE)); + do { + memmove(Cse.rx_buffer, Cse.rx_buffer +1, 24); + Cse.byte_counter--; + } while ((Cse.byte_counter > 2) && (0x5A != Cse.rx_buffer[1])); + if (0x5A != Cse.rx_buffer[1]) { + Cse.received = false; + Cse.byte_counter = 0; + } + } + } + } else { + if ((0x5A == serial_in_byte) && (1 == Cse.byte_counter)) { + Cse.received = true; + } else { + Cse.byte_counter = 0; + } + Cse.rx_buffer[Cse.byte_counter++] = serial_in_byte; + } + } +} + + + +void CseEverySecond(void) +{ + if (Energy.data_valid[0] > ENERGY_WATCHDOG) { + Cse.voltage_cycle = 0; + Cse.current_cycle = 0; + Cse.power_cycle = 0; + } else { + long cf_frequency = 0; + + if (CSE_PULSES_NOT_INITIALIZED == Cse.cf_pulses_last_time) { + Cse.cf_pulses_last_time = Cse.cf_pulses; + } else { + if (Cse.cf_pulses < Cse.cf_pulses_last_time) { + cf_frequency = (65536 - Cse.cf_pulses_last_time) + Cse.cf_pulses; + } else { + cf_frequency = Cse.cf_pulses - Cse.cf_pulses_last_time; + } + if (cf_frequency && Energy.active_power[0]) { + unsigned long delta = (cf_frequency * Settings.energy_power_calibration) / 36; + + + + if (delta <= (4000*100/36) * 10 ) { + Cse.cf_pulses_last_time = Cse.cf_pulses; + Energy.kWhtoday_delta += delta; + } + else { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Load overflow")); + Cse.cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; + } + EnergyUpdateToday(); + } + } + } +} + +void CseSnsInit(void) +{ + + + CseSerial = new TasmotaSerial(pin[GPIO_CSE7766_RX], -1, 1); + if (CseSerial->begin(4800, 2)) { + if (CseSerial->hardwareSerial()) { + SetSerial(4800, TS_SERIAL_8E1); + ClaimSerial(); + } + if (0 == Settings.param[P_CSE7766_INVALID_POWER]) { + Settings.param[P_CSE7766_INVALID_POWER] = CSE_MAX_INVALID_POWER; + } + Cse.power_invalid = Settings.param[P_CSE7766_INVALID_POWER]; + } else { + energy_flg = ENERGY_NONE; + } +} + +void CseDrvInit(void) +{ + Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE)); + if (Cse.rx_buffer != nullptr) { + + if (pin[GPIO_CSE7766_RX] < 99) { + energy_flg = XNRG_02; + } + } +} + +bool CseCommand(void) +{ + bool serviced = true; + + if (CMND_POWERSET == Energy.command_code) { + if (XdrvMailbox.data_len && Cse.power_cycle) { + Settings.energy_power_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.power_cycle) / CSE_PREF; + } + } + else if (CMND_VOLTAGESET == Energy.command_code) { + if (XdrvMailbox.data_len && Cse.voltage_cycle) { + Settings.energy_voltage_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.voltage_cycle) / CSE_UREF; + } + } + else if (CMND_CURRENTSET == Energy.command_code) { + if (XdrvMailbox.data_len && Cse.current_cycle) { + Settings.energy_current_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.current_cycle) / 1000; + } + } + else serviced = false; + + return serviced; +} + + + + + +bool Xnrg02(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_LOOP: + if (CseSerial) { CseSerialInput(); } + break; + case FUNC_ENERGY_EVERY_SECOND: + CseEverySecond(); + break; + case FUNC_COMMAND: + result = CseCommand(); + break; + case FUNC_INIT: + CseSnsInit(); + break; + case FUNC_PRE_INIT: + CseDrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_03_pzem004t.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_03_pzem004t.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_PZEM004T +# 31 "S:/Development/Tasmota/tasmota/xnrg_03_pzem004t.ino" +#define XNRG_03 3 + +const uint32_t PZEM_STABILIZE = 30; + +#include + +TasmotaSerial *PzemSerial = nullptr; + +#define PZEM_VOLTAGE (uint8_t)0xB0 +#define RESP_VOLTAGE (uint8_t)0xA0 + +#define PZEM_CURRENT (uint8_t)0xB1 +#define RESP_CURRENT (uint8_t)0xA1 + +#define PZEM_POWER (uint8_t)0xB2 +#define RESP_POWER (uint8_t)0xA2 + +#define PZEM_ENERGY (uint8_t)0xB3 +#define RESP_ENERGY (uint8_t)0xA3 + +#define PZEM_SET_ADDRESS (uint8_t)0xB4 +#define RESP_SET_ADDRESS (uint8_t)0xA4 + +#define PZEM_POWER_ALARM (uint8_t)0xB5 +#define RESP_POWER_ALARM (uint8_t)0xA5 + +#define PZEM_DEFAULT_READ_TIMEOUT 500 + + + +struct PZEM { + float energy = 0; + float last_energy = 0; + uint8_t send_retry = 0; + uint8_t read_state = 0; + uint8_t phase = 0; + uint8_t address = 0; +} Pzem; + +struct PZEMCommand { + uint8_t command; + uint8_t addr[4]; + uint8_t data; + uint8_t crc; +}; + +uint8_t PzemCrc(uint8_t *data) +{ + uint16_t crc = 0; + for (uint32_t i = 0; i < sizeof(PZEMCommand) -1; i++) { + crc += *data++; + } + return (uint8_t)(crc & 0xFF); +} + +void PzemSend(uint8_t cmd) +{ + PZEMCommand pzem; + + pzem.command = cmd; + pzem.addr[0] = 192; + pzem.addr[1] = 168; + pzem.addr[2] = 1; + pzem.addr[3] = ((PZEM_SET_ADDRESS == cmd) && Pzem.address) ? Pzem.address : 1 + Pzem.phase; + pzem.data = 0; + + uint8_t *bytes = (uint8_t*)&pzem; + pzem.crc = PzemCrc(bytes); + + PzemSerial->flush(); + PzemSerial->write(bytes, sizeof(pzem)); + + Pzem.address = 0; +} + +bool PzemReceiveReady(void) +{ + return PzemSerial->available() >= (int)sizeof(PZEMCommand); +} + +bool PzemRecieve(uint8_t resp, float *data) +{ +# 124 "S:/Development/Tasmota/tasmota/xnrg_03_pzem004t.ino" + uint8_t buffer[sizeof(PZEMCommand)] = { 0 }; + + unsigned long start = millis(); + uint8_t len = 0; + while ((len < sizeof(PZEMCommand)) && (millis() - start < PZEM_DEFAULT_READ_TIMEOUT)) { + if (PzemSerial->available() > 0) { + uint8_t c = (uint8_t)PzemSerial->read(); + if (!len && ((c & 0xF8) != 0xA0)) { + continue; + } + buffer[len++] = c; + } + } + + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, len); + + if (len != sizeof(PZEMCommand)) { + + return false; + } + if (buffer[6] != PzemCrc(buffer)) { + + return false; + } + if (buffer[0] != resp) { + + return false; + } + + switch (resp) { + case RESP_VOLTAGE: + *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 10.0); + break; + case RESP_CURRENT: + *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 100.0); + break; + case RESP_POWER: + *data = (float)(buffer[1] << 8) + buffer[2]; + break; + case RESP_ENERGY: + *data = (float)((uint32_t)buffer[1] << 16) + ((uint16_t)buffer[2] << 8) + buffer[3]; + break; + } + return true; +} + + + +const uint8_t pzem_commands[] { PZEM_SET_ADDRESS, PZEM_VOLTAGE, PZEM_CURRENT, PZEM_POWER, PZEM_ENERGY }; +const uint8_t pzem_responses[] { RESP_SET_ADDRESS, RESP_VOLTAGE, RESP_CURRENT, RESP_POWER, RESP_ENERGY }; + +void PzemEvery250ms(void) +{ + bool data_ready = PzemReceiveReady(); + + if (data_ready) { + float value = 0; + if (PzemRecieve(pzem_responses[Pzem.read_state], &value)) { + Energy.data_valid[Pzem.phase] = 0; + switch (Pzem.read_state) { + case 1: + Energy.voltage[Pzem.phase] = value; + break; + case 2: + Energy.current[Pzem.phase] = value; + break; + case 3: + Energy.active_power[Pzem.phase] = value; + break; + case 4: + Pzem.energy += value; + if (Pzem.phase == Energy.phase_count -1) { + if (Pzem.energy > Pzem.last_energy) { + if (uptime > PZEM_STABILIZE) { + EnergyUpdateTotal(Pzem.energy, false); + } + Pzem.last_energy = Pzem.energy; + } + Pzem.energy = 0; + } + break; + } + Pzem.read_state++; + if (5 == Pzem.read_state) { + Pzem.read_state = 1; + } + + + } + } + + if (0 == Pzem.send_retry || data_ready) { + if (1 == Pzem.read_state) { + if (0 == Pzem.phase) { + Pzem.phase = Energy.phase_count -1; + } else { + Pzem.phase--; + } + + + } + + if (Pzem.address) { + Pzem.read_state = 0; + } + + Pzem.send_retry = 5; + PzemSend(pzem_commands[Pzem.read_state]); + } + else { + Pzem.send_retry--; + if ((Energy.phase_count > 1) && (0 == Pzem.send_retry) && (uptime < PZEM_STABILIZE)) { + Energy.phase_count--; + } + } +} + +void PzemSnsInit(void) +{ + + PzemSerial = new TasmotaSerial(pin[GPIO_PZEM004_RX], pin[GPIO_PZEM0XX_TX], 1); + if (PzemSerial->begin(9600)) { + if (PzemSerial->hardwareSerial()) { + ClaimSerial(); + } + Energy.phase_count = 3; + Pzem.phase = 0; + Pzem.read_state = 1; + } else { + energy_flg = ENERGY_NONE; + } +} + +void PzemDrvInit(void) +{ + if ((pin[GPIO_PZEM004_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) { + energy_flg = XNRG_03; + } +} + +bool PzemCommand(void) +{ + bool serviced = true; + + if (CMND_MODULEADDRESS == Energy.command_code) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4)) { + Pzem.address = XdrvMailbox.payload; + } + } + else serviced = false; + + return serviced; +} + + + + + +bool Xnrg03(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_250_MSECOND: + if (PzemSerial && (uptime > 4)) { PzemEvery250ms(); } + break; + case FUNC_COMMAND: + result = PzemCommand(); + break; + case FUNC_INIT: + PzemSnsInit(); + break; + case FUNC_PRE_INIT: + PzemDrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_04_mcp39f501.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_04_mcp39f501.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_MCP39F501 + + + + + + + +#define XNRG_04 4 + +#define MCP_BAUDRATE 4800 +#define MCP_TIMEOUT 4 +#define MCP_CALIBRATION_TIMEOUT 2 + +#define MCP_CALIBRATE_POWER 0x001 +#define MCP_CALIBRATE_VOLTAGE 0x002 +#define MCP_CALIBRATE_CURRENT 0x004 +#define MCP_CALIBRATE_FREQUENCY 0x008 +#define MCP_SINGLE_WIRE_FLAG 0x100 + +#define MCP_START_FRAME 0xA5 +#define MCP_ACK_FRAME 0x06 +#define MCP_ERROR_NAK 0x15 +#define MCP_ERROR_CRC 0x51 + +#define MCP_SINGLE_WIRE 0xAB + +#define MCP_SET_ADDRESS 0x41 + +#define MCP_READ 0x4E +#define MCP_READ_16 0x52 +#define MCP_READ_32 0x44 + +#define MCP_WRITE 0x4D +#define MCP_WRITE_16 0x57 +#define MCP_WRITE_32 0x45 + +#define MCP_SAVE_REGISTERS 0x53 + +#define MCP_CALIBRATION_BASE 0x0028 +#define MCP_CALIBRATION_LEN 52 + +#define MCP_FREQUENCY_REF_BASE 0x0094 +#define MCP_FREQUENCY_GAIN_BASE 0x00AE +#define MCP_FREQUENCY_LEN 4 + +#define MCP_BUFFER_SIZE 60 + +#include +TasmotaSerial *McpSerial = nullptr; + +typedef struct mcp_cal_registers_type { + uint16_t gain_current_rms; + uint16_t gain_voltage_rms; + uint16_t gain_active_power; + uint16_t gain_reactive_power; + sint32_t offset_current_rms; + sint32_t offset_active_power; + sint32_t offset_reactive_power; + sint16_t dc_offset_current; + sint16_t phase_compensation; + uint16_t apparent_power_divisor; + + uint32_t system_configuration; + uint16_t dio_configuration; + uint32_t range; + + uint32_t calibration_current; + uint16_t calibration_voltage; + uint32_t calibration_active_power; + uint32_t calibration_reactive_power; + uint16_t accumulation_interval; +} mcp_cal_registers_type; + +char *mcp_buffer = nullptr; +unsigned long mcp_window = 0; +unsigned long mcp_kWhcounter = 0; +uint32_t mcp_system_configuration = 0x03000000; +uint32_t mcp_active_power; + + +uint32_t mcp_current_rms; +uint16_t mcp_voltage_rms; +uint16_t mcp_line_frequency; + +uint8_t mcp_address = 0; +uint8_t mcp_calibration_active = 0; +uint8_t mcp_init = 0; +uint8_t mcp_timeout = 0; +uint8_t mcp_calibrate = 0; +uint8_t mcp_byte_counter = 0; + + + + + + +uint8_t McpChecksum(uint8_t *data) +{ + uint8_t checksum = 0; + uint8_t offset = 0; + uint8_t len = data[1] -1; + + for (uint32_t i = offset; i < len; i++) { checksum += data[i]; } + return checksum; +} + +unsigned long McpExtractInt(char *data, uint8_t offset, uint8_t size) +{ + unsigned long result = 0; + unsigned long pow = 1; + + for (uint32_t i = 0; i < size; i++) { + result = result + (uint8_t)data[offset + i] * pow; + pow = pow * 256; + } + return result; +} + +void McpSetInt(unsigned long value, uint8_t *data, uint8_t offset, size_t size) +{ + for (uint32_t i = 0; i < size; i++) { + data[offset + i] = ((value >> (i * 8)) & 0xFF); + } +} + +void McpSend(uint8_t *data) +{ + if (mcp_timeout) { return; } + mcp_timeout = MCP_TIMEOUT; + + data[0] = MCP_START_FRAME; + data[data[1] -1] = McpChecksum(data); + + + + for (uint32_t i = 0; i < data[1]; i++) { + McpSerial->write(data[i]); + } +} + + + +void McpGetAddress(void) +{ + uint8_t data[] = { MCP_START_FRAME, 7, MCP_SET_ADDRESS, 0x00, 0x26, MCP_READ_16, 0x00 }; + + McpSend(data); +} + +void McpAddressReceive(void) +{ + + mcp_address = mcp_buffer[3]; +} + + + +void McpGetCalibration(void) +{ + if (mcp_calibration_active) { return; } + mcp_calibration_active = MCP_CALIBRATION_TIMEOUT; + + uint8_t data[] = { MCP_START_FRAME, 8, MCP_SET_ADDRESS, (MCP_CALIBRATION_BASE >> 8) & 0xFF, MCP_CALIBRATION_BASE & 0xFF, MCP_READ, MCP_CALIBRATION_LEN, 0x00 }; + + McpSend(data); +} + +void McpParseCalibration(void) +{ + bool action = false; + mcp_cal_registers_type cal_registers; + + + cal_registers.gain_current_rms = McpExtractInt(mcp_buffer, 2, 2); + cal_registers.gain_voltage_rms = McpExtractInt(mcp_buffer, 4, 2); + cal_registers.gain_active_power = McpExtractInt(mcp_buffer, 6, 2); + cal_registers.gain_reactive_power = McpExtractInt(mcp_buffer, 8, 2); + cal_registers.offset_current_rms = McpExtractInt(mcp_buffer, 10, 4); + cal_registers.offset_active_power = McpExtractInt(mcp_buffer, 14, 4); + cal_registers.offset_reactive_power = McpExtractInt(mcp_buffer, 18, 4); + cal_registers.dc_offset_current = McpExtractInt(mcp_buffer, 22, 2); + cal_registers.phase_compensation = McpExtractInt(mcp_buffer, 24, 2); + cal_registers.apparent_power_divisor = McpExtractInt(mcp_buffer, 26, 2); + + cal_registers.system_configuration = McpExtractInt(mcp_buffer, 28, 4); + cal_registers.dio_configuration = McpExtractInt(mcp_buffer, 32, 2); + cal_registers.range = McpExtractInt(mcp_buffer, 34, 4); + + cal_registers.calibration_current = McpExtractInt(mcp_buffer, 38, 4); + cal_registers.calibration_voltage = McpExtractInt(mcp_buffer, 42, 2); + cal_registers.calibration_active_power = McpExtractInt(mcp_buffer, 44, 4); + cal_registers.calibration_reactive_power = McpExtractInt(mcp_buffer, 48, 4); + cal_registers.accumulation_interval = McpExtractInt(mcp_buffer, 52, 2); + + if (mcp_calibrate & MCP_CALIBRATE_POWER) { + cal_registers.calibration_active_power = Settings.energy_power_calibration; + if (McpCalibrationCalc(&cal_registers, 16)) { action = true; } + } + if (mcp_calibrate & MCP_CALIBRATE_VOLTAGE) { + cal_registers.calibration_voltage = Settings.energy_voltage_calibration; + if (McpCalibrationCalc(&cal_registers, 0)) { action = true; } + } + if (mcp_calibrate & MCP_CALIBRATE_CURRENT) { + cal_registers.calibration_current = Settings.energy_current_calibration; + if (McpCalibrationCalc(&cal_registers, 8)) { action = true; } + } + mcp_timeout = 0; + if (action) { McpSetCalibration(&cal_registers); } + + mcp_calibrate = 0; + + Settings.energy_power_calibration = cal_registers.calibration_active_power; + Settings.energy_voltage_calibration = cal_registers.calibration_voltage; + Settings.energy_current_calibration = cal_registers.calibration_current; + + mcp_system_configuration = cal_registers.system_configuration; + + if (mcp_system_configuration & MCP_SINGLE_WIRE_FLAG) { + mcp_system_configuration &= ~MCP_SINGLE_WIRE_FLAG; + McpSetSystemConfiguration(2); + } +} + +bool McpCalibrationCalc(struct mcp_cal_registers_type *cal_registers, uint8_t range_shift) +{ + uint32_t measured; + uint32_t expected; + uint16_t *gain; + uint32_t new_gain; + + if (range_shift == 0) { + measured = mcp_voltage_rms; + expected = cal_registers->calibration_voltage; + gain = &(cal_registers->gain_voltage_rms); + } else if (range_shift == 8) { + measured = mcp_current_rms; + expected = cal_registers->calibration_current; + gain = &(cal_registers->gain_current_rms); + } else if (range_shift == 16) { + measured = mcp_active_power; + expected = cal_registers->calibration_active_power; + gain = &(cal_registers->gain_active_power); + } else { + return false; + } + + if (measured == 0) { + return false; + } + + uint32_t range = (cal_registers->range >> range_shift) & 0xFF; + +calc: + new_gain = (*gain) * expected / measured; + + if (new_gain < 25000) { + range++; + if (measured > 6) { + measured = measured / 2; + goto calc; + } + } + + if (new_gain > 55000) { + range--; + measured = measured * 2; + goto calc; + } + + *gain = new_gain; + uint32_t old_range = (cal_registers->range >> range_shift) & 0xFF; + cal_registers->range = cal_registers->range ^ (old_range << range_shift); + cal_registers->range = cal_registers->range | (range << range_shift); + + return true; +} + + + + + + +void McpSetCalibration(struct mcp_cal_registers_type *cal_registers) +{ + uint8_t data[7 + MCP_CALIBRATION_LEN + 2 + 1]; + + data[1] = sizeof(data); + data[2] = MCP_SET_ADDRESS; + data[3] = (MCP_CALIBRATION_BASE >> 8) & 0xFF; + data[4] = (MCP_CALIBRATION_BASE >> 0) & 0xFF; + + data[5] = MCP_WRITE; + data[6] = MCP_CALIBRATION_LEN; + + McpSetInt(cal_registers->gain_current_rms, data, 0+7, 2); + McpSetInt(cal_registers->gain_voltage_rms, data, 2+7, 2); + McpSetInt(cal_registers->gain_active_power, data, 4+7, 2); + McpSetInt(cal_registers->gain_reactive_power, data, 6+7, 2); + McpSetInt(cal_registers->offset_current_rms, data, 8+7, 4); + McpSetInt(cal_registers->offset_active_power, data, 12+7, 4); + McpSetInt(cal_registers->offset_reactive_power, data, 16+7, 4); + McpSetInt(cal_registers->dc_offset_current, data, 20+7, 2); + McpSetInt(cal_registers->phase_compensation, data, 22+7, 2); + McpSetInt(cal_registers->apparent_power_divisor, data, 24+7, 2); + + McpSetInt(cal_registers->system_configuration, data, 26+7, 4); + McpSetInt(cal_registers->dio_configuration, data, 30+7, 2); + McpSetInt(cal_registers->range, data, 32+7, 4); + + McpSetInt(cal_registers->calibration_current, data, 36+7, 4); + McpSetInt(cal_registers->calibration_voltage, data, 40+7, 2); + McpSetInt(cal_registers->calibration_active_power, data, 42+7, 4); + McpSetInt(cal_registers->calibration_reactive_power, data, 46+7, 4); + McpSetInt(cal_registers->accumulation_interval, data, 50+7, 2); + + data[MCP_CALIBRATION_LEN+7] = MCP_SAVE_REGISTERS; + data[MCP_CALIBRATION_LEN+8] = mcp_address; + + McpSend(data); +} + + + +void McpSetSystemConfiguration(uint16 interval) +{ + + uint8_t data[17]; + + data[ 1] = sizeof(data); + data[ 2] = MCP_SET_ADDRESS; + data[ 3] = 0x00; + data[ 4] = 0x42; + data[ 5] = MCP_WRITE_32; + data[ 6] = (mcp_system_configuration >> 24) & 0xFF; + data[ 7] = (mcp_system_configuration >> 16) & 0xFF; + data[ 8] = (mcp_system_configuration >> 8) & 0xFF; + data[ 9] = (mcp_system_configuration >> 0) & 0xFF; + data[10] = MCP_SET_ADDRESS; + data[11] = 0x00; + data[12] = 0x5A; + data[13] = MCP_WRITE_16; + data[14] = (interval >> 8) & 0xFF; + data[15] = (interval >> 0) & 0xFF; + + McpSend(data); +} + + + +void McpGetFrequency(void) +{ + if (mcp_calibration_active) { return; } + mcp_calibration_active = MCP_CALIBRATION_TIMEOUT; + + uint8_t data[] = { MCP_START_FRAME, 11, MCP_SET_ADDRESS, (MCP_FREQUENCY_REF_BASE >> 8) & 0xFF, MCP_FREQUENCY_REF_BASE & 0xFF, MCP_READ_16, + MCP_SET_ADDRESS, (MCP_FREQUENCY_GAIN_BASE >> 8) & 0xFF, MCP_FREQUENCY_GAIN_BASE & 0xFF, MCP_READ_16, 0x00 }; + + McpSend(data); +} + +void McpParseFrequency(void) +{ + + uint16_t line_frequency_ref = mcp_buffer[2] * 256 + mcp_buffer[3]; + uint16_t gain_line_frequency = mcp_buffer[4] * 256 + mcp_buffer[5]; + + if (mcp_calibrate & MCP_CALIBRATE_FREQUENCY) { + line_frequency_ref = Settings.energy_frequency_calibration; + + if ((0xFFFF == mcp_line_frequency) || (0 == gain_line_frequency)) { + mcp_line_frequency = 50000; + gain_line_frequency = 0x8000; + } + gain_line_frequency = gain_line_frequency * line_frequency_ref / mcp_line_frequency; + + mcp_timeout = 0; + McpSetFrequency(line_frequency_ref, gain_line_frequency); + } + + Settings.energy_frequency_calibration = line_frequency_ref; + + mcp_calibrate = 0; +} + +void McpSetFrequency(uint16_t line_frequency_ref, uint16_t gain_line_frequency) +{ + + uint8_t data[17]; + + data[ 1] = sizeof(data); + data[ 2] = MCP_SET_ADDRESS; + data[ 3] = (MCP_FREQUENCY_REF_BASE >> 8) & 0xFF; + data[ 4] = (MCP_FREQUENCY_REF_BASE >> 0) & 0xFF; + + data[ 5] = MCP_WRITE_16; + data[ 6] = (line_frequency_ref >> 8) & 0xFF; + data[ 7] = (line_frequency_ref >> 0) & 0xFF; + + data[ 8] = MCP_SET_ADDRESS; + data[ 9] = (MCP_FREQUENCY_GAIN_BASE >> 8) & 0xFF; + data[10] = (MCP_FREQUENCY_GAIN_BASE >> 0) & 0xFF; + + data[11] = MCP_WRITE_16; + data[12] = (gain_line_frequency >> 8) & 0xFF; + data[13] = (gain_line_frequency >> 0) & 0xFF; + + data[14] = MCP_SAVE_REGISTERS; + data[15] = mcp_address; + + McpSend(data); +} + + + +void McpGetData(void) +{ + uint8_t data[] = { MCP_START_FRAME, 8, MCP_SET_ADDRESS, 0x00, 0x04, MCP_READ, 22, 0x00 }; + + McpSend(data); +} + +void McpParseData(void) +{ + + + + + + mcp_current_rms = McpExtractInt(mcp_buffer, 2, 4); + mcp_voltage_rms = McpExtractInt(mcp_buffer, 6, 2); + mcp_active_power = McpExtractInt(mcp_buffer, 8, 4); + + + mcp_line_frequency = McpExtractInt(mcp_buffer, 22, 2); + + if (Energy.power_on) { + Energy.data_valid[0] = 0; + Energy.frequency[0] = (float)mcp_line_frequency / 1000; + Energy.voltage[0] = (float)mcp_voltage_rms / 10; + Energy.active_power[0] = (float)mcp_active_power / 100; + if (0 == Energy.active_power[0]) { + Energy.current[0] = 0; + } else { + Energy.current[0] = (float)mcp_current_rms / 10000; + } + } else { + Energy.data_valid[0] = ENERGY_WATCHDOG; + } +} + + + +void McpSerialInput(void) +{ + while ((McpSerial->available()) && (mcp_byte_counter < MCP_BUFFER_SIZE)) { + yield(); + mcp_buffer[mcp_byte_counter++] = McpSerial->read(); + mcp_window = millis(); + } + + + if ((mcp_byte_counter) && (millis() - mcp_window > (24000 / MCP_BAUDRATE) +1)) { + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, (uint8_t*)mcp_buffer, mcp_byte_counter); + + if (MCP_BUFFER_SIZE == mcp_byte_counter) { + + } + else if (1 == mcp_byte_counter) { + if (MCP_ERROR_CRC == mcp_buffer[0]) { + + mcp_timeout = 0; + } + else if (MCP_ERROR_NAK == mcp_buffer[0]) { + + mcp_timeout = 0; + } + } + else if (MCP_ACK_FRAME == mcp_buffer[0]) { + if (mcp_byte_counter == mcp_buffer[1]) { + + if (McpChecksum((uint8_t *)mcp_buffer) != mcp_buffer[mcp_byte_counter -1]) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("MCP: " D_CHECKSUM_FAILURE)); + } else { + if (5 == mcp_buffer[1]) { McpAddressReceive(); } + if (25 == mcp_buffer[1]) { McpParseData(); } + if (MCP_CALIBRATION_LEN + 3 == mcp_buffer[1]) { McpParseCalibration(); } + if (MCP_FREQUENCY_LEN + 3 == mcp_buffer[1]) { McpParseFrequency(); } + } + + } + mcp_timeout = 0; + } + else if (MCP_SINGLE_WIRE == mcp_buffer[0]) { + mcp_timeout = 0; + } + + mcp_byte_counter = 0; + McpSerial->flush(); + } +} + + + +void McpEverySecond(void) +{ + if (Energy.data_valid[0] > ENERGY_WATCHDOG) { + mcp_voltage_rms = 0; + mcp_current_rms = 0; + mcp_active_power = 0; + mcp_line_frequency = 0; + } + + if (mcp_active_power) { + Energy.kWhtoday_delta += ((mcp_active_power * 10) / 36); + EnergyUpdateToday(); + } + + if (mcp_timeout) { + mcp_timeout--; + } + else if (mcp_calibration_active) { + mcp_calibration_active--; + } + else if (mcp_init) { + if (2 == mcp_init) { + McpGetCalibration(); + } + else if (1 == mcp_init) { + McpGetFrequency(); + } + mcp_init--; + } + else if (!mcp_address) { + McpGetAddress(); + } + else { + McpGetData(); + } +} + +void McpSnsInit(void) +{ + + McpSerial = new TasmotaSerial(pin[GPIO_MCP39F5_RX], pin[GPIO_MCP39F5_TX], 1); + if (McpSerial->begin(MCP_BAUDRATE)) { + if (McpSerial->hardwareSerial()) { + ClaimSerial(); + mcp_buffer = serial_in_buffer; + } else { + mcp_buffer = (char*)(malloc(MCP_BUFFER_SIZE)); + } + DigitalWrite(GPIO_MCP39F5_RST, 1); + } else { + energy_flg = ENERGY_NONE; + } +} + +void McpDrvInit(void) +{ + if ((pin[GPIO_MCP39F5_RX] < 99) && (pin[GPIO_MCP39F5_TX] < 99)) { + if (pin[GPIO_MCP39F5_RST] < 99) { + pinMode(pin[GPIO_MCP39F5_RST], OUTPUT); + digitalWrite(pin[GPIO_MCP39F5_RST], 0); + } + mcp_calibrate = 0; + mcp_timeout = 2; + mcp_init = 2; + energy_flg = XNRG_04; + } +} + +bool McpCommand(void) +{ + bool serviced = true; + unsigned long value = 0; + + if (CMND_POWERSET == Energy.command_code) { + if (XdrvMailbox.data_len && mcp_active_power) { + value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 100); + if ((value > 100) && (value < 200000)) { + Settings.energy_power_calibration = value; + mcp_calibrate |= MCP_CALIBRATE_POWER; + McpGetCalibration(); + } + } + } + else if (CMND_VOLTAGESET == Energy.command_code) { + if (XdrvMailbox.data_len && mcp_voltage_rms) { + value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 10); + if ((value > 1000) && (value < 2600)) { + Settings.energy_voltage_calibration = value; + mcp_calibrate |= MCP_CALIBRATE_VOLTAGE; + McpGetCalibration(); + } + } + } + else if (CMND_CURRENTSET == Energy.command_code) { + if (XdrvMailbox.data_len && mcp_current_rms) { + value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 10); + if ((value > 100) && (value < 80000)) { + Settings.energy_current_calibration = value; + mcp_calibrate |= MCP_CALIBRATE_CURRENT; + McpGetCalibration(); + } + } + } + else if (CMND_FREQUENCYSET == Energy.command_code) { + if (XdrvMailbox.data_len && mcp_line_frequency) { + value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 1000); + if ((value > 45000) && (value < 65000)) { + Settings.energy_frequency_calibration = value; + mcp_calibrate |= MCP_CALIBRATE_FREQUENCY; + McpGetFrequency(); + } + } + } + else serviced = false; + + return serviced; +} + + + + + +bool Xnrg04(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_LOOP: + if (McpSerial) { McpSerialInput(); } + break; + case FUNC_ENERGY_EVERY_SECOND: + if (McpSerial) { McpEverySecond(); } + break; + case FUNC_COMMAND: + result = McpCommand(); + break; + case FUNC_INIT: + McpSnsInit(); + break; + case FUNC_PRE_INIT: + McpDrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_05_pzem_ac.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_05_pzem_ac.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_PZEM_AC +# 33 "S:/Development/Tasmota/tasmota/xnrg_05_pzem_ac.ino" +#define XNRG_05 5 + +const uint8_t PZEM_AC_DEVICE_ADDRESS = 0x01; +const uint32_t PZEM_AC_STABILIZE = 30; + +#include +TasmotaModbus *PzemAcModbus; + +struct PZEMAC { + float energy = 0; + float last_energy = 0; + uint8_t send_retry = 0; + uint8_t phase = 0; + uint8_t address = 0; + uint8_t address_step = ADDR_IDLE; +} PzemAc; + +void PzemAcEverySecond(void) +{ + bool data_ready = PzemAcModbus->ReceiveReady(); + + if (data_ready) { + uint8_t buffer[30]; + + uint8_t registers = 10; + if (ADDR_RECEIVE == PzemAc.address_step) { + registers = 2; + PzemAc.address_step--; + } + uint8_t error = PzemAcModbus->ReceiveBuffer(buffer, registers); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, PzemAcModbus->ReceiveCount()); + + if (error) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PAC: PzemAc %d error %d"), PZEM_AC_DEVICE_ADDRESS + PzemAc.phase, error); + } else { + Energy.data_valid[PzemAc.phase] = 0; + if (10 == registers) { + + + + + + Energy.voltage[PzemAc.phase] = (float)((buffer[3] << 8) + buffer[4]) / 10.0; + Energy.current[PzemAc.phase] = (float)((buffer[7] << 24) + (buffer[8] << 16) + (buffer[5] << 8) + buffer[6]) / 1000.0; + Energy.active_power[PzemAc.phase] = (float)((buffer[11] << 24) + (buffer[12] << 16) + (buffer[9] << 8) + buffer[10]) / 10.0; + Energy.frequency[PzemAc.phase] = (float)((buffer[17] << 8) + buffer[18]) / 10.0; + Energy.power_factor[PzemAc.phase] = (float)((buffer[19] << 8) + buffer[20]) / 100.0; + + PzemAc.energy += (float)((buffer[15] << 24) + (buffer[16] << 16) + (buffer[13] << 8) + buffer[14]); + if (PzemAc.phase == Energy.phase_count -1) { + if (PzemAc.energy > PzemAc.last_energy) { + if (uptime > PZEM_AC_STABILIZE) { + EnergyUpdateTotal(PzemAc.energy, false); + } + PzemAc.last_energy = PzemAc.energy; + } + PzemAc.energy = 0; + } + + } + } + } + + if (0 == PzemAc.send_retry || data_ready) { + if (0 == PzemAc.phase) { + PzemAc.phase = Energy.phase_count -1; + } else { + PzemAc.phase--; + } + PzemAc.send_retry = ENERGY_WATCHDOG; + if (ADDR_SEND == PzemAc.address_step) { + PzemAcModbus->Send(0xF8, 0x06, 0x0002, (uint16_t)PzemAc.address); + PzemAc.address_step--; + } else { + PzemAcModbus->Send(PZEM_AC_DEVICE_ADDRESS + PzemAc.phase, 0x04, 0, 10); + } + } + else { + PzemAc.send_retry--; + if ((Energy.phase_count > 1) && (0 == PzemAc.send_retry) && (uptime < PZEM_AC_STABILIZE)) { + Energy.phase_count--; + } + } +} + +void PzemAcSnsInit(void) +{ + PzemAcModbus = new TasmotaModbus(pin[GPIO_PZEM016_RX], pin[GPIO_PZEM0XX_TX]); + uint8_t result = PzemAcModbus->Begin(9600); + if (result) { + if (2 == result) { ClaimSerial(); } + Energy.phase_count = 3; + PzemAc.phase = 0; + } else { + energy_flg = ENERGY_NONE; + } +} + +void PzemAcDrvInit(void) +{ + if ((pin[GPIO_PZEM016_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) { + energy_flg = XNRG_05; + } +} + +bool PzemAcCommand(void) +{ + bool serviced = true; + + if (CMND_MODULEADDRESS == Energy.command_code) { + PzemAc.address = XdrvMailbox.payload; + PzemAc.address_step = ADDR_SEND; + } + else serviced = false; + + return serviced; +} + + + + + +bool Xnrg05(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_ENERGY_EVERY_SECOND: + if (uptime > 4) { PzemAcEverySecond(); } + break; + case FUNC_COMMAND: + result = PzemAcCommand(); + break; + case FUNC_INIT: + PzemAcSnsInit(); + break; + case FUNC_PRE_INIT: + PzemAcDrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_06_pzem_dc.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_06_pzem_dc.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_PZEM_DC +# 32 "S:/Development/Tasmota/tasmota/xnrg_06_pzem_dc.ino" +#define XNRG_06 6 + +const uint8_t PZEM_DC_DEVICE_ADDRESS = 0x01; +const uint32_t PZEM_DC_STABILIZE = 30; + +#include +TasmotaModbus *PzemDcModbus; + +struct PZEMDC { + float energy = 0; + float last_energy = 0; + uint8_t send_retry = 0; + uint8_t channel = 0; + uint8_t address = 0; + uint8_t address_step = ADDR_IDLE; +} PzemDc; + +void PzemDcEverySecond(void) +{ + bool data_ready = PzemDcModbus->ReceiveReady(); + + if (data_ready) { + uint8_t buffer[26]; + + uint8_t registers = 8; + if (ADDR_RECEIVE == PzemDc.address_step) { + registers = 2; + PzemDc.address_step--; + } + uint8_t error = PzemDcModbus->ReceiveBuffer(buffer, registers); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, PzemDcModbus->ReceiveCount()); + + if (error) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PDC: PzemDc %d error %d"), PZEM_DC_DEVICE_ADDRESS + PzemDc.channel, error); + } else { + Energy.data_valid[PzemDc.channel] = 0; + if (8 == registers) { + + + + + + Energy.voltage[PzemDc.channel] = (float)((buffer[3] << 8) + buffer[4]) / 100.0; + Energy.current[PzemDc.channel] = (float)((buffer[5] << 8) + buffer[6]) / 100.0; + Energy.active_power[PzemDc.channel] = (float)((buffer[9] << 24) + (buffer[10] << 16) + (buffer[7] << 8) + buffer[8]) / 10.0; + + PzemDc.energy += (float)((buffer[13] << 24) + (buffer[14] << 16) + (buffer[11] << 8) + buffer[12]); + if (PzemDc.channel == Energy.phase_count -1) { + if (PzemDc.energy > PzemDc.last_energy) { + if (uptime > PZEM_DC_STABILIZE) { + EnergyUpdateTotal(PzemDc.energy, false); + } + PzemDc.last_energy = PzemDc.energy; + } + PzemDc.energy = 0; + } + } + } + } + + if (0 == PzemDc.send_retry || data_ready) { + if (0 == PzemDc.channel) { + PzemDc.channel = Energy.phase_count -1; + } else { + PzemDc.channel--; + } + PzemDc.send_retry = ENERGY_WATCHDOG; + if (ADDR_SEND == PzemDc.address_step) { + PzemDcModbus->Send(0xF8, 0x06, 0x0002, (uint16_t)PzemDc.address); + PzemDc.address_step--; + } else { + PzemDcModbus->Send(PZEM_DC_DEVICE_ADDRESS + PzemDc.channel, 0x04, 0, 8); + } + } + else { + PzemDc.send_retry--; + if ((Energy.phase_count > 1) && (0 == PzemDc.send_retry) && (uptime < PZEM_DC_STABILIZE)) { + Energy.phase_count--; + } + } +} + +void PzemDcSnsInit(void) +{ + PzemDcModbus = new TasmotaModbus(pin[GPIO_PZEM017_RX], pin[GPIO_PZEM0XX_TX]); + uint8_t result = PzemDcModbus->Begin(9600, 2); + if (result) { + if (2 == result) { ClaimSerial(); } + Energy.type_dc = true; + Energy.phase_count = 3; + PzemDc.channel = 0; + } else { + energy_flg = ENERGY_NONE; + } +} + +void PzemDcDrvInit(void) +{ + if ((pin[GPIO_PZEM017_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) { + energy_flg = XNRG_06; + } +} + +bool PzemDcCommand(void) +{ + bool serviced = true; + + if (CMND_MODULEADDRESS == Energy.command_code) { + PzemDc.address = XdrvMailbox.payload; + PzemDc.address_step = ADDR_SEND; + } + else serviced = false; + + return serviced; +} + + + + + +bool Xnrg06(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_ENERGY_EVERY_SECOND: + if (uptime > 4) { PzemDcEverySecond(); } + break; + case FUNC_COMMAND: + result = PzemDcCommand(); + break; + case FUNC_INIT: + PzemDcSnsInit(); + break; + case FUNC_PRE_INIT: + PzemDcDrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_07_ade7953.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_07_ade7953.ino" +#ifdef USE_I2C +#ifdef USE_ENERGY_SENSOR +#ifdef USE_ADE7953 +# 31 "S:/Development/Tasmota/tasmota/xnrg_07_ade7953.ino" +#define XNRG_07 7 +#define XI2C_07 7 + +#define ADE7953_PREF 1540 +#define ADE7953_UREF 26000 +#define ADE7953_IREF 10000 + +#define ADE7953_ADDR 0x38 + +const uint16_t Ade7953Registers[] { + 0x31B, + 0x313, + 0x311, + 0x315, + 0x31A, + 0x312, + 0x310, + 0x314, + 0x31C, + 0x10E +}; + +struct Ade7953 { + uint32_t voltage_rms = 0; + uint32_t period = 0; + uint32_t current_rms[2] = { 0, 0 }; + uint32_t active_power[2] = { 0, 0 }; + uint8_t init_step = 0; +} Ade7953; + +int Ade7953RegSize(uint16_t reg) +{ + int size = 0; + switch ((reg >> 8) & 0x0F) { + case 0x03: + size++; + case 0x02: + size++; + case 0x01: + size++; + case 0x00: + case 0x07: + case 0x08: + size++; + } + return size; +} + +void Ade7953Write(uint16_t reg, uint32_t val) +{ + int size = Ade7953RegSize(reg); + if (size) { + Wire.beginTransmission(ADE7953_ADDR); + Wire.write((reg >> 8) & 0xFF); + Wire.write(reg & 0xFF); + while (size--) { + Wire.write((val >> (8 * size)) & 0xFF); + } + Wire.endTransmission(); + delayMicroseconds(5); + } +} + +int32_t Ade7953Read(uint16_t reg) +{ + uint32_t response = 0; + + int size = Ade7953RegSize(reg); + if (size) { + Wire.beginTransmission(ADE7953_ADDR); + Wire.write((reg >> 8) & 0xFF); + Wire.write(reg & 0xFF); + Wire.endTransmission(0); + Wire.requestFrom(ADE7953_ADDR, size); + if (size <= Wire.available()) { + for (uint32_t i = 0; i < size; i++) { + response = response << 8 | Wire.read(); + } + } + } + return response; +} + +void Ade7953Init(void) +{ + Ade7953Write(0x102, 0x0004); + Ade7953Write(0x0FE, 0x00AD); + Ade7953Write(0x120, 0x0030); +} + +void Ade7953GetData(void) +{ + int32_t reg[2][4]; + for (uint32_t i = 0; i < sizeof(Ade7953Registers)/sizeof(uint16_t); i++) { + int32_t value = Ade7953Read(Ade7953Registers[i]); + if (8 == i) { + Ade7953.voltage_rms = value; + } else if (9 == i) { + Ade7953.period = value; + } else { + reg[i >> 2][i &3] = value; + } + } + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ADE: %d, %d, [%d, %d, %d, %d], [%d, %d, %d, %d]"), + Ade7953.voltage_rms, Ade7953.period, + reg[0][0], reg[0][1], reg[0][2], reg[0][3], + reg[1][0], reg[1][1], reg[1][2], reg[1][3]); + + uint32_t apparent_power[2] = { 0, 0 }; + uint32_t reactive_power[2] = { 0, 0 }; + + for (uint32_t channel = 0; channel < 2; channel++) { + Ade7953.current_rms[channel] = reg[channel][0]; + if (Ade7953.current_rms[channel] < 2000) { + Ade7953.current_rms[channel] = 0; + Ade7953.active_power[channel] = 0; + } else { + Ade7953.active_power[channel] = abs(reg[channel][1]); + apparent_power[channel] = abs(reg[channel][2]); + reactive_power[channel] = abs(reg[channel][3]); + } + } + + uint32_t current_rms_sum = Ade7953.current_rms[0] + Ade7953.current_rms[1]; + uint32_t active_power_sum = Ade7953.active_power[0] + Ade7953.active_power[1]; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ADE: U %d, C %d, I %d + %d = %d, P %d + %d = %d"), + Ade7953.voltage_rms, Ade7953.period, + Ade7953.current_rms[0], Ade7953.current_rms[1], current_rms_sum, + Ade7953.active_power[0], Ade7953.active_power[1], active_power_sum); + + if (Energy.power_on) { + Energy.voltage[0] = (float)Ade7953.voltage_rms / Settings.energy_voltage_calibration; + Energy.frequency[0] = 223750.0f / ( (float)Ade7953.period + 1); + + for (uint32_t channel = 0; channel < 2; channel++) { + Energy.data_valid[channel] = 0; + Energy.active_power[channel] = (float)Ade7953.active_power[channel] / (Settings.energy_power_calibration / 10); + Energy.reactive_power[channel] = (float)reactive_power[channel] / (Settings.energy_power_calibration / 10); + Energy.apparent_power[channel] = (float)apparent_power[channel] / (Settings.energy_power_calibration / 10); + if (0 == Energy.active_power[channel]) { + Energy.current[channel] = 0; + } else { + Energy.current[channel] = (float)Ade7953.current_rms[channel] / (Settings.energy_current_calibration * 10); + } + } + } else { + Energy.data_valid[0] = ENERGY_WATCHDOG; + Energy.data_valid[1] = ENERGY_WATCHDOG; + } + + if (active_power_sum) { + Energy.kWhtoday_delta += ((active_power_sum * (100000 / (Settings.energy_power_calibration / 10))) / 3600); + EnergyUpdateToday(); + } +} + +void Ade7953EnergyEverySecond(void) +{ + if (Ade7953.init_step) { + if (1 == Ade7953.init_step) { + Ade7953Init(); + } + Ade7953.init_step--; + } else { + Ade7953GetData(); + } +} + +void Ade7953DrvInit(void) +{ + if (pin[GPIO_ADE7953_IRQ] < 99) { + delay(100); + if (I2cSetDevice(ADE7953_ADDR)) { + if (HLW_PREF_PULSE == Settings.energy_power_calibration) { + Settings.energy_power_calibration = ADE7953_PREF; + Settings.energy_voltage_calibration = ADE7953_UREF; + Settings.energy_current_calibration = ADE7953_IREF; + } + I2cSetActiveFound(ADE7953_ADDR, "ADE7953"); + Ade7953.init_step = 2; + + Energy.phase_count = 2; + Energy.voltage_common = true; + + energy_flg = XNRG_07; + } + } +} + +bool Ade7953Command(void) +{ + bool serviced = true; + + uint32_t channel = (2 == XdrvMailbox.index) ? 1 : 0; + uint32_t value = (uint32_t)(CharToFloat(XdrvMailbox.data) * 100); + + if (CMND_POWERCAL == Energy.command_code) { + if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_PREF; } + + } + else if (CMND_VOLTAGECAL == Energy.command_code) { + if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_UREF; } + + } + else if (CMND_CURRENTCAL == Energy.command_code) { + if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_IREF; } + + } + else if (CMND_POWERSET == Energy.command_code) { + if (XdrvMailbox.data_len && Ade7953.active_power[channel]) { + if ((value > 100) && (value < 200000)) { + Settings.energy_power_calibration = (Ade7953.active_power[channel] * 1000) / value; + } + } + } + else if (CMND_VOLTAGESET == Energy.command_code) { + if (XdrvMailbox.data_len && Ade7953.voltage_rms) { + if ((value > 10000) && (value < 26000)) { + Settings.energy_voltage_calibration = (Ade7953.voltage_rms * 100) / value; + } + } + } + else if (CMND_CURRENTSET == Energy.command_code) { + if (XdrvMailbox.data_len && Ade7953.current_rms[channel]) { + if ((value > 2000) && (value < 1000000)) { + Settings.energy_current_calibration = ((Ade7953.current_rms[channel] * 100) / value) * 100; + } + } + } + else serviced = false; + + return serviced; +} + + + + + +bool Xnrg07(uint8_t function) +{ + if (!I2cEnabled(XI2C_07)) { return false; } + + bool result = false; + + switch (function) { + case FUNC_ENERGY_EVERY_SECOND: + Ade7953EnergyEverySecond(); + break; + case FUNC_COMMAND: + result = Ade7953Command(); + break; + case FUNC_PRE_INIT: + Ade7953DrvInit(); + break; + } + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_08_sdm120.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_08_sdm120.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_SDM120 + + + + + + +#define XNRG_08 8 + + +#ifndef SDM120_SPEED + #define SDM120_SPEED 2400 +#endif + +#ifndef SDM120_ADDR + #define SDM120_ADDR 1 +#endif + +#include +TasmotaModbus *Sdm120Modbus; + +const uint8_t sdm120_table = 8; +const uint8_t sdm220_table = 13; + +const uint16_t sdm120_start_addresses[] { + 0x0000, + 0x0006, + 0x000C, + 0x0012, + 0x0018, + 0x001E, + 0x0046, + 0x0156, + + 0X0048, + 0X004A, + 0X004C, + 0X004E, + 0X0024 +}; + +struct SDM120 { + float total_active = 0; + float import_active = NAN; + float import_reactive = 0; + float export_reactive = 0; + float phase_angle = 0; + uint8_t read_state = 0; + uint8_t send_retry = 0; + uint8_t start_address_count = sdm220_table; +} Sdm120; + + + +void SDM120Every250ms(void) +{ + bool data_ready = Sdm120Modbus->ReceiveReady(); + + if (data_ready) { + uint8_t buffer[14]; + + uint32_t error = Sdm120Modbus->ReceiveBuffer(buffer, 2); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Sdm120Modbus->ReceiveCount()); + + if (error) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: SDM120 error %d"), error); + } else { + Energy.data_valid[0] = 0; + + + + + float value; + ((uint8_t*)&value)[3] = buffer[3]; + ((uint8_t*)&value)[2] = buffer[4]; + ((uint8_t*)&value)[1] = buffer[5]; + ((uint8_t*)&value)[0] = buffer[6]; + + switch(Sdm120.read_state) { + case 0: + Energy.voltage[0] = value; + break; + + case 1: + Energy.current[0] = value; + break; + + case 2: + Energy.active_power[0] = value; + break; + + case 3: + Energy.apparent_power[0] = value; + break; + + case 4: + Energy.reactive_power[0] = value; + break; + + case 5: + Energy.power_factor[0] = value; + break; + + case 6: + Energy.frequency[0] = value; + break; + + case 7: + Sdm120.total_active = value; + break; + + case 8: + Sdm120.import_active = value; + break; + + case 9: + Energy.export_active = value; + break; + + case 10: + Sdm120.import_reactive = value; + break; + + case 11: + Sdm120.export_reactive = value; + break; + + case 12: + Sdm120.phase_angle = value; + break; + } + + Sdm120.read_state++; + if (Sdm120.read_state == Sdm120.start_address_count) { + Sdm120.read_state = 0; + + if (Sdm120.start_address_count > sdm120_table) { + if (!isnan(Sdm120.import_active)) { + Sdm120.total_active = Sdm120.import_active; + } else { + Sdm120.start_address_count = sdm120_table; + } + } + EnergyUpdateTotal(Sdm120.total_active, true); + } + } + } + + if (0 == Sdm120.send_retry || data_ready) { + Sdm120.send_retry = 5; + Sdm120Modbus->Send(SDM120_ADDR, 0x04, sdm120_start_addresses[Sdm120.read_state], 2); + } else { + Sdm120.send_retry--; + } +} + +void Sdm120SnsInit(void) +{ + Sdm120Modbus = new TasmotaModbus(pin[GPIO_SDM120_RX], pin[GPIO_SDM120_TX]); + uint8_t result = Sdm120Modbus->Begin(SDM120_SPEED); + if (result) { + if (2 == result) { ClaimSerial(); } + } else { + energy_flg = ENERGY_NONE; + } +} + +void Sdm120DrvInit(void) +{ + if ((pin[GPIO_SDM120_RX] < 99) && (pin[GPIO_SDM120_TX] < 99)) { + energy_flg = XNRG_08; + } +} + +void Sdm220Reset(void) +{ + if (isnan(Sdm120.import_active)) { return; } + + Sdm120.import_active = 0; + Sdm120.import_reactive = 0; + Sdm120.export_reactive = 0; + Sdm120.phase_angle = 0; +} + +#ifdef USE_WEBSERVER +const char HTTP_ENERGY_SDM220[] PROGMEM = + "{s}" D_IMPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}" + "{s}" D_EXPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}" + "{s}" D_PHASE_ANGLE "{m}%s " D_UNIT_ANGLE "{e}"; +#endif + +void Sdm220Show(bool json) +{ + if (isnan(Sdm120.import_active)) { return; } + + char import_active_chr[FLOATSZ]; + dtostrfd(Sdm120.import_active, Settings.flag2.energy_resolution, import_active_chr); + char import_reactive_chr[FLOATSZ]; + dtostrfd(Sdm120.import_reactive, Settings.flag2.energy_resolution, import_reactive_chr); + char export_reactive_chr[FLOATSZ]; + dtostrfd(Sdm120.export_reactive, Settings.flag2.energy_resolution, export_reactive_chr); + char phase_angle_chr[FLOATSZ]; + dtostrfd(Sdm120.phase_angle, 2, phase_angle_chr); + + if (json) { + ResponseAppend_P(PSTR(",\"" D_JSON_IMPORT_ACTIVE "\":%s,\"" D_JSON_IMPORT_REACTIVE "\":%s,\"" D_JSON_EXPORT_REACTIVE "\":%s,\"" D_JSON_PHASE_ANGLE "\":%s"), + import_active_chr, import_reactive_chr, export_reactive_chr, phase_angle_chr); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_ENERGY_SDM220, import_reactive_chr, export_reactive_chr, phase_angle_chr); +#endif + } +} + + + + + +bool Xnrg08(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_250_MSECOND: + if (uptime > 4) { SDM120Every250ms(); } + break; + case FUNC_JSON_APPEND: + Sdm220Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Sdm220Show(0); + break; +#endif + case FUNC_ENERGY_RESET: + Sdm220Reset(); + break; + case FUNC_INIT: + Sdm120SnsInit(); + break; + case FUNC_PRE_INIT: + Sdm120DrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_09_dds2382.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_09_dds2382.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_DDS2382 + + + + + + +#define XNRG_09 9 + +#ifndef DDS2382_SPEED +#define DDS2382_SPEED 9600 +#endif +#ifndef DDS2382_ADDR +#define DDS2382_ADDR 1 +#endif + +#include +TasmotaModbus *Dds2382Modbus; + +uint8_t Dds2382_send_retry = 0; + +void Dds2382EverySecond(void) +{ + bool data_ready = Dds2382Modbus->ReceiveReady(); + + if (data_ready) { + uint8_t buffer[46]; + + uint32_t error = Dds2382Modbus->ReceiveBuffer(buffer, 18); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Dds2382Modbus->ReceiveCount()); + + if (error) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "DDS2382 response error %d"), error); + } else { + Energy.data_valid[0] = 0; +# 67 "S:/Development/Tasmota/tasmota/xnrg_09_dds2382.ino" + Energy.voltage[0] = (float)((buffer[27] << 8) + buffer[28]) / 10.0; + Energy.current[0] = (float)((buffer[29] << 8) + buffer[30]) / 100.0; + Energy.active_power[0] = (float)((buffer[31] << 8) + buffer[32]); + Energy.reactive_power[0] = (float)((buffer[33] << 8) + buffer[34]); + Energy.power_factor[0] = (float)((buffer[35] << 8) + buffer[36]) / 1000.0; + Energy.frequency[0] = (float)((buffer[37] << 8) + buffer[38]) / 100.0; + uint8_t offset = 11; + if (Settings.flag3.dds2382_model) { + offset = 19; + } + Energy.export_active = (float)((buffer[offset] << 24) + (buffer[offset +1] << 16) + (buffer[offset +2] << 8) + buffer[offset +3]) / 100.0; + float import_active = (float)((buffer[offset +4] << 24) + (buffer[offset +5] << 16) + (buffer[offset +6] << 8) + buffer[offset +7]) / 100.0; + + EnergyUpdateTotal(import_active, true); + } + } + + if (0 == Dds2382_send_retry || data_ready) { + Dds2382_send_retry = 5; + Dds2382Modbus->Send(DDS2382_ADDR, 0x03, 0, 18); + } else { + Dds2382_send_retry--; + } +} + +void Dds2382SnsInit(void) +{ + Dds2382Modbus = new TasmotaModbus(pin[GPIO_DDS2382_RX], pin[GPIO_DDS2382_TX]); + uint8_t result = Dds2382Modbus->Begin(DDS2382_SPEED); + if (result) { + if (2 == result) { ClaimSerial(); } + } else { + energy_flg = ENERGY_NONE; + } +} + +void Dds2382DrvInit(void) +{ + if ((pin[GPIO_DDS2382_RX] < 99) && (pin[GPIO_DDS2382_TX] < 99)) { + energy_flg = XNRG_09; + } +} + + + + + +bool Xnrg09(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_ENERGY_EVERY_SECOND: + if (uptime > 4) { Dds2382EverySecond(); } + break; + case FUNC_INIT: + Dds2382SnsInit(); + break; + case FUNC_PRE_INIT: + Dds2382DrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_10_sdm630.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_10_sdm630.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_SDM630 + + + + + + +#define XNRG_10 10 + + +#ifndef SDM630_SPEED + #define SDM630_SPEED 9600 +#endif + +#ifndef SDM630_ADDR + #define SDM630_ADDR 1 +#endif + +#include +TasmotaModbus *Sdm630Modbus; + +const uint16_t sdm630_start_addresses[] { + 0x0000, + 0x0002, + 0x0004, + 0x0006, + 0x0008, + 0x000A, + 0x000C, + 0x000E, + 0x0010, + 0x0018, + 0x001A, + 0x001C, + 0x001E, + 0x0020, + 0x0022, + 0x0156 +}; + +struct SDM630 { + uint8_t read_state = 0; + uint8_t send_retry = 0; +} Sdm630; + + + +void SDM630Every250ms(void) +{ + bool data_ready = Sdm630Modbus->ReceiveReady(); + + if (data_ready) { + uint8_t buffer[14]; + + uint32_t error = Sdm630Modbus->ReceiveBuffer(buffer, 2); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Sdm630Modbus->ReceiveCount()); + + if (error) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: SDM630 error %d"), error); + } else { + Energy.data_valid[0] = 0; + Energy.data_valid[1] = 0; + Energy.data_valid[2] = 0; + + + + + float value; + ((uint8_t*)&value)[3] = buffer[3]; + ((uint8_t*)&value)[2] = buffer[4]; + ((uint8_t*)&value)[1] = buffer[5]; + ((uint8_t*)&value)[0] = buffer[6]; + + switch(Sdm630.read_state) { + case 0: + Energy.voltage[0] = value; + break; + + case 1: + Energy.voltage[1] = value; + break; + + case 2: + Energy.voltage[2] = value; + break; + + case 3: + Energy.current[0] = value; + break; + + case 4: + Energy.current[1] = value; + break; + + case 5: + Energy.current[2] = value; + break; + + case 6: + Energy.active_power[0] = value; + break; + + case 7: + Energy.active_power[1] = value; + break; + + case 8: + Energy.active_power[2] = value; + break; + + case 9: + Energy.reactive_power[0] = value; + break; + + case 10: + Energy.reactive_power[1] = value; + break; + + case 11: + Energy.reactive_power[2] = value; + break; + + case 12: + Energy.power_factor[0] = value; + break; + + case 13: + Energy.power_factor[1] = value; + break; + + case 14: + Energy.power_factor[2] = value; + break; + + case 15: + EnergyUpdateTotal(value, true); + break; + } + + Sdm630.read_state++; + if (sizeof(sdm630_start_addresses)/2 == Sdm630.read_state) { + Sdm630.read_state = 0; + } + } + } + + if (0 == Sdm630.send_retry || data_ready) { + Sdm630.send_retry = 5; + Sdm630Modbus->Send(SDM630_ADDR, 0x04, sdm630_start_addresses[Sdm630.read_state], 2); + } else { + Sdm630.send_retry--; + } +} + +void Sdm630SnsInit(void) +{ + Sdm630Modbus = new TasmotaModbus(pin[GPIO_SDM630_RX], pin[GPIO_SDM630_TX]); + uint8_t result = Sdm630Modbus->Begin(SDM630_SPEED); + if (result) { + if (2 == result) { ClaimSerial(); } + Energy.phase_count = 3; + } else { + energy_flg = ENERGY_NONE; + } +} + +void Sdm630DrvInit(void) +{ + if ((pin[GPIO_SDM630_RX] < 99) && (pin[GPIO_SDM630_TX] < 99)) { + energy_flg = XNRG_10; + } +} + + + + + +bool Xnrg10(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_250_MSECOND: + if (uptime > 4) { SDM630Every250ms(); } + break; + case FUNC_INIT: + Sdm630SnsInit(); + break; + case FUNC_PRE_INIT: + Sdm630DrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_11_ddsu666.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_11_ddsu666.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_DDSU666 + + + + +#define XNRG_11 11 + + +#ifndef DDSU666_SPEED + #define DDSU666_SPEED 9600 +#endif + +#ifndef DDSU666_ADDR + #define DDSU666_ADDR 1 +#endif + +#include +TasmotaModbus *Ddsu666Modbus; + +const uint16_t Ddsu666_start_addresses[] { + 0x2000, + 0x2002, + 0x2004, + 0x2006, + 0x200A, + 0x200E, + 0X4000, + 0X400A, +}; + +struct DDSU666 { + float import_active = NAN; + uint8_t read_state = 0; + uint8_t send_retry = 0; +} Ddsu666; + + + +void DDSU666Every250ms(void) +{ + bool data_ready = Ddsu666Modbus->ReceiveReady(); + + if (data_ready) { + uint8_t buffer[14]; + + uint32_t error = Ddsu666Modbus->ReceiveBuffer(buffer, 2); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Ddsu666Modbus->ReceiveCount()); + + if (error) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: Ddsu666 error %d"), error); + } else { + Energy.data_valid[0] = 0; + + + + + float value; + ((uint8_t*)&value)[3] = buffer[3]; + ((uint8_t*)&value)[2] = buffer[4]; + ((uint8_t*)&value)[1] = buffer[5]; + ((uint8_t*)&value)[0] = buffer[6]; + + switch(Ddsu666.read_state) { + case 0: + Energy.voltage[0] = value; + break; + + case 1: + Energy.current[0] = value; + break; + + case 2: + Energy.active_power[0] = value * 1000; + break; + + case 3: + Energy.reactive_power[0] = value * 1000; + break; + + case 4: + Energy.power_factor[0] = value; + break; + + case 5: + Energy.frequency[0] = value; + break; + + case 6: + Ddsu666.import_active = value; + break; + + case 7: + Energy.export_active = value; + break; + } + + Ddsu666.read_state++; + + if (Ddsu666.read_state == 8) { + Ddsu666.read_state = 0; + EnergyUpdateTotal(Ddsu666.import_active, true); + } + } + } + + if (0 == Ddsu666.send_retry || data_ready) { + Ddsu666.send_retry = 5; + Ddsu666Modbus->Send(DDSU666_ADDR, 0x04, Ddsu666_start_addresses[Ddsu666.read_state], 2); + } else { + Ddsu666.send_retry--; + } +} + +void Ddsu666SnsInit(void) +{ + Ddsu666Modbus = new TasmotaModbus(pin[GPIO_DDSU666_RX], pin[GPIO_DDSU666_TX]); + uint8_t result = Ddsu666Modbus->Begin(DDSU666_SPEED); + if (result) { + if (2 == result) { ClaimSerial(); } + } else { + energy_flg = ENERGY_NONE; + } +} + +void Ddsu666DrvInit(void) +{ + if ((pin[GPIO_DDSU666_RX] < 99) && (pin[GPIO_DDSU666_TX] < 99)) { + energy_flg = XNRG_11; + } +} + + + + + +bool Xnrg11(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_250_MSECOND: + if (uptime > 4) { DDSU666Every250ms(); } + break; + case FUNC_INIT: + Ddsu666SnsInit(); + break; + case FUNC_PRE_INIT: + Ddsu666DrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_12_solaxX1.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_12_solaxX1.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_SOLAX_X1 + + + + +#define XNRG_12 12 + +#ifndef SOLAXX1_SPEED +#define SOLAXX1_SPEED 9600 +#endif + +#define INVERTER_ADDRESS 0x0A + +#define D_SOLAX_X1 "SolaxX1" + +#include + +enum solaxX1_Error +{ + solaxX1_ERR_NO_ERROR, + solaxX1_ERR_CRC_ERROR +}; + +union { + uint32_t ErrMessage; + struct { + + uint8_t TzProtectFault:1; + uint8_t MainsLostFault:1; + uint8_t GridVoltFault:1; + uint8_t GridFreqFault:1; + uint8_t PLLLostFault:1; + uint8_t BusVoltFault:1; + uint8_t ErrBit06:1; + uint8_t OciFault:1; + + uint8_t Dci_OCP_Fault:1; + uint8_t ResidualCurrentFault:1; + uint8_t PvVoltFault:1; + uint8_t Ac10Mins_Voltage_Fault:1; + uint8_t IsolationFault:1; + uint8_t TemperatureOverFault:1; + uint8_t FanFault:1; + uint8_t ErrBit15:1; + + uint8_t SpiCommsFault:1; + uint8_t SciCommsFault:1; + uint8_t ErrBit18:1; + uint8_t InputConfigFault:1; + uint8_t EepromFault:1; + uint8_t RelayFault:1; + uint8_t SampleConsistenceFault:1; + uint8_t ResidualCurrent_DeviceFault:1; + + uint8_t ErrBit24:1; + uint8_t ErrBit25:1; + uint8_t ErrBit26:1; + uint8_t ErrBit27:1; + uint8_t ErrBit28:1; + uint8_t DCI_DeviceFault:1; + uint8_t OtherDeviceFault:1; + uint8_t ErrBit31:1; + }; +} ErrCode; + +const char kSolaxMode[] PROGMEM = D_WAITING "|" D_CHECKING "|" D_WORKING "|" D_FAILURE; + +const char kSolaxError[] PROGMEM = + D_SOLAX_ERROR_0 "|" D_SOLAX_ERROR_1 "|" D_SOLAX_ERROR_2 "|" D_SOLAX_ERROR_3 "|" D_SOLAX_ERROR_4 "|" D_SOLAX_ERROR_5 "|" + D_SOLAX_ERROR_6 "|" D_SOLAX_ERROR_7 "|" D_SOLAX_ERROR_8; + + + +TasmotaSerial *solaxX1Serial; + +uint8_t solaxX1_Init = 1; + +struct SOLAXX1 { + float temperature = 0; + float energy_today = 0; + float dc1_voltage = 0; + float dc2_voltage = 0; + float dc1_current = 0; + float dc2_current = 0; + float energy_total = 0; + float runtime_total = 0; + float dc1_power = 0; + float dc2_power = 0; + + uint8_t status = 0; + uint32_t errorCode = 0; +} solaxX1; + +union { + uint8_t status; + struct { + uint8_t freeBit7:1; + uint8_t freeBit6:1; + uint8_t freeBit5:1; + uint8_t queryOffline:1; + uint8_t queryOfflineSend:1; + uint8_t hasAddress:1; + uint8_t inverterAddressSend:1; + uint8_t inverterSnReceived:1; + }; +} protocolStatus; + +uint8_t header[2] = {0xAA, 0x55}; +uint8_t source[2] = {0x00, 0x00}; +uint8_t destination[2] = {0x00, 0x00}; +uint8_t controlCode[1] = {0x00}; +uint8_t functionCode[1] = {0x00}; +uint8_t dataLength[1] = {0x00}; +uint8_t data[16] = {0}; + +uint8_t message[30]; + + + +bool solaxX1_RS485ReceiveReady(void) +{ + return (solaxX1Serial->available() > 1); +} + +void solaxX1_RS485Send(uint16_t msgLen) +{ + memcpy(message, header, 2); + memcpy(message + 2, source, 2); + memcpy(message + 4, destination, 2); + memcpy(message + 6, controlCode, 1); + memcpy(message + 7, functionCode, 1); + memcpy(message + 8, dataLength, 1); + memcpy(message + 9, data, sizeof(data)); + uint16_t crc = solaxX1_calculateCRC(message, msgLen); + + while (solaxX1Serial->available() > 0) + { + solaxX1Serial->read(); + } + + solaxX1Serial->flush(); + solaxX1Serial->write(message, msgLen); + solaxX1Serial->write(highByte(crc)); + solaxX1Serial->write(lowByte(crc)); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, message, msgLen); +} + +uint8_t solaxX1_RS485Receive(uint8_t *value) +{ + uint8_t len = 0; + + while (solaxX1Serial->available() > 0) + { + value[len++] = (uint8_t)solaxX1Serial->read(); + } + + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, value, len); + + uint16_t crc = solaxX1_calculateCRC(value, len - 2); + + if (value[len - 1] == lowByte(crc) && value[len - 2] == highByte(crc)) + { + return solaxX1_ERR_NO_ERROR; + } + else + { + return solaxX1_ERR_CRC_ERROR; + } +} + +uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen) +{ + uint8_t i; + uint16_t wChkSum; + wChkSum = 0; + + for (i = 0; i < bLen; i++) + { + wChkSum = wChkSum + bExternTxPackage[i]; + } + return wChkSum; +} + +void solaxX1_SendInverterAddress(void) +{ + source[0] = 0x00; + destination[0] = 0x00; + destination[1] = 0x00; + controlCode[0] = 0x10; + functionCode[0] = 0x01; + dataLength[0] = 0x0F; + data[14] = INVERTER_ADDRESS; + solaxX1_RS485Send(24); +} + +void solaxX1_QueryLiveData(void) +{ + source[0] = 0x01; + destination[0] = 0x00; + destination[1] = INVERTER_ADDRESS; + controlCode[0] = 0x11; + functionCode[0] = 0x02; + dataLength[0] = 0x00; + solaxX1_RS485Send(9); +} + +uint8_t solaxX1_ParseErrorCode(uint32_t code){ + ErrCode.ErrMessage = code; + + if (code == 0) return 0; + if (ErrCode.MainsLostFault) return 1; + if (ErrCode.GridVoltFault) return 2; + if (ErrCode.GridFreqFault) return 3; + if (ErrCode.PvVoltFault) return 4; + if (ErrCode.IsolationFault) return 5; + if (ErrCode.TemperatureOverFault) return 6; + if (ErrCode.FanFault) return 7; + if (ErrCode.OtherDeviceFault) return 8; +} + + + +uint8_t solaxX1_send_retry = 0; +uint8_t solaxX1_nodata_count = 0; + +void solaxX1250MSecond(void) +{ + uint8_t value[61] = {0}; + bool data_ready = solaxX1_RS485ReceiveReady(); + + if (protocolStatus.hasAddress && (data_ready || solaxX1_send_retry == 0)) + { + if (data_ready) + { + uint8_t error = solaxX1_RS485Receive(value); + if (error) + { + DEBUG_SENSOR_LOG(PSTR("SX1: Data response CRC error")); + } + else + { + solaxX1_nodata_count = 0; + solaxX1_send_retry = 12; + Energy.data_valid[0] = 0; + + solaxX1.temperature = (float)((value[9] << 8) | value[10]); + solaxX1.energy_today = (float)((value[11] << 8) | value[12]) * 0.1f; + solaxX1.dc1_voltage = (float)((value[13] << 8) | value[14]) * 0.1f; + solaxX1.dc2_voltage = (float)((value[15] << 8) | value[16]) * 0.1f; + solaxX1.dc1_current = (float)((value[17] << 8) | value[18]) * 0.1f; + solaxX1.dc2_current = (float)((value[19] << 8) | value[20]) * 0.1f; + Energy.current[0] = (float)((value[21] << 8) | value[22]) * 0.1f; + Energy.voltage[0] = (float)((value[23] << 8) | value[24]) * 0.1f; + Energy.frequency[0] = (float)((value[25] << 8) | value[26]) * 0.01f; + Energy.active_power[0] = (float)((value[27] << 8) | value[28]); + + solaxX1.energy_total = (float)((value[31] << 8) | (value[32] << 8) | (value[33] << 8) | value[34]) * 0.1f; + solaxX1.runtime_total = (float)((value[35] << 8) | (value[36] << 8) | (value[37] << 8) | value[38]); + solaxX1.status = (uint8_t)((value[39] << 8) | value[40]); + + + + + + + + solaxX1.errorCode = (uint32_t)((value[58] << 8) | (value[57] << 8) | (value[56] << 8) | value[55]); + + solaxX1.dc1_power = solaxX1.dc1_voltage * solaxX1.dc1_current; + solaxX1.dc2_power = solaxX1.dc2_voltage * solaxX1.dc2_current; + + solaxX1_QueryLiveData(); + EnergyUpdateTotal(solaxX1.energy_total, true); + } + } + + if (0 == solaxX1_send_retry && 255 != solaxX1_nodata_count) { + solaxX1_send_retry = 12; + solaxX1_QueryLiveData(); + } + + + + if (255 == solaxX1_nodata_count) { + solaxX1_nodata_count = 0; + solaxX1_send_retry = 12; + } + } + else + { + if ((solaxX1_nodata_count % 4) == 0) { DEBUG_SENSOR_LOG(PSTR("SX1: No Data count: %d"), solaxX1_nodata_count); } + if (solaxX1_nodata_count < 10 * 4) + { + solaxX1_nodata_count++; + } + else if (255 != solaxX1_nodata_count) + { + + solaxX1_nodata_count = 255; + solaxX1_send_retry = 12; + protocolStatus.status = 0b00001000; + Energy.data_valid[0] = ENERGY_WATCHDOG; + + solaxX1.temperature = solaxX1.dc1_voltage = solaxX1.dc2_voltage = solaxX1.dc1_current = solaxX1.dc2_current = solaxX1.dc1_power = 0; + solaxX1.dc2_power = solaxX1.status = Energy.current[0] = Energy.voltage[0] = Energy.frequency[0] = Energy.active_power[0] = 0; + + } + } + + if (!protocolStatus.hasAddress && (data_ready || solaxX1_send_retry == 0)) + { + if (data_ready) + { + + if (protocolStatus.inverterAddressSend) + { + uint8_t error = solaxX1_RS485Receive(value); + if (error) + { + DEBUG_SENSOR_LOG(PSTR("SX1: Address confirmation response CRC error")); + } + else + { + if (value[6] == 0x10 && value[7] == 0x81 && value[9] == 0x06) + { + DEBUG_SENSOR_LOG(PSTR("SX1: Set hasAddress")); + protocolStatus.status = 0b00100000; + } + } + } + + + if (protocolStatus.queryOfflineSend) + { + uint8_t error = solaxX1_RS485Receive(value); + if (error) + { + DEBUG_SENSOR_LOG(PSTR("SX1: Query Offline response CRC error")); + } + else + { + + if (value[6] == 0x10 && value[7] == 0x80 && protocolStatus.inverterSnReceived == false) + { + for (uint8_t i = 9; i <= 22; i++) + { + data[i - 9] = value[i]; + } + solaxX1_SendInverterAddress(); + protocolStatus.status = 0b1100000; + DEBUG_SENSOR_LOG(PSTR("SX1: Set inverterSnReceived and inverterAddressSend")); + } + } + } + } + + if (solaxX1_send_retry == 0) + { + if (protocolStatus.queryOfflineSend) + { + protocolStatus.status = 0b00001000; + DEBUG_SENSOR_LOG(PSTR("SX1: Set Query Offline")); + } + solaxX1_send_retry = 12; + } + + + if (protocolStatus.queryOffline) + { + + source[0] = 0x01; + destination[1] = 0x00; + controlCode[0] = 0x10; + functionCode[0] = 0x00; + dataLength[0] = 0x00; + solaxX1_RS485Send(9); + protocolStatus.status = 0b00010000; + DEBUG_SENSOR_LOG(PSTR("SX1: Query Offline Send")); + } + } + + if (!data_ready) + solaxX1_send_retry--; +} + +void solaxX1SnsInit(void) +{ + AddLog_P(LOG_LEVEL_DEBUG, PSTR("SX1: Solax X1 Inverter Init")); + DEBUG_SENSOR_LOG(PSTR("SX1: RX pin: %d, TX pin: %d"), pin[GPIO_SOLAXX1_RX], pin[GPIO_SOLAXX1_TX]); + protocolStatus.status = 0b00100000; + + solaxX1Serial = new TasmotaSerial(pin[GPIO_SOLAXX1_RX], pin[GPIO_SOLAXX1_TX], 1); + if (solaxX1Serial->begin(SOLAXX1_SPEED)) { + if (solaxX1Serial->hardwareSerial()) { ClaimSerial(); } + } else { + energy_flg = ENERGY_NONE; + } +} + +void solaxX1DrvInit(void) +{ + if ((pin[GPIO_SOLAXX1_RX] < 99) && (pin[GPIO_SOLAXX1_TX] < 99)) { + energy_flg = XNRG_12; + } +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_solaxX1_DATA1[] PROGMEM = + "{s}" D_SOLAX_X1 " " D_SOLAR_POWER "{m}%s " D_UNIT_WATT "{e}" + "{s}" D_SOLAX_X1 " " D_PV1_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" + "{s}" D_SOLAX_X1 " " D_PV1_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" + "{s}" D_SOLAX_X1 " " D_PV1_POWER "{m}%s " D_UNIT_WATT "{e}"; +#ifdef SOLAXX1_PV2 +const char HTTP_SNS_solaxX1_DATA2[] PROGMEM = + "{s}" D_SOLAX_X1 " " D_PV2_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" + "{s}" D_SOLAX_X1 " " D_PV2_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" + "{s}" D_SOLAX_X1 " " D_PV2_POWER "{m}%s " D_UNIT_WATT "{e}"; +#endif +const char HTTP_SNS_solaxX1_DATA3[] PROGMEM = + "{s}" D_SOLAX_X1 " " D_UPTIME "{m}%s " D_UNIT_HOUR "{e}" + "{s}" D_SOLAX_X1 " " D_STATUS "{m}%s" + "{s}" D_SOLAX_X1 " " D_ERROR "{m}%s"; +#endif + +void solaxX1Show(bool json) +{ + char solar_power[33]; + dtostrfd(solaxX1.dc1_power + solaxX1.dc2_power, Settings.flag2.wattage_resolution, solar_power); + char pv1_voltage[33]; + dtostrfd(solaxX1.dc1_voltage, Settings.flag2.voltage_resolution, pv1_voltage); + char pv1_current[33]; + dtostrfd(solaxX1.dc1_current, Settings.flag2.current_resolution, pv1_current); + char pv1_power[33]; + dtostrfd(solaxX1.dc1_power, Settings.flag2.wattage_resolution, pv1_power); +#ifdef SOLAXX1_PV2 + char pv2_voltage[33]; + dtostrfd(solaxX1.dc2_voltage, Settings.flag2.voltage_resolution, pv2_voltage); + char pv2_current[33]; + dtostrfd(solaxX1.dc2_current, Settings.flag2.current_resolution, pv2_current); + char pv2_power[33]; + dtostrfd(solaxX1.dc2_power, Settings.flag2.wattage_resolution, pv2_power); +#endif + char temperature[33]; + dtostrfd(solaxX1.temperature, Settings.flag2.temperature_resolution, temperature); + char runtime[33]; + dtostrfd(solaxX1.runtime_total, 0, runtime); + char status[33]; + GetTextIndexed(status, sizeof(status), solaxX1.status, kSolaxMode); + + if (json) + { + ResponseAppend_P(PSTR(",\"" D_JSON_SOLAR_POWER "\":%s,\"" D_JSON_PV1_VOLTAGE "\":%s,\"" D_JSON_PV1_CURRENT "\":%s,\"" D_JSON_PV1_POWER "\":%s"), + solar_power, pv1_voltage, pv1_current, pv1_power); +#ifdef SOLAXX1_PV2 + ResponseAppend_P(PSTR(",\"" D_JSON_PV2_VOLTAGE "\":%s,\"" D_JSON_PV2_CURRENT "\":%s,\"" D_JSON_PV2_POWER "\":%s"), + pv2_voltage, pv2_current, pv2_power); +#endif + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_RUNTIME "\":%s,\"" D_JSON_STATUS "\":\"%s\",\"" D_JSON_ERROR "\":%d"), + temperature, runtime, status, solaxX1.errorCode); + +#ifdef USE_WEBSERVER + } + else + { + WSContentSend_PD(HTTP_SNS_solaxX1_DATA1, solar_power, pv1_voltage, pv1_current, pv1_power); +#ifdef SOLAXX1_PV2 + WSContentSend_PD(HTTP_SNS_solaxX1_DATA2, pv2_voltage, pv2_current, pv2_power); +#endif + WSContentSend_PD(HTTP_SNS_TEMP, D_SOLAX_X1, temperature, TempUnit()); + char errorCodeString[33]; + WSContentSend_PD(HTTP_SNS_solaxX1_DATA3, runtime, status, + GetTextIndexed(errorCodeString, sizeof(errorCodeString), solaxX1_ParseErrorCode(solaxX1.errorCode), kSolaxError)); +#endif + } +} + + + + + +bool Xnrg12(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_250_MSECOND: + if (uptime > 4) { solaxX1250MSecond(); } + break; + case FUNC_JSON_APPEND: + solaxX1Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + solaxX1Show(0); + break; +#endif + case FUNC_INIT: + solaxX1SnsInit(); + break; + case FUNC_PRE_INIT: + solaxX1DrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_13_fif_le01mr.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_13_fif_le01mr.ino" +#ifdef USE_ENERGY_SENSOR +#ifdef USE_LE01MR +# 71 "S:/Development/Tasmota/tasmota/xnrg_13_fif_le01mr.ino" +#define XNRG_13 13 + + +#ifndef LE01MR_SPEED + #define LE01MR_SPEED 2400 +#endif + +#ifndef LE01MR_ADDR + #define LE01MR_ADDR 1 +#endif + +#include +TasmotaModbus *FifLEModbus; + +const uint8_t le01mr_table_sz = 9; + +const uint16_t le01mr_register_addresses[] { + + 0x0130, + 0x0131, + 0x0158, + 0x0139, + 0x0140, + 0x0148, + 0x0150, + 0xA000, + 0xA01E +}; + +struct LE01MR { + float total_active = 0; + float total_reactive = 0; + uint8_t read_state = 0; + uint8_t send_retry = 0; + uint8_t start_address_count = le01mr_table_sz; +} Le01mr; + + + +void FifLEEvery250ms(void) +{ + bool data_ready = FifLEModbus->ReceiveReady(); + + if (data_ready) { + uint8_t buffer[14]; + uint8_t reg_count = 2; + if (Le01mr.read_state < 3) { + reg_count=1; + } + + uint32_t error = FifLEModbus->ReceiveBuffer(buffer, reg_count); + + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, FifLEModbus->ReceiveCount()); + + if (error) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("FiF-LE: LE01MR Modbus error %d"), error); + } else { + Energy.data_valid[0] = 0; +# 146 "S:/Development/Tasmota/tasmota/xnrg_13_fif_le01mr.ino" + uint32_t value_buff = 0; + + if (Le01mr.read_state >= 0 && Le01mr.read_state < 3) { + value_buff = ((uint32_t)buffer[3])<<8 | buffer[4]; + } else { + value_buff = ((uint32_t)buffer[3])<<24 | ((uint32_t)buffer[4])<<16 | ((uint32_t)buffer[5])<<8 | buffer[6]; + } + + switch(Le01mr.read_state) { + case 0: + Energy.frequency[0] = value_buff * 0.01f; + break; + + case 1: + Energy.voltage[0] = value_buff * 0.01f; + break; + + case 2: + Energy.power_factor[0] = ((int16_t)value_buff) * 0.001f; + break; + + case 3: + Energy.current[0] = value_buff * 0.001f; + break; + + case 4: + Energy.active_power[0] = value_buff * 1.0f; + break; + + case 5: + Energy.reactive_power[0] = value_buff * 1.0f; + break; + + case 6: + Energy.apparent_power[0] = value_buff * 1.0f; + break; + + case 7: + Le01mr.total_active = value_buff * 0.01f; + break; + + case 8: + Le01mr.total_reactive = value_buff * 0.01f; + break; + } + + Le01mr.read_state++; + if (Le01mr.read_state == Le01mr.start_address_count) { + Le01mr.read_state = 0; + + EnergyUpdateTotal(Le01mr.total_active, true); + } + } + } + + if (0 == Le01mr.send_retry || data_ready) { + uint8_t reg_count = 2; + + Le01mr.send_retry = 5; + + if (Le01mr.read_state < 3) reg_count=1; + + FifLEModbus->Send(LE01MR_ADDR, 0x03, le01mr_register_addresses[Le01mr.read_state], reg_count); + } else { + Le01mr.send_retry--; + } +} + +void FifLESnsInit(void) +{ + FifLEModbus = new TasmotaModbus(pin[GPIO_LE01MR_RX], pin[GPIO_LE01MR_TX]); + uint8_t result = FifLEModbus->Begin(LE01MR_SPEED); + if (result) { + if (2 == result) { ClaimSerial(); } + } else { + energy_flg = ENERGY_NONE; + } +} + +void FifLEDrvInit(void) +{ + if ((pin[GPIO_LE01MR_RX] < 99) && (pin[GPIO_LE01MR_TX] < 99)) { + energy_flg = XNRG_13; + } +} + +void FifLEReset(void) +{ + Le01mr.total_active = 0; + Le01mr.total_reactive = 0; +} + +#ifdef USE_WEBSERVER +const char HTTP_ENERGY_LE01MR[] PROGMEM = + "{s}" D_TOTAL_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}" + "{s}" D_TOTAL_REACTIVE "{m}%s " D_UNIT_KWARH "{e}" + ; +#endif + +void FifLEShow(bool json) +{ + char total_reactive_chr[FLOATSZ]; + dtostrfd(Le01mr.total_reactive, Settings.flag2.energy_resolution, total_reactive_chr); + char total_active_chr[FLOATSZ]; + dtostrfd(Le01mr.total_active, Settings.flag2.energy_resolution, total_active_chr); + + if (json) { + ResponseAppend_P(PSTR(",\"" D_JSON_TOTAL_ACTIVE "\":%s,\"" D_JSON_TOTAL_REACTIVE "\":%s"), + total_active_chr, total_reactive_chr); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_ENERGY_LE01MR, total_active_chr, total_reactive_chr); +#endif + } +} + + + + + +bool Xnrg13(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_250_MSECOND: + if (uptime > 4) { + FifLEEvery250ms(); + } + break; + case FUNC_JSON_APPEND: + FifLEShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + FifLEShow(0); + break; +#endif + case FUNC_ENERGY_RESET: + FifLEReset(); + break; + case FUNC_INIT: + FifLESnsInit(); + break; + case FUNC_PRE_INIT: + FifLEDrvInit(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xnrg_interface.ino" +# 20 "S:/Development/Tasmota/tasmota/xnrg_interface.ino" +#ifdef USE_ENERGY_SENSOR + +#ifdef XFUNC_PTR_IN_ROM +bool (* const xnrg_func_ptr[])(uint8_t) PROGMEM = { +#else +bool (* const xnrg_func_ptr[])(uint8_t) = { +#endif + +#ifdef XNRG_01 + &Xnrg01, +#endif + +#ifdef XNRG_02 + &Xnrg02, +#endif + +#ifdef XNRG_03 + &Xnrg03, +#endif + +#ifdef XNRG_04 + &Xnrg04, +#endif + +#ifdef XNRG_05 + &Xnrg05, +#endif + +#ifdef XNRG_06 + &Xnrg06, +#endif + +#ifdef XNRG_07 + &Xnrg07, +#endif + +#ifdef XNRG_08 + &Xnrg08, +#endif + +#ifdef XNRG_09 + &Xnrg09, +#endif + +#ifdef XNRG_10 + &Xnrg10, +#endif + +#ifdef XNRG_11 + &Xnrg11, +#endif + +#ifdef XNRG_12 + &Xnrg12, +#endif + +#ifdef XNRG_13 + &Xnrg13, +#endif + +#ifdef XNRG_14 + &Xnrg14, +#endif + +#ifdef XNRG_15 + &Xnrg15, +#endif + +#ifdef XNRG_16 + &Xnrg16 +#endif +}; + +const uint8_t xnrg_present = sizeof(xnrg_func_ptr) / sizeof(xnrg_func_ptr[0]); + +uint8_t xnrg_active = 0; + +bool XnrgCall(uint8_t function) +{ + DEBUG_TRACE_LOG(PSTR("NRG: %d"), function); + + if (FUNC_PRE_INIT == function) { + for (uint32_t x = 0; x < xnrg_present; x++) { + xnrg_func_ptr[x](function); + if (energy_flg) { + xnrg_active = x; + return true; + } + } + } + else if (energy_flg) { + return xnrg_func_ptr[xnrg_active](function); + } + return false; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_01_counter.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_01_counter.ino" +#ifdef USE_COUNTER + + + + +#define XSNS_01 1 + +#define D_PRFX_COUNTER "Counter" +#define D_CMND_COUNTERTYPE "Type" +#define D_CMND_COUNTERDEBOUNCE "Debounce" + +const char kCounterCommands[] PROGMEM = D_PRFX_COUNTER "|" + "|" D_CMND_COUNTERTYPE "|" D_CMND_COUNTERDEBOUNCE ; + +void (* const CounterCommand[])(void) PROGMEM = { + &CmndCounter, &CmndCounterType, &CmndCounterDebounce }; + +struct COUNTER { + uint32_t timer[MAX_COUNTERS]; + uint8_t no_pullup = 0; + bool any_counter = false; +} Counter; + +#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +void CounterUpdate(uint8_t index) ICACHE_RAM_ATTR; +void CounterUpdate1(void) ICACHE_RAM_ATTR; +void CounterUpdate2(void) ICACHE_RAM_ATTR; +void CounterUpdate3(void) ICACHE_RAM_ATTR; +void CounterUpdate4(void) ICACHE_RAM_ATTR; +#endif + +void CounterUpdate(uint8_t index) +{ + uint32_t time = micros(); + uint32_t debounce_time = time - Counter.timer[index]; + if (debounce_time > Settings.pulse_counter_debounce * 1000) { + Counter.timer[index] = time; + if (bitRead(Settings.pulse_counter_type, index)) { + RtcSettings.pulse_counter[index] = debounce_time; + } else { + RtcSettings.pulse_counter[index]++; + } + } +} + +void CounterUpdate1(void) +{ + CounterUpdate(0); +} + +void CounterUpdate2(void) +{ + CounterUpdate(1); +} + +void CounterUpdate3(void) +{ + CounterUpdate(2); +} + +void CounterUpdate4(void) +{ + CounterUpdate(3); +} + + + +bool CounterPinState(void) +{ + if ((XdrvMailbox.index >= GPIO_CNTR1_NP) && (XdrvMailbox.index < (GPIO_CNTR1_NP + MAX_COUNTERS))) { + bitSet(Counter.no_pullup, XdrvMailbox.index - GPIO_CNTR1_NP); + XdrvMailbox.index -= (GPIO_CNTR1_NP - GPIO_CNTR1); + return true; + } + return false; +} + +void CounterInit(void) +{ + typedef void (*function) () ; + function counter_callbacks[] = { CounterUpdate1, CounterUpdate2, CounterUpdate3, CounterUpdate4 }; + + for (uint32_t i = 0; i < MAX_COUNTERS; i++) { + if (pin[GPIO_CNTR1 +i] < 99) { + Counter.any_counter = true; + pinMode(pin[GPIO_CNTR1 +i], bitRead(Counter.no_pullup, i) ? INPUT : INPUT_PULLUP); + attachInterrupt(pin[GPIO_CNTR1 +i], counter_callbacks[i], FALLING); + } + } +} + +void CounterEverySecond(void) +{ + for (uint32_t i = 0; i < MAX_COUNTERS; i++) { + if (pin[GPIO_CNTR1 +i] < 99) { + if (bitRead(Settings.pulse_counter_type, i)) { + uint32_t time = micros() - Counter.timer[i]; + if (time > 4200000000) { + RtcSettings.pulse_counter[i] = 4200000000; + } + } + } + } +} + +void CounterSaveState(void) +{ + for (uint32_t i = 0; i < MAX_COUNTERS; i++) { + if (pin[GPIO_CNTR1 +i] < 99) { + Settings.pulse_counter[i] = RtcSettings.pulse_counter[i]; + } + } +} + +void CounterShow(bool json) +{ + bool header = false; + uint8_t dsxflg = 0; + for (uint32_t i = 0; i < MAX_COUNTERS; i++) { + if (pin[GPIO_CNTR1 +i] < 99) { + char counter[33]; + if (bitRead(Settings.pulse_counter_type, i)) { + dtostrfd((double)RtcSettings.pulse_counter[i] / 1000000, 6, counter); + } else { + dsxflg++; + snprintf_P(counter, sizeof(counter), PSTR("%lu"), RtcSettings.pulse_counter[i]); + } + + if (json) { + if (!header) { + ResponseAppend_P(PSTR(",\"COUNTER\":{")); + } + ResponseAppend_P(PSTR("%s\"C%d\":%s"), (header)?",":"", i +1, counter); + header = true; +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (1 == dsxflg)) { + DomoticzSensor(DZ_COUNT, RtcSettings.pulse_counter[i]); + dsxflg++; + } +#endif + if ((0 == tele_period ) && (Settings.flag3.counter_reset_on_tele)) { + RtcSettings.pulse_counter[i] = 0; + } +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(PSTR("{s}" D_COUNTER "%d{m}%s%s{e}"), + i +1, counter, (bitRead(Settings.pulse_counter_type, i)) ? " " D_UNIT_SECOND : ""); +#endif + } + } + } + if (header) { + ResponseJsonEnd(); + } +} + + + + + +void CmndCounter(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) { + if ((XdrvMailbox.data_len > 0) && (pin[GPIO_CNTR1 + XdrvMailbox.index -1] < 99)) { + if ((XdrvMailbox.data[0] == '-') || (XdrvMailbox.data[0] == '+')) { + RtcSettings.pulse_counter[XdrvMailbox.index -1] += XdrvMailbox.payload; + Settings.pulse_counter[XdrvMailbox.index -1] += XdrvMailbox.payload; + } else { + RtcSettings.pulse_counter[XdrvMailbox.index -1] = XdrvMailbox.payload; + Settings.pulse_counter[XdrvMailbox.index -1] = XdrvMailbox.payload; + } + } + ResponseCmndIdxNumber(RtcSettings.pulse_counter[XdrvMailbox.index -1]); + } +} + +void CmndCounterType(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1) && (pin[GPIO_CNTR1 + XdrvMailbox.index -1] < 99)) { + bitWrite(Settings.pulse_counter_type, XdrvMailbox.index -1, XdrvMailbox.payload &1); + RtcSettings.pulse_counter[XdrvMailbox.index -1] = 0; + Settings.pulse_counter[XdrvMailbox.index -1] = 0; + } + ResponseCmndIdxNumber(bitRead(Settings.pulse_counter_type, XdrvMailbox.index -1)); + } +} + +void CmndCounterDebounce(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) { + Settings.pulse_counter_debounce = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings.pulse_counter_debounce); +} + + + + + +bool Xsns01(uint8_t function) +{ + bool result = false; + + if (Counter.any_counter) { + switch (function) { + case FUNC_EVERY_SECOND: + CounterEverySecond(); + break; + case FUNC_JSON_APPEND: + CounterShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + CounterShow(0); + break; +#endif + case FUNC_SAVE_BEFORE_RESTART: + case FUNC_SAVE_AT_MIDNIGHT: + CounterSaveState(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kCounterCommands, CounterCommand); + break; + } + } else { + switch (function) { + case FUNC_INIT: + CounterInit(); + break; + case FUNC_PIN_STATE: + result = CounterPinState(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_02_analog.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_02_analog.ino" +#ifndef USE_ADC_VCC + + + + +#define XSNS_02 2 + +#define TO_CELSIUS(x) ((x) - 273.15) +#define TO_KELVIN(x) ((x) + 273.15) + + +#define ANALOG_V33 3.3 +#define ANALOG_T0 TO_KELVIN(25.0) + + + + + +#define ANALOG_NTC_BRIDGE_RESISTANCE 32000 +#define ANALOG_NTC_RESISTANCE 10000 +#define ANALOG_NTC_B_COEFFICIENT 3350 + + + + + +#define ANALOG_LDR_BRIDGE_RESISTANCE 10000 +#define ANALOG_LDR_LUX_CALC_SCALAR 12518931 +#define ANALOG_LDR_LUX_CALC_EXPONENT -1.4050 +# 58 "S:/Development/Tasmota/tasmota/xsns_02_analog.ino" +#define ANALOG_CT_FLAGS 0 +#define ANALOG_CT_MULTIPLIER 2146 +#define ANALOG_CT_VOLTAGE 2300 + +#define CT_FLAG_ENERGY_RESET (1 << 0) + +struct { + float temperature = 0; + float current = 0; + float energy = 0; + uint32_t previous_millis = 0; + uint16_t last_value = 0; +} Adc; + +void AdcInit(void) +{ + if ((Settings.adc_param_type != my_adc0) || (Settings.adc_param1 > 1000000)) { + if (ADC0_TEMP == my_adc0) { + + Settings.adc_param_type = ADC0_TEMP; + Settings.adc_param1 = ANALOG_NTC_BRIDGE_RESISTANCE; + Settings.adc_param2 = ANALOG_NTC_RESISTANCE; + Settings.adc_param3 = ANALOG_NTC_B_COEFFICIENT * 10000; + } + else if (ADC0_LIGHT == my_adc0) { + Settings.adc_param_type = ADC0_LIGHT; + Settings.adc_param1 = ANALOG_LDR_BRIDGE_RESISTANCE; + Settings.adc_param2 = ANALOG_LDR_LUX_CALC_SCALAR; + Settings.adc_param3 = ANALOG_LDR_LUX_CALC_EXPONENT * 10000; + } + else if (ADC0_RANGE == my_adc0) { + Settings.adc_param_type = ADC0_RANGE; + Settings.adc_param1 = 0; + Settings.adc_param2 = 1023; + Settings.adc_param3 = 0; + Settings.adc_param4 = 100; + } + else if (ADC0_CT_POWER == my_adc0) { + Settings.adc_param_type = ADC0_CT_POWER; + Settings.adc_param1 = ANALOG_CT_FLAGS; + Settings.adc_param2 = ANALOG_CT_MULTIPLIER; + Settings.adc_param3 = ANALOG_CT_VOLTAGE; + } + } +} + +uint16_t AdcRead(uint8_t factor) +{ + + + + + + uint8_t samples = 1 << factor; + uint16_t analog = 0; + for (uint32_t i = 0; i < samples; i++) { + analog += analogRead(A0); + delay(1); + } + analog >>= factor; + return analog; +} + +#ifdef USE_RULES +void AdcEvery250ms(void) +{ + if (ADC0_INPUT == my_adc0) { + uint16_t new_value = AdcRead(5); + if ((new_value < Adc.last_value -10) || (new_value > Adc.last_value +10)) { + Adc.last_value = new_value; + uint16_t value = Adc.last_value / 10; + Response_P(PSTR("{\"ANALOG\":{\"A0div10\":%d}}"), (value > 99) ? 100 : value); + XdrvRulesProcess(); + } + } +} +#endif + +uint16_t AdcGetLux(void) +{ + int adc = AdcRead(2); + + double resistorVoltage = ((double)adc / 1023) * ANALOG_V33; + double ldrVoltage = ANALOG_V33 - resistorVoltage; + double ldrResistance = ldrVoltage / resistorVoltage * (double)Settings.adc_param1; + double ldrLux = (double)Settings.adc_param2 * FastPrecisePow(ldrResistance, (double)Settings.adc_param3 / 10000); + + return (uint16_t)ldrLux; +} + +uint16_t AdcGetRange(void) +{ + + + + int adc = AdcRead(2); + double adcrange = ( ((double)Settings.adc_param2 - (double)adc) / ( ((double)Settings.adc_param2 - (double)Settings.adc_param1)) * ((double)Settings.adc_param3 - (double)Settings.adc_param4) + (double)Settings.adc_param4 ); + return (uint16_t)adcrange; +} + +void AdcGetCurrentPower(uint8_t factor) +{ + + + + + + uint8_t samples = 1 << factor; + uint16_t analog = 0; + uint16_t analog_min = 1023; + uint16_t analog_max = 0; + for (uint32_t i = 0; i < samples; i++) { + analog = analogRead(A0); + if (analog < analog_min) { + analog_min = analog; + } + if (analog > analog_max) { + analog_max = analog; + } + delay(1); + } + + Adc.current = (float)(analog_max-analog_min) * ((float)(Settings.adc_param2) / 100000); + float power = Adc.current * (float)(Settings.adc_param3) / 10; + uint32_t current_millis = millis(); + Adc.energy = Adc.energy + ((power * (current_millis - Adc.previous_millis)) / 3600000000); + Adc.previous_millis = current_millis; +} + +void AdcEverySecond(void) +{ + if (ADC0_TEMP == my_adc0) { + int adc = AdcRead(2); + + double Rt = (adc * Settings.adc_param1) / (1024.0 * ANALOG_V33 - (double)adc); + double BC = (double)Settings.adc_param3 / 10000; + double T = BC / (BC / ANALOG_T0 + TaylorLog(Rt / (double)Settings.adc_param2)); + Adc.temperature = ConvertTemp(TO_CELSIUS(T)); + } + else if (ADC0_CT_POWER == my_adc0) { + AdcGetCurrentPower(5); + } +} + +void AdcShow(bool json) +{ + if (ADC0_INPUT == my_adc0) { + uint16_t analog = AdcRead(5); + + if (json) { + ResponseAppend_P(PSTR(",\"ANALOG\":{\"A0\":%d}"), analog); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_ANALOG, "", 0, analog); +#endif + } + } + + else if (ADC0_TEMP == my_adc0) { + char temperature[33]; + dtostrfd(Adc.temperature, Settings.flag2.temperature_resolution, temperature); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMP, "ANALOG", temperature); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_TEMP, temperature); + } +#endif +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, Adc.temperature); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, "", temperature, TempUnit()); +#endif + } + } + + else if (ADC0_LIGHT == my_adc0) { + uint16_t adc_light = AdcGetLux(); + + if (json) { + ResponseAppend_P(JSON_SNS_ILLUMINANCE, "ANALOG", adc_light); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_ILLUMINANCE, adc_light); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, "", adc_light); +#endif + } + } + + else if (ADC0_RANGE == my_adc0) { + uint16_t adc_range = AdcGetRange(); + + if (json) { + ResponseAppend_P(JSON_SNS_RANGE, "ANALOG", adc_range); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_RANGE, "", adc_range); +#endif + } + } + + else if (ADC0_CT_POWER == my_adc0) { + AdcGetCurrentPower(5); + + float voltage = (float)(Settings.adc_param3) / 10; + char voltage_chr[FLOATSZ]; + dtostrfd(voltage, Settings.flag2.voltage_resolution, voltage_chr); + char current_chr[FLOATSZ]; + dtostrfd(Adc.current, Settings.flag2.current_resolution, current_chr); + char power_chr[FLOATSZ]; + dtostrfd(voltage * Adc.current, Settings.flag2.wattage_resolution, power_chr); + char energy_chr[FLOATSZ]; + dtostrfd(Adc.energy, Settings.flag2.energy_resolution, energy_chr); + + if (json) { + ResponseAppend_P(PSTR(",\"ANALOG\":{\"" D_JSON_ENERGY "\":%s,\"" D_JSON_POWERUSAGE "\":%s,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s}"), + energy_chr, power_chr, voltage_chr, current_chr); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_POWER_ENERGY, power_chr); + DomoticzSensor(DZ_VOLTAGE, voltage_chr); + DomoticzSensor(DZ_CURRENT, current_chr); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_VOLTAGE, voltage_chr); + WSContentSend_PD(HTTP_SNS_CURRENT, current_chr); + WSContentSend_PD(HTTP_SNS_POWER, power_chr); + WSContentSend_PD(HTTP_SNS_ENERGY_TOTAL, energy_chr); +#endif + } + } + +} + + + + + +const char kAdcCommands[] PROGMEM = "|" + D_CMND_ADC "|" D_CMND_ADCS "|" D_CMND_ADCPARAM; + +void (* const AdcCommand[])(void) PROGMEM = { + &CmndAdc, &CmndAdcs, &CmndAdcParam }; + +void CmndAdc(void) +{ + if (ValidAdc() && (XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < ADC0_END)) { + Settings.my_adc0 = XdrvMailbox.payload; + restart_flag = 2; + } + char stemp1[TOPSZ]; + Response_P(PSTR("{\"" D_CMND_ADC "0\":{\"%d\":\"%s\"}}"), Settings.my_adc0, GetTextIndexed(stemp1, sizeof(stemp1), Settings.my_adc0, kAdc0Names)); +} + +void CmndAdcs(void) +{ + Response_P(PSTR("{\"" D_CMND_ADCS "\":{")); + bool jsflg = false; + char stemp1[TOPSZ]; + for (uint32_t i = 0; i < ADC0_END; i++) { + if (jsflg) { + ResponseAppend_P(PSTR(",")); + } + jsflg = true; + ResponseAppend_P(PSTR("\"%d\":\"%s\""), i, GetTextIndexed(stemp1, sizeof(stemp1), i, kAdc0Names)); + } + ResponseJsonEndEnd(); +} + +void CmndAdcParam(void) +{ + if (XdrvMailbox.data_len) { + if ((ADC0_TEMP == XdrvMailbox.payload) || + (ADC0_LIGHT == XdrvMailbox.payload) || + (ADC0_RANGE == XdrvMailbox.payload) || + (ADC0_CT_POWER == XdrvMailbox.payload)) { + if (strstr(XdrvMailbox.data, ",") != nullptr) { + char sub_string[XdrvMailbox.data_len +1]; + + + + Settings.adc_param_type = XdrvMailbox.payload; + Settings.adc_param1 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); + Settings.adc_param2 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 3), nullptr, 10); + if (ADC0_RANGE == XdrvMailbox.payload) { + Settings.adc_param3 = abs(strtol(subStr(sub_string, XdrvMailbox.data, ",", 4), nullptr, 10)); + Settings.adc_param4 = abs(strtol(subStr(sub_string, XdrvMailbox.data, ",", 5), nullptr, 10)); + } else { + Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000); + } + if (ADC0_CT_POWER == XdrvMailbox.payload) { + if ((Settings.adc_param1 & CT_FLAG_ENERGY_RESET) > 0) { + Adc.energy = 0; + Settings.adc_param1 ^= CT_FLAG_ENERGY_RESET; + } + } + } else { + + + + + Settings.adc_param_type = 0; + AdcInit(); + } + } + } + + + Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d"), Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2); + if (ADC0_RANGE == my_adc0) { + ResponseAppend_P(PSTR(",%d,%d"), Settings.adc_param3, Settings.adc_param4); + } else { + int value = Settings.adc_param3; + uint8_t precision; + for (precision = 4; precision > 0; precision--) { + if (value % 10) { break; } + value /= 10; + } + char param3[33]; + dtostrfd(((double)Settings.adc_param3)/10000, precision, param3); + ResponseAppend_P(PSTR(",%s"), param3); + } + ResponseAppend_P(PSTR("]}")); +} + + + + + +bool Xsns02(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_COMMAND: + result = DecodeCommand(kAdcCommands, AdcCommand); + break; + default: + if ((ADC0_INPUT == my_adc0) || + (ADC0_TEMP == my_adc0) || + (ADC0_LIGHT == my_adc0) || + (ADC0_RANGE == my_adc0) || + (ADC0_CT_POWER == my_adc0)) { + switch (function) { +#ifdef USE_RULES + case FUNC_EVERY_250_MSECOND: + AdcEvery250ms(); + break; +#endif + case FUNC_EVERY_SECOND: + AdcEverySecond(); + break; + case FUNC_INIT: + AdcInit(); + break; + case FUNC_JSON_APPEND: + AdcShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + AdcShow(0); + break; +#endif + } + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_04_snfsc.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_04_snfsc.ino" +#ifdef USE_SONOFF_SC +# 57 "S:/Development/Tasmota/tasmota/xsns_04_snfsc.ino" +#define XSNS_04 4 + +uint16_t sc_value[5] = { 0 }; + +void SonoffScSend(const char *data) +{ + Serial.write(data); + Serial.write('\x1B'); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_SERIAL D_TRANSMIT " %s"), data); +} + +void SonoffScInit(void) +{ + + SonoffScSend("AT+START"); + +} + +void SonoffScSerialInput(char *rcvstat) +{ + char *p; + char *str; + uint16_t value[5] = { 0 }; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_SERIAL D_RECEIVED " %s"), rcvstat); + + if (!strncasecmp_P(rcvstat, PSTR("AT+UPDATE="), 10)) { + int8_t i = -1; + for (str = strtok_r(rcvstat, ":", &p); str && i < 5; str = strtok_r(nullptr, ":", &p)) { + value[i++] = atoi(str); + } + if (value[0] > 0) { + for (uint32_t i = 0; i < 5; i++) { + sc_value[i] = value[i]; + } + sc_value[2] = (11 - sc_value[2]) * 10; + sc_value[3] *= 10; + sc_value[4] = (11 - sc_value[4]) * 10; + SonoffScSend("AT+SEND=ok"); + } else { + SonoffScSend("AT+SEND=fail"); + } + } + else if (!strcasecmp_P(rcvstat, PSTR("AT+STATUS?"))) { + SonoffScSend("AT+STATUS=4"); + } +} + + + +#ifdef USE_WEBSERVER +const char HTTP_SNS_SCPLUS[] PROGMEM = + "{s}" D_LIGHT "{m}%d%%{e}{s}" D_NOISE "{m}%d%%{e}{s}" D_AIR_QUALITY "{m}%d%%{e}"; +#endif + +void SonoffScShow(bool json) +{ + if (sc_value[0] > 0) { + float t = ConvertTemp(sc_value[1]); + float h = ConvertHumidity(sc_value[0]); + + char temperature[33]; + dtostrfd(t, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(h, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(PSTR(",\"SonoffSC\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"" D_JSON_LIGHT "\":%d,\"" D_JSON_NOISE "\":%d,\"" D_JSON_AIRQUALITY "\":%d}"), + temperature, humidity, sc_value[2], sc_value[3], sc_value[4]); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzTempHumSensor(temperature, humidity); + DomoticzSensor(DZ_ILLUMINANCE, sc_value[2]); + DomoticzSensor(DZ_COUNT, sc_value[3]); + DomoticzSensor(DZ_AIRQUALITY, 500 + ((100 - sc_value[4]) * 20)); + } +#endif + +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, t); + KnxSensor(KNX_HUMIDITY, h); + } +#endif + +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, "", temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, "", humidity); + WSContentSend_PD(HTTP_SNS_SCPLUS, sc_value[2], sc_value[3], sc_value[4]); +#endif + } + } +} + + + + + +bool Xsns04(uint8_t function) +{ + bool result = false; + + if (SONOFF_SC == my_module_type) { + switch (function) { + case FUNC_JSON_APPEND: + SonoffScShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + SonoffScShow(0); + break; +#endif + case FUNC_INIT: + SonoffScInit(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_05_ds18x20.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_05_ds18x20.ino" +#ifdef USE_DS18x20 + + + + +#define XSNS_05 5 + + + +#define DS18S20_CHIPID 0x10 +#define DS1822_CHIPID 0x22 +#define DS18B20_CHIPID 0x28 +#define MAX31850_CHIPID 0x3B + +#define W1_SKIP_ROM 0xCC +#define W1_CONVERT_TEMP 0x44 +#define W1_WRITE_EEPROM 0x48 +#define W1_WRITE_SCRATCHPAD 0x4E +#define W1_READ_SCRATCHPAD 0xBE + +#define DS18X20_MAX_SENSORS 8 + +const char kDs18x20Types[] PROGMEM = "DS18x20|DS18S20|DS1822|DS18B20|MAX31850"; + +uint8_t ds18x20_chipids[] = { 0, DS18S20_CHIPID, DS1822_CHIPID, DS18B20_CHIPID, MAX31850_CHIPID }; + +struct DS18X20STRUCT { + uint8_t address[8]; + uint8_t index; + uint8_t valid; + float temperature; +} ds18x20_sensor[DS18X20_MAX_SENSORS]; +uint8_t ds18x20_sensors = 0; +uint8_t ds18x20_pin = 0; +uint8_t ds18x20_pin_out = 0; +bool ds18x20_dual_mode = false; +char ds18x20_types[12]; +#ifdef W1_PARASITE_POWER +uint8_t ds18x20_sensor_curr = 0; +unsigned long w1_power_until = 0; +#endif + + + + + +#define W1_MATCH_ROM 0x55 +#define W1_SEARCH_ROM 0xF0 + +uint8_t onewire_last_discrepancy = 0; +uint8_t onewire_last_family_discrepancy = 0; +bool onewire_last_device_flag = false; +unsigned char onewire_rom_id[8] = { 0 }; + + + +uint8_t OneWireReset(void) +{ + uint8_t retries = 125; + + if (!ds18x20_dual_mode) { + pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT); + do { + if (--retries == 0) { + return 0; + } + delayMicroseconds(2); + } while (!digitalRead(ds18x20_pin)); + pinMode(ds18x20_pin, OUTPUT); + digitalWrite(ds18x20_pin, LOW); + delayMicroseconds(480); + pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT); + } else { + digitalWrite(ds18x20_pin_out, HIGH); + do { + if (--retries == 0) { + return 0; + } + delayMicroseconds(2); + } while (!digitalRead(ds18x20_pin)); + digitalWrite(ds18x20_pin_out, LOW); + delayMicroseconds(480); + digitalWrite(ds18x20_pin_out, HIGH); + } + delayMicroseconds(70); + uint8_t r = !digitalRead(ds18x20_pin); + delayMicroseconds(410); + return r; +} + +void OneWireWriteBit(uint8_t v) +{ + static const uint8_t delay_low[2] = { 65, 10 }; + static const uint8_t delay_high[2] = { 5, 55 }; + + v &= 1; + if (!ds18x20_dual_mode) { + digitalWrite(ds18x20_pin, LOW); + pinMode(ds18x20_pin, OUTPUT); + delayMicroseconds(delay_low[v]); + digitalWrite(ds18x20_pin, HIGH); + } else { + digitalWrite(ds18x20_pin_out, LOW); + delayMicroseconds(delay_low[v]); + digitalWrite(ds18x20_pin_out, HIGH); + } + delayMicroseconds(delay_high[v]); +} +# 150 "S:/Development/Tasmota/tasmota/xsns_05_ds18x20.ino" +void OneWireReadBit1(void) +{ + pinMode(ds18x20_pin, OUTPUT); + digitalWrite(ds18x20_pin, LOW); + delayMicroseconds(3); + pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT); +} + +void OneWireReadBit2(void) +{ + digitalWrite(ds18x20_pin_out, LOW); + delayMicroseconds(3); + digitalWrite(ds18x20_pin_out, HIGH); +} + +uint8_t OneWireReadBit(void) +{ + if (!ds18x20_dual_mode) { + OneWireReadBit1(); + } else { + OneWireReadBit2(); + } + delayMicroseconds(10); + uint8_t r = digitalRead(ds18x20_pin); + delayMicroseconds(53); + return r; +} + + + +void OneWireWrite(uint8_t v) +{ + for (uint8_t bit_mask = 0x01; bit_mask; bit_mask <<= 1) { + OneWireWriteBit((bit_mask & v) ? 1 : 0); + } +} + +uint8_t OneWireRead(void) +{ + uint8_t r = 0; + + for (uint8_t bit_mask = 0x01; bit_mask; bit_mask <<= 1) { + if (OneWireReadBit()) { + r |= bit_mask; + } + } + return r; +} + +void OneWireSelect(const uint8_t rom[8]) +{ + OneWireWrite(W1_MATCH_ROM); + for (uint32_t i = 0; i < 8; i++) { + OneWireWrite(rom[i]); + } +} + +void OneWireResetSearch(void) +{ + onewire_last_discrepancy = 0; + onewire_last_device_flag = false; + onewire_last_family_discrepancy = 0; + for (uint32_t i = 0; i < 8; i++) { + onewire_rom_id[i] = 0; + } +} + +uint8_t OneWireSearch(uint8_t *newAddr) +{ + uint8_t id_bit_number = 1; + uint8_t last_zero = 0; + uint8_t rom_byte_number = 0; + uint8_t search_result = 0; + uint8_t id_bit; + uint8_t cmp_id_bit; + unsigned char rom_byte_mask = 1; + unsigned char search_direction; + + if (!onewire_last_device_flag) { + if (!OneWireReset()) { + onewire_last_discrepancy = 0; + onewire_last_device_flag = false; + onewire_last_family_discrepancy = 0; + return false; + } + OneWireWrite(W1_SEARCH_ROM); + do { + id_bit = OneWireReadBit(); + cmp_id_bit = OneWireReadBit(); + + if ((id_bit == 1) && (cmp_id_bit == 1)) { + break; + } else { + if (id_bit != cmp_id_bit) { + search_direction = id_bit; + } else { + if (id_bit_number < onewire_last_discrepancy) { + search_direction = ((onewire_rom_id[rom_byte_number] & rom_byte_mask) > 0); + } else { + search_direction = (id_bit_number == onewire_last_discrepancy); + } + if (search_direction == 0) { + last_zero = id_bit_number; + if (last_zero < 9) { + onewire_last_family_discrepancy = last_zero; + } + } + } + if (search_direction == 1) { + onewire_rom_id[rom_byte_number] |= rom_byte_mask; + } else { + onewire_rom_id[rom_byte_number] &= ~rom_byte_mask; + } + OneWireWriteBit(search_direction); + id_bit_number++; + rom_byte_mask <<= 1; + if (rom_byte_mask == 0) { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } while (rom_byte_number < 8); + if (!(id_bit_number < 65)) { + onewire_last_discrepancy = last_zero; + if (onewire_last_discrepancy == 0) { + onewire_last_device_flag = true; + } + search_result = true; + } + } + if (!search_result || !onewire_rom_id[0]) { + onewire_last_discrepancy = 0; + onewire_last_device_flag = false; + onewire_last_family_discrepancy = 0; + search_result = false; + } + for (uint32_t i = 0; i < 8; i++) { + newAddr[i] = onewire_rom_id[i]; + } + return search_result; +} + +bool OneWireCrc8(uint8_t *addr) +{ + uint8_t crc = 0; + uint8_t len = 8; + + while (len--) { + uint8_t inbyte = *addr++; + for (uint32_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) { + crc ^= 0x8C; + } + inbyte >>= 1; + } + } + return (crc == *addr); +} + + + +void Ds18x20Init(void) +{ + uint64_t ids[DS18X20_MAX_SENSORS]; + + ds18x20_pin = pin[GPIO_DSB]; + + if (pin[GPIO_DSB_OUT] < 99) { + ds18x20_pin_out = pin[GPIO_DSB_OUT]; + ds18x20_dual_mode = true; + pinMode(ds18x20_pin_out, OUTPUT); + pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT); + } + + OneWireResetSearch(); + + ds18x20_sensors = 0; + while (ds18x20_sensors < DS18X20_MAX_SENSORS) { + if (!OneWireSearch(ds18x20_sensor[ds18x20_sensors].address)) { + break; + } + if (OneWireCrc8(ds18x20_sensor[ds18x20_sensors].address) && + ((ds18x20_sensor[ds18x20_sensors].address[0] == DS18S20_CHIPID) || + (ds18x20_sensor[ds18x20_sensors].address[0] == DS1822_CHIPID) || + (ds18x20_sensor[ds18x20_sensors].address[0] == DS18B20_CHIPID) || + (ds18x20_sensor[ds18x20_sensors].address[0] == MAX31850_CHIPID))) { + ds18x20_sensor[ds18x20_sensors].index = ds18x20_sensors; + ids[ds18x20_sensors] = ds18x20_sensor[ds18x20_sensors].address[0]; + for (uint32_t j = 6; j > 0; j--) { + ids[ds18x20_sensors] = ids[ds18x20_sensors] << 8 | ds18x20_sensor[ds18x20_sensors].address[j]; + } + ds18x20_sensors++; + } + } + for (uint32_t i = 0; i < ds18x20_sensors; i++) { + for (uint32_t j = i + 1; j < ds18x20_sensors; j++) { + if (ids[ds18x20_sensor[i].index] > ids[ds18x20_sensor[j].index]) { + std::swap(ds18x20_sensor[i].index, ds18x20_sensor[j].index); + } + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DSB D_SENSORS_FOUND " %d"), ds18x20_sensors); +} + +void Ds18x20Convert(void) +{ + OneWireReset(); +#ifdef W1_PARASITE_POWER + + if (++ds18x20_sensor_curr >= ds18x20_sensors) + ds18x20_sensor_curr = 0; + OneWireSelect(ds18x20_sensor[ds18x20_sensor_curr].address); +#else + OneWireWrite(W1_SKIP_ROM); +#endif + OneWireWrite(W1_CONVERT_TEMP); + +} + +bool Ds18x20Read(uint8_t sensor) +{ + uint8_t data[9]; + int8_t sign = 1; + + uint8_t index = ds18x20_sensor[sensor].index; + if (ds18x20_sensor[index].valid) { ds18x20_sensor[index].valid--; } + for (uint32_t retry = 0; retry < 3; retry++) { + OneWireReset(); + OneWireSelect(ds18x20_sensor[index].address); + OneWireWrite(W1_READ_SCRATCHPAD); + for (uint32_t i = 0; i < 9; i++) { + data[i] = OneWireRead(); + } + if (OneWireCrc8(data)) { + switch(ds18x20_sensor[index].address[0]) { + case DS18S20_CHIPID: { + if (data[1] > 0x80) { + data[0] = (~data[0]) +1; + sign = -1; + } + float temp9 = (float)(data[0] >> 1) * sign; + ds18x20_sensor[index].temperature = ConvertTemp((temp9 - 0.25) + ((16.0 - data[6]) / 16.0)); + ds18x20_sensor[index].valid = SENSOR_MAX_MISS; + return true; + } + case DS1822_CHIPID: + case DS18B20_CHIPID: { + if (data[4] != 0x7F) { + data[4] = 0x7F; + OneWireReset(); + OneWireSelect(ds18x20_sensor[index].address); + OneWireWrite(W1_WRITE_SCRATCHPAD); + OneWireWrite(data[2]); + OneWireWrite(data[3]); + OneWireWrite(data[4]); + OneWireSelect(ds18x20_sensor[index].address); + OneWireWrite(W1_WRITE_EEPROM); +#ifdef W1_PARASITE_POWER + w1_power_until = millis() + 10; +#endif + } + uint16_t temp12 = (data[1] << 8) + data[0]; + if (temp12 > 2047) { + temp12 = (~temp12) +1; + sign = -1; + } + ds18x20_sensor[index].temperature = ConvertTemp(sign * temp12 * 0.0625); + ds18x20_sensor[index].valid = SENSOR_MAX_MISS; + return true; + } + case MAX31850_CHIPID: { + int16_t temp14 = (data[1] << 8) + (data[0] & 0xFC); + ds18x20_sensor[index].temperature = ConvertTemp(temp14 * 0.0625); + ds18x20_sensor[index].valid = SENSOR_MAX_MISS; + return true; + } + } + } + } + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DSB D_SENSOR_CRC_ERROR)); + return false; +} + +void Ds18x20Name(uint8_t sensor) +{ + uint8_t index = sizeof(ds18x20_chipids); + while (index) { + if (ds18x20_sensor[ds18x20_sensor[sensor].index].address[0] == ds18x20_chipids[index]) { + break; + } + index--; + } + GetTextIndexed(ds18x20_types, sizeof(ds18x20_types), index, kDs18x20Types); + if (ds18x20_sensors > 1) { + snprintf_P(ds18x20_types, sizeof(ds18x20_types), PSTR("%s%c%d"), ds18x20_types, IndexSeparator(), sensor +1); + } +} + + + +void Ds18x20EverySecond(void) +{ + if (!ds18x20_sensors) { return; } + +#ifdef W1_PARASITE_POWER + + unsigned long now = millis(); + if (now < w1_power_until) + return; +#endif + if (uptime & 1 +#ifdef W1_PARASITE_POWER + + || ds18x20_sensors >= 2 +#endif + ) { + + Ds18x20Convert(); + } else { + for (uint32_t i = 0; i < ds18x20_sensors; i++) { + + if (!Ds18x20Read(i)) { + Ds18x20Name(i); + AddLogMissed(ds18x20_types, ds18x20_sensor[ds18x20_sensor[i].index].valid); +#ifdef USE_DS18x20_RECONFIGURE + if (!ds18x20_sensor[ds18x20_sensor[i].index].valid) { + memset(&ds18x20_sensor, 0, sizeof(ds18x20_sensor)); + Ds18x20Init(); + } +#endif + } + } + } +} + +void Ds18x20Show(bool json) +{ + for (uint32_t i = 0; i < ds18x20_sensors; i++) { + uint8_t index = ds18x20_sensor[i].index; + + if (ds18x20_sensor[index].valid) { + char temperature[33]; + dtostrfd(ds18x20_sensor[index].temperature, Settings.flag2.temperature_resolution, temperature); + + Ds18x20Name(i); + + if (json) { + char address[17]; + for (uint32_t j = 0; j < 6; j++) { + sprintf(address+2*j, "%02X", ds18x20_sensor[index].address[6-j]); + } + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ID "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%s}"), ds18x20_types, address, temperature); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzSensor(DZ_TEMP, temperature); + } +#endif +#ifdef USE_KNX + if ((0 == tele_period) && (0 == i)) { + KnxSensor(KNX_TEMPERATURE, ds18x20_sensor[index].temperature); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, ds18x20_types, temperature, TempUnit()); +#endif + } + } + } +} + + + + + +bool Xsns05(uint8_t function) +{ + bool result = false; + + if (pin[GPIO_DSB] < 99) { + switch (function) { + case FUNC_INIT: + Ds18x20Init(); + break; + case FUNC_EVERY_SECOND: + Ds18x20EverySecond(); + break; + case FUNC_JSON_APPEND: + Ds18x20Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Ds18x20Show(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_06_dht_old.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_06_dht_old.ino" +#ifdef USE_DHT_OLD +# 29 "S:/Development/Tasmota/tasmota/xsns_06_dht_old.ino" +#define XSNS_06 6 + +#define DHT_MAX_SENSORS 4 +#define DHT_MAX_RETRY 8 + +uint32_t dht_max_cycles; +uint8_t dht_data[5]; +uint8_t dht_sensors = 0; +uint8_t dht_pin_out = 0; +bool dht_active = true; +bool dht_dual_mode = false; + +struct DHTSTRUCT { + uint8_t pin; + uint8_t type; + char stype[12]; + uint32_t lastreadtime; + uint8_t lastresult; + float t = NAN; + float h = NAN; +} Dht[DHT_MAX_SENSORS]; + +void DhtReadPrep(void) +{ + for (uint32_t i = 0; i < dht_sensors; i++) { + if (!dht_dual_mode) { + digitalWrite(Dht[i].pin, HIGH); + } else { + digitalWrite(dht_pin_out, HIGH); + } + } +} + +int32_t DhtExpectPulse(uint8_t sensor, bool level) +{ + int32_t count = 0; + + while (digitalRead(Dht[sensor].pin) == level) { + if (count++ >= (int32_t)dht_max_cycles) { + return -1; + } + } + return count; +} + +bool DhtRead(uint8_t sensor) +{ + int32_t cycles[80]; + uint8_t error = 0; + + dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; + + + + + if (Dht[sensor].lastresult > DHT_MAX_RETRY) { + Dht[sensor].lastresult = 0; + if (!dht_dual_mode) { + digitalWrite(Dht[sensor].pin, HIGH); + } else { + digitalWrite(dht_pin_out, HIGH); + } + delay(250); + } + if (!dht_dual_mode) { + pinMode(Dht[sensor].pin, OUTPUT); + digitalWrite(Dht[sensor].pin, LOW); + } else { + digitalWrite(dht_pin_out, LOW); + } + + if (GPIO_SI7021 == Dht[sensor].type) { + delayMicroseconds(500); + } else { + delay(20); + } + + noInterrupts(); + if (!dht_dual_mode) { + digitalWrite(Dht[sensor].pin, HIGH); + delayMicroseconds(40); + pinMode(Dht[sensor].pin, INPUT_PULLUP); + } else { + digitalWrite(dht_pin_out, HIGH); + delayMicroseconds(40); + } + delayMicroseconds(10); + if (-1 == DhtExpectPulse(sensor, LOW)) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE)); + error = 1; + } + else if (-1 == DhtExpectPulse(sensor, HIGH)) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE)); + error = 1; + } + else { + for (uint32_t i = 0; i < 80; i += 2) { + cycles[i] = DhtExpectPulse(sensor, LOW); + cycles[i+1] = DhtExpectPulse(sensor, HIGH); + } + } + interrupts(); + + if (error) { return false; } + + for (uint32_t i = 0; i < 40; ++i) { + int32_t lowCycles = cycles[2*i]; + int32_t highCycles = cycles[2*i+1]; + if ((-1 == lowCycles) || (-1 == highCycles)) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE)); + return false; + } + dht_data[i/8] <<= 1; + if (highCycles > lowCycles) { + dht_data[i / 8] |= 1; + } + } + + uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; + if (dht_data[4] != checksum) { + char hex_char[15]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), + ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); + return false; + } + + return true; +} + +void DhtReadTempHum(uint8_t sensor) +{ + if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) { + Dht[sensor].t = NAN; + Dht[sensor].h = NAN; + } + if (DhtRead(sensor)) { + switch (Dht[sensor].type) { + case GPIO_DHT11: + Dht[sensor].h = dht_data[0]; + Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f); + break; + case GPIO_DHT22: + case GPIO_SI7021: + Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1; + Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; + if (dht_data[2] & 0x80) { + Dht[sensor].t *= -1; + } + break; + } + Dht[sensor].t = ConvertTemp(Dht[sensor].t); + Dht[sensor].h = ConvertHumidity(Dht[sensor].h); + Dht[sensor].lastresult = 0; + } else { + Dht[sensor].lastresult++; + } +} + + + +bool DhtPinState() +{ + if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { + if (dht_sensors < DHT_MAX_SENSORS) { + Dht[dht_sensors].pin = XdrvMailbox.payload; + Dht[dht_sensors].type = XdrvMailbox.index; + dht_sensors++; + XdrvMailbox.index = GPIO_DHT11; + } else { + XdrvMailbox.index = 0; + } + return true; + } + return false; +} + +void DhtInit(void) +{ + if (dht_sensors) { + dht_max_cycles = microsecondsToClockCycles(1000); + + if (pin[GPIO_DHT11_OUT] < 99) { + dht_pin_out = pin[GPIO_DHT11_OUT]; + dht_dual_mode = true; + dht_sensors = 1; + pinMode(dht_pin_out, OUTPUT); + } + + for (uint32_t i = 0; i < dht_sensors; i++) { + pinMode(Dht[i].pin, INPUT_PULLUP); + Dht[i].lastreadtime = 0; + Dht[i].lastresult = 0; + GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); + if (dht_sensors > 1) { + snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_SENSORS_FOUND " %d"), dht_sensors); + } else { + dht_active = false; + } +} + +void DhtEverySecond(void) +{ + if (uptime &1) { + + DhtReadPrep(); + } else { + for (uint32_t i = 0; i < dht_sensors; i++) { + + DhtReadTempHum(i); + } + } +} + +void DhtShow(bool json) +{ + for (uint32_t i = 0; i < dht_sensors; i++) { + char temperature[33]; + dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if ((0 == tele_period) && (0 == i)) { + KnxSensor(KNX_TEMPERATURE, Dht[i].t); + KnxSensor(KNX_HUMIDITY, Dht[i].h); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); +#endif + } + } +} + + + + + +bool Xsns06(uint8_t function) +{ + bool result = false; + + if (dht_active) { + switch (function) { + case FUNC_EVERY_SECOND: + DhtEverySecond(); + break; + case FUNC_JSON_APPEND: + DhtShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + DhtShow(0); + break; +#endif + case FUNC_INIT: + DhtInit(); + break; + case FUNC_PIN_STATE: + result = DhtPinState(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_06_dht_v2.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_06_dht_v2.ino" +#ifdef USE_DHT_V2 +# 29 "S:/Development/Tasmota/tasmota/xsns_06_dht_v2.ino" +#define XSNS_06 6 + +#define DHT_MAX_SENSORS 4 +#define DHT_MAX_RETRY 8 + +uint32_t dht_max_cycles; +uint8_t dht_data[5]; +uint8_t dht_sensors = 0; +uint8_t dht_pin_out = 0; +bool dht_active = true; +bool dht_dual_mode = false; + +struct DHTSTRUCT { + uint8_t pin; + uint8_t type; + char stype[12]; + uint32_t lastreadtime; + uint8_t lastresult; + float t = NAN; + float h = NAN; +} Dht[DHT_MAX_SENSORS]; + +void DhtReadPrep(void) +{ + for (uint32_t i = 0; i < dht_sensors; i++) { + if (!dht_dual_mode) { + digitalWrite(Dht[i].pin, HIGH); + } else { + digitalWrite(dht_pin_out, HIGH); + } + } +} + +int32_t DhtExpectPulse(uint8_t sensor, bool level) +{ + int32_t count = 0; + + while (digitalRead(Dht[sensor].pin) == level) { + if (count++ >= (int32_t)dht_max_cycles) { + return -1; + } + } + return count; +} + +bool DhtRead(uint8_t sensor) +{ + int32_t cycles[80]; + uint8_t error = 0; + + dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; + + if (Dht[sensor].lastresult > DHT_MAX_RETRY) { + Dht[sensor].lastresult = 0; + if (!dht_dual_mode) { + digitalWrite(Dht[sensor].pin, HIGH); + } else { + digitalWrite(dht_pin_out, HIGH); + } + delay(250); + } + + + noInterrupts(); + if (!dht_dual_mode) { + pinMode(Dht[sensor].pin, OUTPUT); + digitalWrite(Dht[sensor].pin, LOW); + } else { + digitalWrite(dht_pin_out, LOW); + } + + switch (Dht[sensor].type) { + case GPIO_SI7021: +# 114 "S:/Development/Tasmota/tasmota/xsns_06_dht_v2.ino" + delayMicroseconds(500); + if (!dht_dual_mode) { + digitalWrite(Dht[sensor].pin, HIGH); + } else { + digitalWrite(dht_pin_out, HIGH); + } + delayMicroseconds(40); + break; + + case GPIO_DHT22: +# 133 "S:/Development/Tasmota/tasmota/xsns_06_dht_v2.ino" + delayMicroseconds(1100); + if (!dht_dual_mode) { + digitalWrite(Dht[sensor].pin, HIGH); + } else { + digitalWrite(dht_pin_out, HIGH); + } + delayMicroseconds(30); + break; + + case GPIO_DHT11: +# 151 "S:/Development/Tasmota/tasmota/xsns_06_dht_v2.ino" + default: + + delay(20); + if (!dht_dual_mode) { + digitalWrite(Dht[sensor].pin, HIGH); + } else { + digitalWrite(dht_pin_out, HIGH); + } + delayMicroseconds(30); + break; + } + + + pinMode(Dht[sensor].pin, INPUT_PULLUP); + + if (-1 == DhtExpectPulse(sensor, LOW)) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE)); + error = 1; + } + else if (-1 == DhtExpectPulse(sensor, HIGH)) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE)); + error = 1; + } + else { + for (uint32_t i = 0; i < 80; i += 2) { + cycles[i] = DhtExpectPulse(sensor, LOW); + cycles[i+1] = DhtExpectPulse(sensor, HIGH); + } + } + interrupts(); + if (error) { return false; } + + + for (uint32_t i = 0; i < 40; ++i) { + int32_t lowCycles = cycles[2*i]; + int32_t highCycles = cycles[2*i+1]; + if ((-1 == lowCycles) || (-1 == highCycles)) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE)); + return false; + } + dht_data[i/8] <<= 1; + if (highCycles > lowCycles) { + dht_data[i / 8] |= 1; + } + } + + + uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; + if (dht_data[4] != checksum) { + char hex_char[15]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), + ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); + return false; + } + + return true; +} + +void DhtReadTempHum(uint8_t sensor) +{ + if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) { + Dht[sensor].t = NAN; + Dht[sensor].h = NAN; + } + if (DhtRead(sensor)) { + switch (Dht[sensor].type) { + case GPIO_DHT11: + Dht[sensor].h = dht_data[0]; + Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f); + break; + case GPIO_DHT22: + case GPIO_SI7021: + Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1; + Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; + if (dht_data[2] & 0x80) { + Dht[sensor].t *= -1; + } + break; + } + Dht[sensor].t = ConvertTemp(Dht[sensor].t); + Dht[sensor].h = ConvertHumidity(Dht[sensor].h); + Dht[sensor].lastresult = 0; + } else { + Dht[sensor].lastresult++; + } +} + + + +bool DhtPinState() +{ + if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { + if (dht_sensors < DHT_MAX_SENSORS) { + Dht[dht_sensors].pin = XdrvMailbox.payload; + Dht[dht_sensors].type = XdrvMailbox.index; + dht_sensors++; + XdrvMailbox.index = GPIO_DHT11; + } else { + XdrvMailbox.index = 0; + } + return true; + } + return false; +} + +void DhtInit(void) +{ + if (dht_sensors) { + dht_max_cycles = microsecondsToClockCycles(1000); + + if (pin[GPIO_DHT11_OUT] < 99) { + dht_pin_out = pin[GPIO_DHT11_OUT]; + dht_dual_mode = true; + dht_sensors = 1; + pinMode(dht_pin_out, OUTPUT); + } + + for (uint32_t i = 0; i < dht_sensors; i++) { + pinMode(Dht[i].pin, INPUT_PULLUP); + Dht[i].lastreadtime = 0; + Dht[i].lastresult = 0; + GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); + if (dht_sensors > 1) { + snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v2) " D_SENSORS_FOUND " %d"), dht_sensors); + } else { + dht_active = false; + } +} + +void DhtEverySecond(void) +{ + if (uptime &1) { + + DhtReadPrep(); + } else { + for (uint32_t i = 0; i < dht_sensors; i++) { + + DhtReadTempHum(i); + } + } +} + +void DhtShow(bool json) +{ + for (uint32_t i = 0; i < dht_sensors; i++) { + char temperature[33]; + dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if ((0 == tele_period) && (0 == i)) { + KnxSensor(KNX_TEMPERATURE, Dht[i].t); + KnxSensor(KNX_HUMIDITY, Dht[i].h); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); +#endif + } + } +} + + + + + +bool Xsns06(uint8_t function) +{ + bool result = false; + + if (dht_active) { + switch (function) { + case FUNC_EVERY_SECOND: + DhtEverySecond(); + break; + case FUNC_JSON_APPEND: + DhtShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + DhtShow(0); + break; +#endif + case FUNC_INIT: + DhtInit(); + break; + case FUNC_PIN_STATE: + result = DhtPinState(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_06_dht_v3.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_06_dht_v3.ino" +#ifdef USE_DHT_V3 +# 30 "S:/Development/Tasmota/tasmota/xsns_06_dht_v3.ino" +#define XSNS_06 6 + +#define DHT_MAX_SENSORS 4 +#define DHT_MAX_RETRY 8 + +uint8_t dht_data[5]; +uint8_t dht_sensors = 0; +uint8_t dht_pin_out = 0; +bool dht_active = true; +bool dht_dual_mode = false; + +struct DHTSTRUCT { + uint8_t pin; + uint8_t type; + char stype[12]; + uint32_t lastreadtime; + uint8_t lastresult; + float t = NAN; + float h = NAN; +} Dht[DHT_MAX_SENSORS]; + +bool DhtExpectPulse(uint8_t sensor, int level) +{ + unsigned long timeout = micros() + 100; + while (digitalRead(Dht[sensor].pin) != level) { + if (micros() > timeout) { return false; } + delayMicroseconds(1); + } + return true; +} + +int DhtReadDat(uint8_t sensor) +{ + uint8_t result = 0; + for (uint32_t i = 0; i < 8; i++) { + if (!DhtExpectPulse(sensor, HIGH)) { return -1; } + + delayMicroseconds(35); + if (digitalRead(Dht[sensor].pin)) { + result |= (1 << (7 - i)); + } + + if (!DhtExpectPulse(sensor, LOW)) { return -1; } + } + return result; +} + +bool DhtRead(uint8_t sensor) +{ + dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; + + if (!dht_dual_mode) { + pinMode(Dht[sensor].pin, OUTPUT); + digitalWrite(Dht[sensor].pin, LOW); + } else { + digitalWrite(dht_pin_out, LOW); + } + + switch (Dht[sensor].type) { + case GPIO_DHT11: + delay(19); + break; + case GPIO_DHT22: + delay(2); + break; + case GPIO_SI7021: + delayMicroseconds(500); + break; + } + + if (!dht_dual_mode) { + pinMode(Dht[sensor].pin, INPUT_PULLUP); + } else { + digitalWrite(dht_pin_out, HIGH); + } + + switch (Dht[sensor].type) { + case GPIO_DHT11: + case GPIO_DHT22: + delayMicroseconds(50); + break; + case GPIO_SI7021: + + delayMicroseconds(20); + break; + } + + noInterrupts(); + if (!DhtExpectPulse(sensor, LOW)) { + interrupts(); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE)); + return false; + } + if (!DhtExpectPulse(sensor, HIGH)) { + interrupts(); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE)); + return false; + } + if (!DhtExpectPulse(sensor, LOW)) { + interrupts(); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE)); + return false; + } + + int data = 0; + for (uint32_t i = 0; i < 5; i++) { + data = DhtReadDat(sensor); + if (-1 == data) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE)); + break; + } + dht_data[i] = data; + } + interrupts(); + if (-1 == data) { return false; } + + uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; + if (dht_data[4] != checksum) { + char hex_char[15]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), + ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); + return false; + } + + return true; +} + +void DhtReadTempHum(uint8_t sensor) +{ + if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) { + Dht[sensor].t = NAN; + Dht[sensor].h = NAN; + } + if (DhtRead(sensor)) { + switch (Dht[sensor].type) { + case GPIO_DHT11: + Dht[sensor].h = dht_data[0]; + Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f); + break; + case GPIO_DHT22: + case GPIO_SI7021: + Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1; + Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; + if (dht_data[2] & 0x80) { + Dht[sensor].t *= -1; + } + break; + } + Dht[sensor].t = ConvertTemp(Dht[sensor].t); + Dht[sensor].h = ConvertHumidity(Dht[sensor].h); + Dht[sensor].lastresult = 0; + } else { + Dht[sensor].lastresult++; + } +} + + + +bool DhtPinState() +{ + if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { + if (dht_sensors < DHT_MAX_SENSORS) { + Dht[dht_sensors].pin = XdrvMailbox.payload; + Dht[dht_sensors].type = XdrvMailbox.index; + dht_sensors++; + XdrvMailbox.index = GPIO_DHT11; + } else { + XdrvMailbox.index = 0; + } + return true; + } + return false; +} + +void DhtInit(void) +{ + if (dht_sensors) { + if (pin[GPIO_DHT11_OUT] < 99) { + dht_pin_out = pin[GPIO_DHT11_OUT]; + dht_dual_mode = true; + dht_sensors = 1; + pinMode(dht_pin_out, OUTPUT); + } + + for (uint32_t i = 0; i < dht_sensors; i++) { + pinMode(Dht[i].pin, INPUT_PULLUP); + Dht[i].lastreadtime = 0; + Dht[i].lastresult = 0; + GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); + if (dht_sensors > 1) { + snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v3) " D_SENSORS_FOUND " %d"), dht_sensors); + } else { + dht_active = false; + } +} + +void DhtEverySecond(void) +{ + if (uptime &1) { + + + } else { + for (uint32_t i = 0; i < dht_sensors; i++) { + + DhtReadTempHum(i); + } + } +} + +void DhtShow(bool json) +{ + for (uint32_t i = 0; i < dht_sensors; i++) { + char temperature[33]; + dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if ((0 == tele_period) && (0 == i)) { + KnxSensor(KNX_TEMPERATURE, Dht[i].t); + KnxSensor(KNX_HUMIDITY, Dht[i].h); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); +#endif + } + } +} + + + + + +bool Xsns06(uint8_t function) +{ + bool result = false; + + if (dht_active) { + switch (function) { + case FUNC_EVERY_SECOND: + DhtEverySecond(); + break; + case FUNC_JSON_APPEND: + DhtShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + DhtShow(0); + break; +#endif + case FUNC_INIT: + DhtInit(); + break; + case FUNC_PIN_STATE: + result = DhtPinState(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_06_dht_v4.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_06_dht_v4.ino" +#ifdef USE_DHT_V4 +# 30 "S:/Development/Tasmota/tasmota/xsns_06_dht_v4.ino" +#define XSNS_06 6 + +#define DHT_MAX_SENSORS 4 +#define DHT_MAX_RETRY 8 + +uint8_t dht_data[5]; +uint8_t dht_sensors = 0; +uint8_t dht_pin_out = 0; +bool dht_active = true; +bool dht_dual_mode = false; + +struct DHTSTRUCT { + uint8_t pin; + uint8_t type; + char stype[12]; + uint32_t lastreadtime; + uint8_t lastresult; + float t = NAN; + float h = NAN; +} Dht[DHT_MAX_SENSORS]; + +bool DhtExpectPulse(uint32_t sensor, uint32_t level) +{ + unsigned long timeout = micros() + 100; + while (digitalRead(Dht[sensor].pin) != level) { + if (micros() > timeout) { return false; } + delayMicroseconds(1); + } + return true; +} + +bool DhtRead(uint32_t sensor) +{ + dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; + + if (!dht_dual_mode) { + pinMode(Dht[sensor].pin, OUTPUT); + digitalWrite(Dht[sensor].pin, LOW); + } else { + digitalWrite(dht_pin_out, LOW); + } + + switch (Dht[sensor].type) { + case GPIO_DHT11: + delay(19); + break; + case GPIO_DHT22: + delay(2); + break; + case GPIO_SI7021: + delayMicroseconds(500); + break; + } + + if (!dht_dual_mode) { + pinMode(Dht[sensor].pin, INPUT_PULLUP); + } else { + digitalWrite(dht_pin_out, HIGH); + } + + switch (Dht[sensor].type) { + case GPIO_DHT11: + case GPIO_DHT22: + delayMicroseconds(50); + break; + case GPIO_SI7021: + delayMicroseconds(20); + break; + } + + uint32_t level = 9; + noInterrupts(); + for (uint32_t i = 0; i < 3; i++) { + level = i &1; + if (!DhtExpectPulse(sensor, level)) { break; } + level = 9; + } + if (9 == level) { + int data = 0; + for (uint32_t i = 0; i < 5; i++) { + data = 0; + for (uint32_t j = 0; j < 8; j++) { + level = 1; + if (!DhtExpectPulse(sensor, level)) { break; } + + delayMicroseconds(35); + if (digitalRead(Dht[sensor].pin)) { + data |= (1 << (7 - j)); + } + + level = 0; + if (!DhtExpectPulse(sensor, level)) { break; } + level = 9; + } + if (level < 2) { break; } + + dht_data[i] = data; + } + } + interrupts(); + if (level < 2) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " %s " D_PULSE), (0 == level) ? D_START_SIGNAL_LOW : D_START_SIGNAL_HIGH); + return false; + } + + uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; + if (dht_data[4] != checksum) { + char hex_char[15]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), + ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); + return false; + } + + return true; +} + +void DhtReadTempHum(uint32_t sensor) +{ + if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) { + Dht[sensor].t = NAN; + Dht[sensor].h = NAN; + } + if (DhtRead(sensor)) { + switch (Dht[sensor].type) { + case GPIO_DHT11: + Dht[sensor].h = dht_data[0]; + Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f); + break; + case GPIO_DHT22: + case GPIO_SI7021: + Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1; + Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; + if (dht_data[2] & 0x80) { + Dht[sensor].t *= -1; + } + break; + } + Dht[sensor].t = ConvertTemp(Dht[sensor].t); + if (Dht[sensor].h > 100) { Dht[sensor].h = 100.0; } + if (Dht[sensor].h < 0) { Dht[sensor].h = 0.0; } + Dht[sensor].h = ConvertHumidity(Dht[sensor].h); + Dht[sensor].lastresult = 0; + } else { + Dht[sensor].lastresult++; + } +} + + + +bool DhtPinState() +{ + if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { + if (dht_sensors < DHT_MAX_SENSORS) { + Dht[dht_sensors].pin = XdrvMailbox.payload; + Dht[dht_sensors].type = XdrvMailbox.index; + dht_sensors++; + XdrvMailbox.index = GPIO_DHT11; + } else { + XdrvMailbox.index = 0; + } + return true; + } + return false; +} + +void DhtInit(void) +{ + if (dht_sensors) { + if (pin[GPIO_DHT11_OUT] < 99) { + dht_pin_out = pin[GPIO_DHT11_OUT]; + dht_dual_mode = true; + dht_sensors = 1; + pinMode(dht_pin_out, OUTPUT); + } + + for (uint32_t i = 0; i < dht_sensors; i++) { + pinMode(Dht[i].pin, INPUT_PULLUP); + Dht[i].lastreadtime = 0; + Dht[i].lastresult = 0; + GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); + if (dht_sensors > 1) { + snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v4) " D_SENSORS_FOUND " %d"), dht_sensors); + } else { + dht_active = false; + } +} + +void DhtEverySecond(void) +{ + if (uptime &1) { + } else { + for (uint32_t i = 0; i < dht_sensors; i++) { + + DhtReadTempHum(i); + } + } +} + +void DhtShow(bool json) +{ + for (uint32_t i = 0; i < dht_sensors; i++) { + char temperature[33]; + dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if ((0 == tele_period) && (0 == i)) { + KnxSensor(KNX_TEMPERATURE, Dht[i].t); + KnxSensor(KNX_HUMIDITY, Dht[i].h); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); +#endif + } + } +} + + + + + +bool Xsns06(uint8_t function) +{ + bool result = false; + + if (dht_active) { + switch (function) { + case FUNC_EVERY_SECOND: + DhtEverySecond(); + break; + case FUNC_JSON_APPEND: + DhtShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + DhtShow(0); + break; +#endif + case FUNC_INIT: + DhtInit(); + break; + case FUNC_PIN_STATE: + result = DhtPinState(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_06_dht_v5.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_06_dht_v5.ino" +#ifdef USE_DHT +# 30 "S:/Development/Tasmota/tasmota/xsns_06_dht_v5.ino" +#define XSNS_06 6 + +#define DHT_MAX_SENSORS 4 +#define DHT_MAX_RETRY 8 + +uint8_t dht_data[5]; +uint8_t dht_sensors = 0; +uint8_t dht_pin_out = 0; +bool dht_active = true; +bool dht_dual_mode = false; + +struct DHTSTRUCT { + uint8_t pin; + uint8_t type; + uint8_t lastresult; + char stype[12]; + float t = NAN; + float h = NAN; +} Dht[DHT_MAX_SENSORS]; + +bool DhtWaitState(uint32_t sensor, uint32_t level) +{ + unsigned long timeout = micros() + 100; + while (digitalRead(Dht[sensor].pin) != level) { + if (TimeReachedUsec(timeout)) { + PrepLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " %s " D_PULSE), + (level) ? D_START_SIGNAL_HIGH : D_START_SIGNAL_LOW); + return false; + } + delayMicroseconds(1); + } + return true; +} + +bool DhtRead(uint32_t sensor) +{ + dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; + + if (!dht_dual_mode) { + pinMode(Dht[sensor].pin, OUTPUT); + digitalWrite(Dht[sensor].pin, LOW); + } else { + digitalWrite(dht_pin_out, LOW); + } + + switch (Dht[sensor].type) { + case GPIO_DHT11: + delay(19); + break; + case GPIO_DHT22: + delay(2); + break; + case GPIO_SI7021: + delayMicroseconds(500); + break; + } + + if (!dht_dual_mode) { + pinMode(Dht[sensor].pin, INPUT_PULLUP); + } else { + digitalWrite(dht_pin_out, HIGH); + } + + switch (Dht[sensor].type) { + case GPIO_DHT11: + case GPIO_DHT22: + delayMicroseconds(50); + break; + case GPIO_SI7021: + delayMicroseconds(20); + break; + } +# 133 "S:/Development/Tasmota/tasmota/xsns_06_dht_v5.ino" + 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); + 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) { + char hex_char[15]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), + ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); + return false; + } + + float temperature = NAN; + float humidity = NAN; + switch (Dht[sensor].type) { + case GPIO_DHT11: + humidity = dht_data[0]; + temperature = dht_data[2] + ((float)dht_data[3] * 0.1f); + break; + case GPIO_DHT22: + case GPIO_SI7021: + humidity = ((dht_data[0] << 8) | dht_data[1]) * 0.1; + temperature = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; + if (dht_data[2] & 0x80) { + temperature *= -1; + } + break; + } + if (isnan(temperature) || isnan(humidity)) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "Invalid NAN reading")); + return false; + } + + if (humidity > 100) { humidity = 100.0; } + if (humidity < 0) { humidity = 0.1; } + Dht[sensor].h = ConvertHumidity(humidity); + Dht[sensor].t = ConvertTemp(temperature); + Dht[sensor].lastresult = 0; + + return true; +} + + + +bool DhtPinState() +{ + if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { + if (dht_sensors < DHT_MAX_SENSORS) { + Dht[dht_sensors].pin = XdrvMailbox.payload; + Dht[dht_sensors].type = XdrvMailbox.index; + dht_sensors++; + XdrvMailbox.index = GPIO_DHT11; + } else { + XdrvMailbox.index = 0; + } + return true; + } + return false; +} + +void DhtInit(void) +{ + if (dht_sensors) { + if (pin[GPIO_DHT11_OUT] < 99) { + dht_pin_out = pin[GPIO_DHT11_OUT]; + dht_dual_mode = true; + dht_sensors = 1; + pinMode(dht_pin_out, OUTPUT); + } + + for (uint32_t i = 0; i < dht_sensors; i++) { + pinMode(Dht[i].pin, INPUT_PULLUP); + Dht[i].lastresult = DHT_MAX_RETRY; + GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); + if (dht_sensors > 1) { + snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v5) " D_SENSORS_FOUND " %d"), dht_sensors); + } else { + dht_active = false; + } +} + +void DhtEverySecond(void) +{ + if (uptime &1) { + for (uint32_t sensor = 0; sensor < dht_sensors; sensor++) { + + if (!DhtRead(sensor)) { + Dht[sensor].lastresult++; + if (Dht[sensor].lastresult > DHT_MAX_RETRY) { + Dht[sensor].t = NAN; + Dht[sensor].h = NAN; + } + } + } + } +} + +void DhtShow(bool json) +{ + for (uint32_t i = 0; i < dht_sensors; i++) { + char temperature[33]; + dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if ((0 == tele_period) && (0 == i)) { + KnxSensor(KNX_TEMPERATURE, Dht[i].t); + KnxSensor(KNX_HUMIDITY, Dht[i].h); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); +#endif + } + } +} + + + + + +bool Xsns06(uint8_t function) +{ + bool result = false; + + if (dht_active) { + switch (function) { + case FUNC_EVERY_SECOND: + DhtEverySecond(); + break; + case FUNC_JSON_APPEND: + DhtShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + DhtShow(0); + break; +#endif + case FUNC_INIT: + DhtInit(); + break; + case FUNC_PIN_STATE: + result = DhtPinState(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_07_sht1x.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_07_sht1x.ino" +#ifdef USE_I2C +#ifdef USE_SHT +# 31 "S:/Development/Tasmota/tasmota/xsns_07_sht1x.ino" +#define XSNS_07 7 +#define XI2C_08 8 + +enum { + SHT1X_CMD_MEASURE_TEMP = B00000011, + SHT1X_CMD_MEASURE_RH = B00000101, + SHT1X_CMD_SOFT_RESET = B00011110 +}; + +uint8_t sht_sda_pin; +uint8_t sht_scl_pin; +uint8_t sht_type = 0; +char sht_types[] = "SHT1X"; +uint8_t sht_valid = 0; +float sht_temperature = 0; +float sht_humidity = 0; + +bool ShtReset(void) +{ + pinMode(sht_sda_pin, INPUT_PULLUP); + pinMode(sht_scl_pin, OUTPUT); + delay(11); + for (uint32_t i = 0; i < 9; i++) { + digitalWrite(sht_scl_pin, HIGH); + digitalWrite(sht_scl_pin, LOW); + } + bool success = ShtSendCommand(SHT1X_CMD_SOFT_RESET); + delay(11); + return success; +} + +bool ShtSendCommand(const uint8_t cmd) +{ + pinMode(sht_sda_pin, OUTPUT); + + digitalWrite(sht_sda_pin, HIGH); + digitalWrite(sht_scl_pin, HIGH); + digitalWrite(sht_sda_pin, LOW); + digitalWrite(sht_scl_pin, LOW); + digitalWrite(sht_scl_pin, HIGH); + digitalWrite(sht_sda_pin, HIGH); + digitalWrite(sht_scl_pin, LOW); + + shiftOut(sht_sda_pin, sht_scl_pin, MSBFIRST, cmd); + + bool ackerror = false; + digitalWrite(sht_scl_pin, HIGH); + pinMode(sht_sda_pin, INPUT_PULLUP); + if (digitalRead(sht_sda_pin) != LOW) { + ackerror = true; + } + digitalWrite(sht_scl_pin, LOW); + delayMicroseconds(1); + if (digitalRead(sht_sda_pin) != HIGH) { + ackerror = true; + } + if (ackerror) { + + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_SHT1 D_SENSOR_DID_NOT_ACK_COMMAND)); + } + return (!ackerror); +} + +bool ShtAwaitResult(void) +{ + + for (uint32_t i = 0; i < 16; i++) { + if (LOW == digitalRead(sht_sda_pin)) { + return true; + } + delay(20); + } + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_SHT1 D_SENSOR_BUSY)); + + return false; +} + +int ShtReadData(void) +{ + int val = 0; + + + val = shiftIn(sht_sda_pin, sht_scl_pin, 8); + val <<= 8; + + pinMode(sht_sda_pin, OUTPUT); + digitalWrite(sht_sda_pin, LOW); + digitalWrite(sht_scl_pin, HIGH); + digitalWrite(sht_scl_pin, LOW); + pinMode(sht_sda_pin, INPUT_PULLUP); + + val |= shiftIn(sht_sda_pin, sht_scl_pin, 8); + + digitalWrite(sht_scl_pin, HIGH); + digitalWrite(sht_scl_pin, LOW); + return val; +} + +bool ShtRead(void) +{ + if (sht_valid) { sht_valid--; } + if (!ShtReset()) { return false; } + if (!ShtSendCommand(SHT1X_CMD_MEASURE_TEMP)) { return false; } + if (!ShtAwaitResult()) { return false; } + float tempRaw = ShtReadData(); + if (!ShtSendCommand(SHT1X_CMD_MEASURE_RH)) { return false; } + if (!ShtAwaitResult()) { return false; } + float humRaw = ShtReadData(); + + + const float d1 = -39.7; + const float d2 = 0.01; + sht_temperature = d1 + (tempRaw * d2); + const float c1 = -2.0468; + const float c2 = 0.0367; + const float c3 = -1.5955E-6; + const float t1 = 0.01; + const float t2 = 0.00008; + float rhLinear = c1 + c2 * humRaw + c3 * humRaw * humRaw; + sht_humidity = (sht_temperature - 25) * (t1 + t2 * humRaw) + rhLinear; + sht_temperature = ConvertTemp(sht_temperature); + ConvertHumidity(sht_humidity); + + sht_valid = SENSOR_MAX_MISS; + return true; +} + + + +void ShtDetect(void) +{ + sht_sda_pin = pin[GPIO_I2C_SDA]; + sht_scl_pin = pin[GPIO_I2C_SCL]; + if (ShtRead()) { + sht_type = 1; + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C D_SHT1X_FOUND)); + } else { + Wire.begin(sht_sda_pin, sht_scl_pin); + sht_type = 0; + } +} + +void ShtEverySecond(void) +{ + if (!(uptime %4)) { + + if (!ShtRead()) { + AddLogMissed(sht_types, sht_valid); + } + } +} + +void ShtShow(bool json) +{ + if (sht_valid) { + char temperature[33]; + dtostrfd(sht_temperature, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(sht_humidity, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, sht_types, temperature, humidity); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, sht_temperature); + KnxSensor(KNX_HUMIDITY, sht_humidity); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, sht_types, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, sht_types, humidity); +#endif + } + } +} + + + + + +bool Xsns07(uint8_t function) +{ + if (!I2cEnabled(XI2C_08)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + ShtDetect(); + } + else if (sht_type) { + switch (function) { + case FUNC_EVERY_SECOND: + ShtEverySecond(); + break; + case FUNC_JSON_APPEND: + ShtShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + ShtShow(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_08_htu21.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_08_htu21.ino" +#ifdef USE_I2C +#ifdef USE_HTU +# 30 "S:/Development/Tasmota/tasmota/xsns_08_htu21.ino" +#define XSNS_08 8 +#define XI2C_09 9 + +#define HTU21_ADDR 0x40 + +#define SI7013_CHIPID 0x0D +#define SI7020_CHIPID 0x14 +#define SI7021_CHIPID 0x15 +#define HTU21_CHIPID 0x32 + +#define HTU21_READTEMP 0xE3 +#define HTU21_READHUM 0xE5 +#define HTU21_WRITEREG 0xE6 +#define HTU21_READREG 0xE7 +#define HTU21_RESET 0xFE +#define HTU21_HEATER_WRITE 0x51 +#define HTU21_HEATER_READ 0x11 +#define HTU21_SERIAL2_READ1 0xFC +#define HTU21_SERIAL2_READ2 0xC9 + +#define HTU21_HEATER_ON 0x04 +#define HTU21_HEATER_OFF 0xFB + +#define HTU21_RES_RH12_T14 0x00 +#define HTU21_RES_RH8_T12 0x01 +#define HTU21_RES_RH10_T13 0x80 +#define HTU21_RES_RH11_T11 0x81 + +#define HTU21_CRC8_POLYNOM 0x13100 + +const char kHtuTypes[] PROGMEM = "HTU21|SI7013|SI7020|SI7021|T/RH?"; + +uint8_t htu_address; +uint8_t htu_type = 0; +uint8_t htu_delay_temp; +uint8_t htu_delay_humidity = 50; +uint8_t htu_valid = 0; +float htu_temperature = 0; +float htu_humidity = 0; +char htu_types[7]; + +uint8_t HtuCheckCrc8(uint16_t data) +{ + for (uint32_t bit = 0; bit < 16; bit++) { + if (data & 0x8000) { + data = (data << 1) ^ HTU21_CRC8_POLYNOM; + } else { + data <<= 1; + } + } + return data >>= 8; +} + +uint8_t HtuReadDeviceId(void) +{ + uint16_t deviceID = 0; + uint8_t checksum = 0; + + Wire.beginTransmission(HTU21_ADDR); + Wire.write(HTU21_SERIAL2_READ1); + Wire.write(HTU21_SERIAL2_READ2); + Wire.endTransmission(); + + Wire.requestFrom(HTU21_ADDR, 3); + deviceID = Wire.read() << 8; + deviceID |= Wire.read(); + checksum = Wire.read(); + if (HtuCheckCrc8(deviceID) == checksum) { + deviceID = deviceID >> 8; + } else { + deviceID = 0; + } + return (uint8_t)deviceID; +} + +void HtuSetResolution(uint8_t resolution) +{ + uint8_t current = I2cRead8(HTU21_ADDR, HTU21_READREG); + current &= 0x7E; + current |= resolution; + I2cWrite8(HTU21_ADDR, HTU21_WRITEREG, current); +} + +void HtuReset(void) +{ + Wire.beginTransmission(HTU21_ADDR); + Wire.write(HTU21_RESET); + Wire.endTransmission(); + delay(15); +} + +void HtuHeater(uint8_t heater) +{ + uint8_t current = I2cRead8(HTU21_ADDR, HTU21_READREG); + + switch(heater) + { + case HTU21_HEATER_ON : current |= heater; + break; + case HTU21_HEATER_OFF : current &= heater; + break; + default : current &= heater; + break; + } + I2cWrite8(HTU21_ADDR, HTU21_WRITEREG, current); +} + +void HtuInit(void) +{ + HtuReset(); + HtuHeater(HTU21_HEATER_OFF); + HtuSetResolution(HTU21_RES_RH12_T14); +} + +bool HtuRead(void) +{ + uint8_t checksum = 0; + uint16_t sensorval = 0; + + if (htu_valid) { htu_valid--; } + + Wire.beginTransmission(HTU21_ADDR); + Wire.write(HTU21_READTEMP); + if (Wire.endTransmission() != 0) { return false; } + delay(htu_delay_temp); + + Wire.requestFrom(HTU21_ADDR, 3); + if (3 == Wire.available()) { + sensorval = Wire.read() << 8; + sensorval |= Wire.read(); + checksum = Wire.read(); + } + if (HtuCheckCrc8(sensorval) != checksum) { return false; } + + htu_temperature = ConvertTemp(0.002681 * (float)sensorval - 46.85); + + Wire.beginTransmission(HTU21_ADDR); + Wire.write(HTU21_READHUM); + if (Wire.endTransmission() != 0) { return false; } + delay(htu_delay_humidity); + + Wire.requestFrom(HTU21_ADDR, 3); + if (3 <= Wire.available()) { + sensorval = Wire.read() << 8; + sensorval |= Wire.read(); + checksum = Wire.read(); + } + if (HtuCheckCrc8(sensorval) != checksum) { return false; } + + sensorval ^= 0x02; + htu_humidity = 0.001907 * (float)sensorval - 6; + if (htu_humidity > 100) { htu_humidity = 100.0; } + if (htu_humidity < 0) { htu_humidity = 0.01; } + + if ((0.00 == htu_humidity) && (0.00 == htu_temperature)) { + htu_humidity = 0.0; + } + if ((htu_temperature > 0.00) && (htu_temperature < 80.00)) { + htu_humidity = (-0.15) * (25 - htu_temperature) + htu_humidity; + } + ConvertHumidity(htu_humidity); + + htu_valid = SENSOR_MAX_MISS; + return true; +} + + + +void HtuDetect(void) +{ + htu_address = HTU21_ADDR; + if (I2cActive(htu_address)) { return; } + + htu_type = HtuReadDeviceId(); + if (htu_type) { + uint8_t index = 0; + HtuInit(); + switch (htu_type) { + case HTU21_CHIPID: + htu_delay_temp = 50; + htu_delay_humidity = 16; + break; + case SI7021_CHIPID: + index++; + case SI7020_CHIPID: + index++; + case SI7013_CHIPID: + index++; + htu_delay_temp = 12; + htu_delay_humidity = 23; + break; + default: + index = 4; + htu_delay_temp = 50; + htu_delay_humidity = 23; + } + GetTextIndexed(htu_types, sizeof(htu_types), index, kHtuTypes); + I2cSetActiveFound(htu_address, htu_types); + } +} + +void HtuEverySecond(void) +{ + if (uptime &1) { + + if (!HtuRead()) { + AddLogMissed(htu_types, htu_valid); + } + } +} + +void HtuShow(bool json) +{ + if (htu_valid) { + char temperature[33]; + dtostrfd(htu_temperature, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(htu_humidity, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, htu_types, temperature, humidity); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, htu_temperature); + KnxSensor(KNX_HUMIDITY, htu_humidity); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, htu_types, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, htu_types, humidity); +#endif + } + } +} + + + + + +bool Xsns08(uint8_t function) +{ + if (!I2cEnabled(XI2C_09)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + HtuDetect(); + } + else if (htu_type) { + switch (function) { + case FUNC_EVERY_SECOND: + HtuEverySecond(); + break; + case FUNC_JSON_APPEND: + HtuShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + HtuShow(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_09_bmp.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_09_bmp.ino" +#ifdef USE_I2C +#ifdef USE_BMP +# 30 "S:/Development/Tasmota/tasmota/xsns_09_bmp.ino" +#define XSNS_09 9 +#define XI2C_10 10 + +#define BMP_ADDR1 0x76 +#define BMP_ADDR2 0x77 + +#define BMP180_CHIPID 0x55 +#define BMP280_CHIPID 0x58 +#define BME280_CHIPID 0x60 +#define BME680_CHIPID 0x61 + +#define BMP_REGISTER_CHIPID 0xD0 + +#define BMP_REGISTER_RESET 0xE0 + +#define BMP_CMND_RESET 0xB6 + +#define BMP_MAX_SENSORS 2 + +const char kBmpTypes[] PROGMEM = "BMP180|BMP280|BME280|BME680"; + +typedef struct { + uint8_t bmp_address; + char bmp_name[7]; + uint8_t bmp_type; + uint8_t bmp_model; +#ifdef USE_BME680 + uint8_t bme680_state; + float bmp_gas_resistance; +#endif + float bmp_temperature; + float bmp_pressure; + float bmp_humidity; +} bmp_sensors_t; + +uint8_t bmp_addresses[] = { BMP_ADDR1, BMP_ADDR2 }; +uint8_t bmp_count = 0; +uint8_t bmp_once = 1; + +bmp_sensors_t *bmp_sensors = nullptr; + + + + + +#define BMP180_REG_CONTROL 0xF4 +#define BMP180_REG_RESULT 0xF6 +#define BMP180_TEMPERATURE 0x2E +#define BMP180_PRESSURE3 0xF4 + +#define BMP180_AC1 0xAA +#define BMP180_AC2 0xAC +#define BMP180_AC3 0xAE +#define BMP180_AC4 0xB0 +#define BMP180_AC5 0xB2 +#define BMP180_AC6 0xB4 +#define BMP180_VB1 0xB6 +#define BMP180_VB2 0xB8 +#define BMP180_MB 0xBA +#define BMP180_MC 0xBC +#define BMP180_MD 0xBE + +#define BMP180_OSS 3 + +typedef struct { + int16_t cal_ac1; + int16_t cal_ac2; + int16_t cal_ac3; + int16_t cal_b1; + int16_t cal_b2; + int16_t cal_mc; + int16_t cal_md; + uint16_t cal_ac4; + uint16_t cal_ac5; + uint16_t cal_ac6; +} bmp180_cal_data_t; + +bmp180_cal_data_t *bmp180_cal_data = nullptr; + +bool Bmp180Calibration(uint8_t bmp_idx) +{ + if (!bmp180_cal_data) { + bmp180_cal_data = (bmp180_cal_data_t*)malloc(BMP_MAX_SENSORS * sizeof(bmp180_cal_data_t)); + } + if (!bmp180_cal_data) { return false; } + + bmp180_cal_data[bmp_idx].cal_ac1 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC1); + bmp180_cal_data[bmp_idx].cal_ac2 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC2); + bmp180_cal_data[bmp_idx].cal_ac3 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC3); + bmp180_cal_data[bmp_idx].cal_ac4 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC4); + bmp180_cal_data[bmp_idx].cal_ac5 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC5); + bmp180_cal_data[bmp_idx].cal_ac6 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC6); + bmp180_cal_data[bmp_idx].cal_b1 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_VB1); + bmp180_cal_data[bmp_idx].cal_b2 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_VB2); + bmp180_cal_data[bmp_idx].cal_mc = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_MC); + bmp180_cal_data[bmp_idx].cal_md = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_MD); + + + if (!bmp180_cal_data[bmp_idx].cal_ac1 | + !bmp180_cal_data[bmp_idx].cal_ac2 | + !bmp180_cal_data[bmp_idx].cal_ac3 | + !bmp180_cal_data[bmp_idx].cal_ac4 | + !bmp180_cal_data[bmp_idx].cal_ac5 | + !bmp180_cal_data[bmp_idx].cal_ac6 | + !bmp180_cal_data[bmp_idx].cal_b1 | + !bmp180_cal_data[bmp_idx].cal_b2 | + !bmp180_cal_data[bmp_idx].cal_mc | + !bmp180_cal_data[bmp_idx].cal_md) { + return false; + } + + if ((bmp180_cal_data[bmp_idx].cal_ac1 == (int16_t)0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_ac2 == (int16_t)0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_ac3 == (int16_t)0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_ac4 == 0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_ac5 == 0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_ac6 == 0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_b1 == (int16_t)0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_b2 == (int16_t)0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_mc == (int16_t)0xFFFF) | + (bmp180_cal_data[bmp_idx].cal_md == (int16_t)0xFFFF)) { + return false; + } + return true; +} + +void Bmp180Read(uint8_t bmp_idx) +{ + if (!bmp180_cal_data) { return; } + + I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_CONTROL, BMP180_TEMPERATURE); + delay(5); + int ut = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_RESULT); + int32_t xt1 = (ut - (int32_t)bmp180_cal_data[bmp_idx].cal_ac6) * ((int32_t)bmp180_cal_data[bmp_idx].cal_ac5) >> 15; + int32_t xt2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_mc << 11) / (xt1 + (int32_t)bmp180_cal_data[bmp_idx].cal_md); + int32_t bmp180_b5 = xt1 + xt2; + bmp_sensors[bmp_idx].bmp_temperature = ((bmp180_b5 + 8) >> 4) / 10.0; + + I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_CONTROL, BMP180_PRESSURE3); + delay(2 + (4 << BMP180_OSS)); + uint32_t up = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_RESULT); + up >>= (8 - BMP180_OSS); + + int32_t b6 = bmp180_b5 - 4000; + int32_t x1 = ((int32_t)bmp180_cal_data[bmp_idx].cal_b2 * ((b6 * b6) >> 12)) >> 11; + int32_t x2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_ac2 * b6) >> 11; + int32_t x3 = x1 + x2; + int32_t b3 = ((((int32_t)bmp180_cal_data[bmp_idx].cal_ac1 * 4 + x3) << BMP180_OSS) + 2) >> 2; + + x1 = ((int32_t)bmp180_cal_data[bmp_idx].cal_ac3 * b6) >> 13; + x2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_b1 * ((b6 * b6) >> 12)) >> 16; + x3 = ((x1 + x2) + 2) >> 2; + uint32_t b4 = ((uint32_t)bmp180_cal_data[bmp_idx].cal_ac4 * (uint32_t)(x3 + 32768)) >> 15; + uint32_t b7 = ((uint32_t)up - b3) * (uint32_t)(50000UL >> BMP180_OSS); + + int32_t p; + if (b7 < 0x80000000) { + p = (b7 * 2) / b4; + } + else { + p = (b7 / b4) * 2; + } + x1 = (p >> 8) * (p >> 8); + x1 = (x1 * 3038) >> 16; + x2 = (-7357 * p) >> 16; + p += ((x1 + x2 + (int32_t)3791) >> 4); + bmp_sensors[bmp_idx].bmp_pressure = (float)p / 100.0; +} + + + + + + + +#define BME280_REGISTER_CONTROLHUMID 0xF2 +#define BME280_REGISTER_CONTROL 0xF4 +#define BME280_REGISTER_CONFIG 0xF5 +#define BME280_REGISTER_PRESSUREDATA 0xF7 +#define BME280_REGISTER_TEMPDATA 0xFA +#define BME280_REGISTER_HUMIDDATA 0xFD + +#define BME280_REGISTER_DIG_T1 0x88 +#define BME280_REGISTER_DIG_T2 0x8A +#define BME280_REGISTER_DIG_T3 0x8C +#define BME280_REGISTER_DIG_P1 0x8E +#define BME280_REGISTER_DIG_P2 0x90 +#define BME280_REGISTER_DIG_P3 0x92 +#define BME280_REGISTER_DIG_P4 0x94 +#define BME280_REGISTER_DIG_P5 0x96 +#define BME280_REGISTER_DIG_P6 0x98 +#define BME280_REGISTER_DIG_P7 0x9A +#define BME280_REGISTER_DIG_P8 0x9C +#define BME280_REGISTER_DIG_P9 0x9E +#define BME280_REGISTER_DIG_H1 0xA1 +#define BME280_REGISTER_DIG_H2 0xE1 +#define BME280_REGISTER_DIG_H3 0xE3 +#define BME280_REGISTER_DIG_H4 0xE4 +#define BME280_REGISTER_DIG_H5 0xE5 +#define BME280_REGISTER_DIG_H6 0xE7 + +typedef struct { + uint16_t dig_T1; + int16_t dig_T2; + int16_t dig_T3; + uint16_t dig_P1; + int16_t dig_P2; + int16_t dig_P3; + int16_t dig_P4; + int16_t dig_P5; + int16_t dig_P6; + int16_t dig_P7; + int16_t dig_P8; + int16_t dig_P9; + int16_t dig_H2; + int16_t dig_H4; + int16_t dig_H5; + uint8_t dig_H1; + uint8_t dig_H3; + int8_t dig_H6; +} Bme280CalibrationData_t; + +Bme280CalibrationData_t *Bme280CalibrationData = nullptr; + +bool Bmx280Calibrate(uint8_t bmp_idx) +{ + + + if (!Bme280CalibrationData) { + Bme280CalibrationData = (Bme280CalibrationData_t*)malloc(BMP_MAX_SENSORS * sizeof(Bme280CalibrationData_t)); + } + if (!Bme280CalibrationData) { return false; } + + Bme280CalibrationData[bmp_idx].dig_T1 = I2cRead16LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T1); + Bme280CalibrationData[bmp_idx].dig_T2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T2); + Bme280CalibrationData[bmp_idx].dig_T3 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T3); + Bme280CalibrationData[bmp_idx].dig_P1 = I2cRead16LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P1); + Bme280CalibrationData[bmp_idx].dig_P2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P2); + Bme280CalibrationData[bmp_idx].dig_P3 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P3); + Bme280CalibrationData[bmp_idx].dig_P4 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P4); + Bme280CalibrationData[bmp_idx].dig_P5 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P5); + Bme280CalibrationData[bmp_idx].dig_P6 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P6); + Bme280CalibrationData[bmp_idx].dig_P7 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P7); + Bme280CalibrationData[bmp_idx].dig_P8 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P8); + Bme280CalibrationData[bmp_idx].dig_P9 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P9); + if (BME280_CHIPID == bmp_sensors[bmp_idx].bmp_type) { + Bme280CalibrationData[bmp_idx].dig_H1 = I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H1); + Bme280CalibrationData[bmp_idx].dig_H2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H2); + Bme280CalibrationData[bmp_idx].dig_H3 = I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H3); + Bme280CalibrationData[bmp_idx].dig_H4 = (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H4) << 4) | (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H4 + 1) & 0xF); + Bme280CalibrationData[bmp_idx].dig_H5 = (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H5 + 1) << 4) | (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H5) >> 4); + Bme280CalibrationData[bmp_idx].dig_H6 = (int8_t)I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H6); + I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0x00); + + I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROLHUMID, 0x01); + I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONFIG, 0xA0); + I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0x27); + } else { + I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0xB7); + } + + return true; +} + +void Bme280Read(uint8_t bmp_idx) +{ + if (!Bme280CalibrationData) { return; } + + int32_t adc_T = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_TEMPDATA); + adc_T >>= 4; + + int32_t vart1 = ((((adc_T >> 3) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1 << 1))) * ((int32_t)Bme280CalibrationData[bmp_idx].dig_T2)) >> 11; + int32_t vart2 = (((((adc_T >> 4) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1)) * ((adc_T >> 4) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1))) >> 12) * + ((int32_t)Bme280CalibrationData[bmp_idx].dig_T3)) >> 14; + int32_t t_fine = vart1 + vart2; + float T = (t_fine * 5 + 128) >> 8; + bmp_sensors[bmp_idx].bmp_temperature = T / 100.0; + + int32_t adc_P = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_PRESSUREDATA); + adc_P >>= 4; + + int64_t var1 = ((int64_t)t_fine) - 128000; + int64_t var2 = var1 * var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P6; + var2 = var2 + ((var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P5) << 17); + var2 = var2 + (((int64_t)Bme280CalibrationData[bmp_idx].dig_P4) << 35); + var1 = ((var1 * var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P3) >> 8) + ((var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P2) << 12); + var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)Bme280CalibrationData[bmp_idx].dig_P1) >> 33; + if (0 == var1) { + return; + } + int64_t p = 1048576 - adc_P; + p = (((p << 31) - var2) * 3125) / var1; + var1 = (((int64_t)Bme280CalibrationData[bmp_idx].dig_P9) * (p >> 13) * (p >> 13)) >> 25; + var2 = (((int64_t)Bme280CalibrationData[bmp_idx].dig_P8) * p) >> 19; + p = ((p + var1 + var2) >> 8) + (((int64_t)Bme280CalibrationData[bmp_idx].dig_P7) << 4); + bmp_sensors[bmp_idx].bmp_pressure = (float)p / 25600.0; + + if (BMP280_CHIPID == bmp_sensors[bmp_idx].bmp_type) { return; } + + int32_t adc_H = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_HUMIDDATA); + + int32_t v_x1_u32r = (t_fine - ((int32_t)76800)); + v_x1_u32r = (((((adc_H << 14) - (((int32_t)Bme280CalibrationData[bmp_idx].dig_H4) << 20) - + (((int32_t)Bme280CalibrationData[bmp_idx].dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) * + (((((((v_x1_u32r * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H6)) >> 10) * + (((v_x1_u32r * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + + ((int32_t)2097152)) * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H2) + 8192) >> 14)); + v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * + ((int32_t)Bme280CalibrationData[bmp_idx].dig_H1)) >> 4)); + v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r; + v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r; + float h = (v_x1_u32r >> 12); + bmp_sensors[bmp_idx].bmp_humidity = h / 1024.0; +} + +#ifdef USE_BME680 + + + + +#include + +struct bme680_dev *gas_sensor = nullptr; + +static void BmeDelayMs(uint32_t ms) +{ + delay(ms); +} + +bool Bme680Init(uint8_t bmp_idx) +{ + if (!gas_sensor) { + gas_sensor = (bme680_dev*)malloc(BMP_MAX_SENSORS * sizeof(bme680_dev)); + } + if (!gas_sensor) { return false; } + + gas_sensor[bmp_idx].dev_id = bmp_sensors[bmp_idx].bmp_address; + gas_sensor[bmp_idx].intf = BME680_I2C_INTF; + gas_sensor[bmp_idx].read = &I2cReadBuffer; + gas_sensor[bmp_idx].write = &I2cWriteBuffer; + gas_sensor[bmp_idx].delay_ms = BmeDelayMs; + + + + gas_sensor[bmp_idx].amb_temp = 25; + + int8_t rslt = BME680_OK; + rslt = bme680_init(&gas_sensor[bmp_idx]); + if (rslt != BME680_OK) { return false; } + + + gas_sensor[bmp_idx].tph_sett.os_hum = BME680_OS_2X; + gas_sensor[bmp_idx].tph_sett.os_pres = BME680_OS_4X; + gas_sensor[bmp_idx].tph_sett.os_temp = BME680_OS_8X; + gas_sensor[bmp_idx].tph_sett.filter = BME680_FILTER_SIZE_3; + + + gas_sensor[bmp_idx].gas_sett.run_gas = BME680_ENABLE_GAS_MEAS; + + gas_sensor[bmp_idx].gas_sett.heatr_temp = 320; + gas_sensor[bmp_idx].gas_sett.heatr_dur = 150; + + + + gas_sensor[bmp_idx].power_mode = BME680_FORCED_MODE; + + + uint8_t set_required_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_FILTER_SEL | BME680_GAS_SENSOR_SEL; + + + rslt = bme680_set_sensor_settings(set_required_settings,&gas_sensor[bmp_idx]); + if (rslt != BME680_OK) { return false; } + + bmp_sensors[bmp_idx].bme680_state = 0; + + return true; +} + +void Bme680Read(uint8_t bmp_idx) +{ + if (!gas_sensor) { return; } + + int8_t rslt = BME680_OK; + + if (BME680_CHIPID == bmp_sensors[bmp_idx].bmp_type) { + if (0 == bmp_sensors[bmp_idx].bme680_state) { + + rslt = bme680_set_sensor_mode(&gas_sensor[bmp_idx]); + if (rslt != BME680_OK) { return; } + + + + + + + + bmp_sensors[bmp_idx].bme680_state = 1; + } else { + bmp_sensors[bmp_idx].bme680_state = 0; + + struct bme680_field_data data; + rslt = bme680_get_sensor_data(&data, &gas_sensor[bmp_idx]); + if (rslt != BME680_OK) { return; } + + bmp_sensors[bmp_idx].bmp_temperature = data.temperature / 100.0; + bmp_sensors[bmp_idx].bmp_humidity = data.humidity / 1000.0; + bmp_sensors[bmp_idx].bmp_pressure = data.pressure / 100.0; + + if (data.status & BME680_GASM_VALID_MSK) { + bmp_sensors[bmp_idx].bmp_gas_resistance = data.gas_resistance / 1000.0; + } else { + bmp_sensors[bmp_idx].bmp_gas_resistance = 0; + } + } + } + return; +} + +#endif + + + +void BmpDetect(void) +{ + int bmp_sensor_size = BMP_MAX_SENSORS * sizeof(bmp_sensors_t); + if (!bmp_sensors) { + bmp_sensors = (bmp_sensors_t*)malloc(bmp_sensor_size); + } + if (!bmp_sensors) { return; } + memset(bmp_sensors, 0, bmp_sensor_size); + + for (uint32_t i = 0; i < BMP_MAX_SENSORS; i++) { + if (I2cActive(bmp_addresses[i])) { continue; } + uint8_t bmp_type = I2cRead8(bmp_addresses[i], BMP_REGISTER_CHIPID); + if (bmp_type) { + bmp_sensors[bmp_count].bmp_address = bmp_addresses[i]; + bmp_sensors[bmp_count].bmp_type = bmp_type; + bmp_sensors[bmp_count].bmp_model = 0; + + bool success = false; + switch (bmp_type) { + case BMP180_CHIPID: + success = Bmp180Calibration(bmp_count); + break; + case BME280_CHIPID: + bmp_sensors[bmp_count].bmp_model++; + case BMP280_CHIPID: + bmp_sensors[bmp_count].bmp_model++; + success = Bmx280Calibrate(bmp_count); + break; +#ifdef USE_BME680 + case BME680_CHIPID: + bmp_sensors[bmp_count].bmp_model = 3; + success = Bme680Init(bmp_count); + break; +#endif + } + if (success) { + GetTextIndexed(bmp_sensors[bmp_count].bmp_name, sizeof(bmp_sensors[bmp_count].bmp_name), bmp_sensors[bmp_count].bmp_model, kBmpTypes); + I2cSetActiveFound(bmp_sensors[bmp_count].bmp_address, bmp_sensors[bmp_count].bmp_name); + bmp_count++; + } + } + } +} + +void BmpRead(void) +{ + for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) { + switch (bmp_sensors[bmp_idx].bmp_type) { + case BMP180_CHIPID: + Bmp180Read(bmp_idx); + break; + case BMP280_CHIPID: + case BME280_CHIPID: + Bme280Read(bmp_idx); + break; +#ifdef USE_BME680 + case BME680_CHIPID: + Bme680Read(bmp_idx); + break; +#endif + } + } + ConvertTemp(bmp_sensors[0].bmp_temperature); + ConvertHumidity(bmp_sensors[0].bmp_humidity); +} + +void BmpShow(bool json) +{ + for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) { + if (bmp_sensors[bmp_idx].bmp_type) { + float bmp_sealevel = 0.0; + if (bmp_sensors[bmp_idx].bmp_pressure != 0.0) { + bmp_sealevel = (bmp_sensors[bmp_idx].bmp_pressure / FastPrecisePow(1.0 - ((float)Settings.altitude / 44330.0), 5.255)) - 21.6; + bmp_sealevel = ConvertPressure(bmp_sealevel); + } + float bmp_temperature = ConvertTemp(bmp_sensors[bmp_idx].bmp_temperature); + float bmp_pressure = ConvertPressure(bmp_sensors[bmp_idx].bmp_pressure); + + char name[10]; + strlcpy(name, bmp_sensors[bmp_idx].bmp_name, sizeof(name)); + if (bmp_count > 1) { + snprintf_P(name, sizeof(name), PSTR("%s%c%02X"), name, IndexSeparator(), bmp_sensors[bmp_idx].bmp_address); + } + + char temperature[33]; + dtostrfd(bmp_temperature, Settings.flag2.temperature_resolution, temperature); + char pressure[33]; + dtostrfd(bmp_pressure, Settings.flag2.pressure_resolution, pressure); + char sea_pressure[33]; + dtostrfd(bmp_sealevel, Settings.flag2.pressure_resolution, sea_pressure); + char humidity[33]; + dtostrfd(bmp_sensors[bmp_idx].bmp_humidity, Settings.flag2.humidity_resolution, humidity); +#ifdef USE_BME680 + char gas_resistance[33]; + dtostrfd(bmp_sensors[bmp_idx].bmp_gas_resistance, 2, gas_resistance); +#endif + + if (json) { + char json_humidity[40]; + snprintf_P(json_humidity, sizeof(json_humidity), PSTR(",\"" D_JSON_HUMIDITY "\":%s"), humidity); + char json_sealevel[40]; + snprintf_P(json_sealevel, sizeof(json_sealevel), PSTR(",\"" D_JSON_PRESSUREATSEALEVEL "\":%s"), sea_pressure); +#ifdef USE_BME680 + char json_gas[40]; + snprintf_P(json_gas, sizeof(json_gas), PSTR(",\"" D_JSON_GAS "\":%s"), gas_resistance); + + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s,\"" D_JSON_PRESSURE "\":%s%s%s}"), + name, + temperature, + (bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "", + pressure, + (Settings.altitude != 0) ? json_sealevel : "", + (bmp_sensors[bmp_idx].bmp_model >= 3) ? json_gas : ""); +#else + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s,\"" D_JSON_PRESSURE "\":%s%s}"), + name, temperature, (bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "", pressure, (Settings.altitude != 0) ? json_sealevel : ""); +#endif + +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == bmp_idx)) { + DomoticzTempHumPressureSensor(temperature, humidity, pressure); +#ifdef USE_BME680 + if (bmp_sensors[bmp_idx].bmp_model >= 3) { DomoticzSensor(DZ_AIRQUALITY, (uint32_t)bmp_sensors[bmp_idx].bmp_gas_resistance); } +#endif + } +#endif + +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, bmp_temperature); + KnxSensor(KNX_HUMIDITY, bmp_sensors[bmp_idx].bmp_humidity); + } +#endif + +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, name, temperature, TempUnit()); + if (bmp_sensors[bmp_idx].bmp_model >= 2) { + WSContentSend_PD(HTTP_SNS_HUM, name, humidity); + } + WSContentSend_PD(HTTP_SNS_PRESSURE, name, pressure, PressureUnit().c_str()); + if (Settings.altitude != 0) { + WSContentSend_PD(HTTP_SNS_SEAPRESSURE, name, sea_pressure, PressureUnit().c_str()); + } +#ifdef USE_BME680 + if (bmp_sensors[bmp_idx].bmp_model >= 3) { + WSContentSend_PD(PSTR("{s}%s " D_GAS "{m}%s " D_UNIT_KILOOHM "{e}"), name, gas_resistance); + } +#endif + +#endif + } + } + } +} + +#ifdef USE_DEEPSLEEP + +void BMP_EnterSleep(void) +{ + for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) { + switch (bmp_sensors[bmp_idx].bmp_type) { + case BMP180_CHIPID: + case BMP280_CHIPID: + case BME280_CHIPID: + I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP_REGISTER_RESET, BMP_CMND_RESET); + break; + default: + break; + } + } +} + +#endif + + + + + +bool Xsns09(uint8_t function) +{ + if (!I2cEnabled(XI2C_10)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + BmpDetect(); + } + else if (bmp_count) { + switch (function) { + case FUNC_EVERY_SECOND: + BmpRead(); + break; + case FUNC_JSON_APPEND: + BmpShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + BmpShow(0); + break; +#endif +#ifdef USE_DEEPSLEEP + case FUNC_SAVE_BEFORE_RESTART: + BMP_EnterSleep(); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_10_bh1750.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_10_bh1750.ino" +#ifdef USE_I2C +#ifdef USE_BH1750 + + + + + + +#define XSNS_10 10 +#define XI2C_11 11 + +#define BH1750_ADDR1 0x23 +#define BH1750_ADDR2 0x5C + +#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 + +uint8_t bh1750_address; +uint8_t bh1750_addresses[] = { BH1750_ADDR1, BH1750_ADDR2 }; +uint8_t bh1750_type = 0; +uint8_t bh1750_valid = 0; +uint16_t bh1750_illuminance = 0; +char bh1750_types[] = "BH1750"; + +bool Bh1750Read(void) +{ + if (bh1750_valid) { bh1750_valid--; } + + if (2 != Wire.requestFrom(bh1750_address, (uint8_t)2)) { return false; } + uint8_t msb = Wire.read(); + uint8_t lsb = Wire.read(); + bh1750_illuminance = ((msb << 8) | lsb) / 1.2; + bh1750_valid = SENSOR_MAX_MISS; + return true; +} + + + +void Bh1750Detect(void) +{ + for (uint32_t i = 0; i < sizeof(bh1750_addresses); i++) { + bh1750_address = bh1750_addresses[i]; + if (I2cActive(bh1750_address)) { continue; } + Wire.beginTransmission(bh1750_address); + Wire.write(BH1750_CONTINUOUS_HIGH_RES_MODE); + if (!Wire.endTransmission()) { + I2cSetActiveFound(bh1750_address, bh1750_types); + bh1750_type = 1; + break; + } + } +} + +void Bh1750EverySecond(void) +{ + + if (!Bh1750Read()) { + AddLogMissed(bh1750_types, bh1750_valid); + } +} + +void Bh1750Show(bool json) +{ + if (bh1750_valid) { + if (json) { + ResponseAppend_P(JSON_SNS_ILLUMINANCE, bh1750_types, bh1750_illuminance); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_ILLUMINANCE, bh1750_illuminance); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, bh1750_types, bh1750_illuminance); +#endif + } + } +} + + + + + +bool Xsns10(uint8_t function) +{ + if (!I2cEnabled(XI2C_11)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Bh1750Detect(); + } + else if (bh1750_type) { + switch (function) { + case FUNC_EVERY_SECOND: + Bh1750EverySecond(); + break; + case FUNC_JSON_APPEND: + Bh1750Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Bh1750Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_11_veml6070.ino" +# 89 "S:/Development/Tasmota/tasmota/xsns_11_veml6070.ino" +#ifdef USE_I2C +#ifdef USE_VEML6070 + + + + + + +#define XSNS_11 11 +#define XI2C_12 12 + +#define VEML6070_ADDR_H 0x39 +#define VEML6070_ADDR_L 0x38 +#define VEML6070_INTEGRATION_TIME 3 +#define VEML6070_ENABLE 1 +#define VEML6070_DISABLE 0 +#define VEML6070_RSET_DEFAULT 270000 +#define VEML6070_UV_MAX_INDEX 15 +#define VEML6070_UV_MAX_DEFAULT 11 +#define VEML6070_POWER_COEFFCIENT 0.025 +#define VEML6070_TABLE_COEFFCIENT 32.86270591 + + + + + +const char kVemlTypes[] PROGMEM = "VEML6070"; +double uv_risk_map[VEML6070_UV_MAX_INDEX] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +double uvrisk = 0; +double uvpower = 0; +uint16_t uvlevel = 0; +uint8_t veml6070_addr_low = VEML6070_ADDR_L; +uint8_t veml6070_addr_high = VEML6070_ADDR_H; +uint8_t itime = VEML6070_INTEGRATION_TIME; +uint8_t veml6070_type = 0; +char veml6070_name[9]; +char str_uvrisk_text[10]; + + + +void Veml6070Detect(void) +{ + if (I2cActive(VEML6070_ADDR_L)) { return; } + + + Wire.beginTransmission(VEML6070_ADDR_L); + Wire.write((itime << 2) | 0x02); + uint8_t status = Wire.endTransmission(); + + if (!status) { + veml6070_type = 1; + Veml6070UvTableInit(); + uint8_t veml_model = 0; + GetTextIndexed(veml6070_name, sizeof(veml6070_name), veml_model, kVemlTypes); + I2cSetActiveFound(VEML6070_ADDR_L, veml6070_name); + } +} + + + +void Veml6070UvTableInit(void) +{ + + for (uint32_t i = 0; i < VEML6070_UV_MAX_INDEX; i++) { +#ifdef USE_VEML6070_RSET + if ( (USE_VEML6070_RSET >= 220000) && (USE_VEML6070_RSET <= 1000000) ) { + uv_risk_map[i] = ( (USE_VEML6070_RSET / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1); + } else { + uv_risk_map[i] = ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 resistor error %d"), USE_VEML6070_RSET); + } +#else + uv_risk_map[i] = ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 resistor default used %d"), VEML6070_RSET_DEFAULT); +#endif + } +} + + + +void Veml6070EverySecond(void) +{ + + Veml6070ModeCmd(1); + uvlevel = Veml6070ReadUv(); + uvrisk = Veml6070UvRiskLevel(uvlevel); + uvpower = Veml6070UvPower(uvrisk); + Veml6070ModeCmd(0); +} + + + +void Veml6070ModeCmd(bool mode_cmd) +{ + + + Wire.beginTransmission(VEML6070_ADDR_L); + Wire.write((mode_cmd << 0) | 0x02 | (itime << 2)); + uint8_t status = Wire.endTransmission(); + + if (!status) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 mode_cmd")); + } +} + + + +uint16_t Veml6070ReadUv(void) +{ + uint16_t uv_raw = 0; + + if (Wire.requestFrom(VEML6070_ADDR_H, 1) != 1) { + return -1; + } + uv_raw = Wire.read(); + uv_raw <<= 8; + + if (Wire.requestFrom(VEML6070_ADDR_L, 1) != 1) { + return -1; + } + uv_raw |= Wire.read(); + + return uv_raw; +} + + + +double Veml6070UvRiskLevel(uint16_t uv_level) +{ + double risk = 0; + if (uv_level < uv_risk_map[VEML6070_UV_MAX_INDEX-1]) { + risk = (double)uv_level / uv_risk_map[0]; + + if ( (risk >= 0) && (risk <= 2.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_1); } + else if ( (risk >= 3.0) && (risk <= 5.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_2); } + else if ( (risk >= 6.0) && (risk <= 7.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_3); } + else if ( (risk >= 8.0) && (risk <= 10.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_4); } + else if ( (risk >= 11.0) && (risk <= 12.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_5); } + else if ( (risk >= 13.0) && (risk <= 25.0) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_6); } + else { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_7); } + return risk; + } else { + + snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_7); + return ( risk = 99 ); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 out of range %d"), risk); + } +} + + + +double Veml6070UvPower(double uvrisk) +{ + + double power = 0; + return ( power = VEML6070_POWER_COEFFCIENT * uvrisk ); +} + + + + +#ifdef USE_WEBSERVER + +#ifdef USE_VEML6070_SHOW_RAW + const char HTTP_SNS_UV_LEVEL[] PROGMEM = "{s}VEML6070 " D_UV_LEVEL "{m}%s " D_UNIT_INCREMENTS "{e}"; +#endif + + const char HTTP_SNS_UV_INDEX[] PROGMEM = "{s}VEML6070 " D_UV_INDEX "{m}%s %s{e}"; + const char HTTP_SNS_UV_POWER[] PROGMEM = "{s}VEML6070 " D_UV_POWER "{m}%s " D_UNIT_WATT_METER_QUADRAT "{e}"; +#endif + + + +void Veml6070Show(bool json) +{ + + char str_uvlevel[33]; + dtostrfd((double)uvlevel, 0, str_uvlevel); + char str_uvrisk[33]; + dtostrfd(uvrisk, 2, str_uvrisk); + char str_uvpower[33]; + dtostrfd(uvpower, 3, str_uvpower); + if (json) { +#ifdef USE_VEML6070_SHOW_RAW + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_UV_LEVEL "\":%s,\"" D_JSON_UV_INDEX "\":%s,\"" D_JSON_UV_INDEX_TEXT "\":\"%s\",\"" D_JSON_UV_POWER "\":%s}"), + veml6070_name, str_uvlevel, str_uvrisk, str_uvrisk_text, str_uvpower); +#else + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_UV_INDEX "\":%s,\"" D_JSON_UV_INDEX_TEXT "\":\"%s\",\"" D_JSON_UV_POWER "\":%s}"), + veml6070_name, str_uvrisk, str_uvrisk_text, str_uvpower); +#endif +#ifdef USE_DOMOTICZ + if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, uvlevel); } +#endif +#ifdef USE_WEBSERVER + } else { +#ifdef USE_VEML6070_SHOW_RAW + WSContentSend_PD(HTTP_SNS_UV_LEVEL, str_uvlevel); +#endif + WSContentSend_PD(HTTP_SNS_UV_INDEX, str_uvrisk, str_uvrisk_text); + WSContentSend_PD(HTTP_SNS_UV_POWER, str_uvpower); +#endif + } +} + + + + + +bool Xsns11(uint8_t function) +{ + if (!I2cEnabled(XI2C_12)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Veml6070Detect(); + } + else if (veml6070_type) { + switch (function) { + case FUNC_EVERY_SECOND: + Veml6070EverySecond(); + break; + case FUNC_JSON_APPEND: + Veml6070Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Veml6070Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_12_ads1115.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_12_ads1115.ino" +#ifdef USE_I2C +#ifdef USE_ADS1115 +# 43 "S:/Development/Tasmota/tasmota/xsns_12_ads1115.ino" +#define XSNS_12 12 +#define XI2C_13 13 + +#define ADS1115_ADDRESS_ADDR_GND 0x48 +#define ADS1115_ADDRESS_ADDR_VDD 0x49 +#define ADS1115_ADDRESS_ADDR_SDA 0x4A +#define ADS1115_ADDRESS_ADDR_SCL 0x4B + +#define ADS1115_CONVERSIONDELAY (8) + + + + +#define ADS1115_REG_POINTER_MASK (0x03) +#define ADS1115_REG_POINTER_CONVERT (0x00) +#define ADS1115_REG_POINTER_CONFIG (0x01) +#define ADS1115_REG_POINTER_LOWTHRESH (0x02) +#define ADS1115_REG_POINTER_HITHRESH (0x03) + + + + +#define ADS1115_REG_CONFIG_OS_MASK (0x8000) +#define ADS1115_REG_CONFIG_OS_SINGLE (0x8000) +#define ADS1115_REG_CONFIG_OS_BUSY (0x0000) +#define ADS1115_REG_CONFIG_OS_NOTBUSY (0x8000) + +#define ADS1115_REG_CONFIG_MUX_MASK (0x7000) +#define ADS1115_REG_CONFIG_MUX_DIFF_0_1 (0x0000) +#define ADS1115_REG_CONFIG_MUX_DIFF_0_3 (0x1000) +#define ADS1115_REG_CONFIG_MUX_DIFF_1_3 (0x2000) +#define ADS1115_REG_CONFIG_MUX_DIFF_2_3 (0x3000) +#define ADS1115_REG_CONFIG_MUX_SINGLE_0 (0x4000) +#define ADS1115_REG_CONFIG_MUX_SINGLE_1 (0x5000) +#define ADS1115_REG_CONFIG_MUX_SINGLE_2 (0x6000) +#define ADS1115_REG_CONFIG_MUX_SINGLE_3 (0x7000) + +#define ADS1115_REG_CONFIG_PGA_MASK (0x0E00) +#define ADS1115_REG_CONFIG_PGA_6_144V (0x0000) +#define ADS1115_REG_CONFIG_PGA_4_096V (0x0200) +#define ADS1115_REG_CONFIG_PGA_2_048V (0x0400) +#define ADS1115_REG_CONFIG_PGA_1_024V (0x0600) +#define ADS1115_REG_CONFIG_PGA_0_512V (0x0800) +#define ADS1115_REG_CONFIG_PGA_0_256V (0x0A00) + +#define ADS1115_REG_CONFIG_MODE_MASK (0x0100) +#define ADS1115_REG_CONFIG_MODE_CONTIN (0x0000) +#define ADS1115_REG_CONFIG_MODE_SINGLE (0x0100) + +#define ADS1115_REG_CONFIG_DR_MASK (0x00E0) +#define ADS1115_REG_CONFIG_DR_128SPS (0x0000) +#define ADS1115_REG_CONFIG_DR_250SPS (0x0020) +#define ADS1115_REG_CONFIG_DR_490SPS (0x0040) +#define ADS1115_REG_CONFIG_DR_920SPS (0x0060) +#define ADS1115_REG_CONFIG_DR_1600SPS (0x0080) +#define ADS1115_REG_CONFIG_DR_2400SPS (0x00A0) +#define ADS1115_REG_CONFIG_DR_3300SPS (0x00C0) +#define ADS1115_REG_CONFIG_DR_6000SPS (0x00E0) + +#define ADS1115_REG_CONFIG_CMODE_MASK (0x0010) +#define ADS1115_REG_CONFIG_CMODE_TRAD (0x0000) +#define ADS1115_REG_CONFIG_CMODE_WINDOW (0x0010) + +#define ADS1115_REG_CONFIG_CPOL_MASK (0x0008) +#define ADS1115_REG_CONFIG_CPOL_ACTVLOW (0x0000) +#define ADS1115_REG_CONFIG_CPOL_ACTVHI (0x0008) + +#define ADS1115_REG_CONFIG_CLAT_MASK (0x0004) +#define ADS1115_REG_CONFIG_CLAT_NONLAT (0x0000) +#define ADS1115_REG_CONFIG_CLAT_LATCH (0x0004) + +#define ADS1115_REG_CONFIG_CQUE_MASK (0x0003) +#define ADS1115_REG_CONFIG_CQUE_1CONV (0x0000) +#define ADS1115_REG_CONFIG_CQUE_2CONV (0x0001) +#define ADS1115_REG_CONFIG_CQUE_4CONV (0x0002) +#define ADS1115_REG_CONFIG_CQUE_NONE (0x0003) + +struct ADS1115 { + uint8_t count = 0; + uint8_t address; + uint8_t addresses[4] = { ADS1115_ADDRESS_ADDR_GND, ADS1115_ADDRESS_ADDR_VDD, ADS1115_ADDRESS_ADDR_SDA, ADS1115_ADDRESS_ADDR_SCL }; + uint8_t found[4] = {false,false,false,false}; +} Ads1115; + + + +void Ads1115StartComparator(uint8_t channel, uint16_t mode) +{ + + uint16_t config = mode | + ADS1115_REG_CONFIG_CQUE_NONE | + ADS1115_REG_CONFIG_CLAT_NONLAT | + ADS1115_REG_CONFIG_PGA_6_144V | + ADS1115_REG_CONFIG_CPOL_ACTVLOW | + ADS1115_REG_CONFIG_CMODE_TRAD | + ADS1115_REG_CONFIG_DR_6000SPS; + + + config |= (ADS1115_REG_CONFIG_MUX_SINGLE_0 + (0x1000 * channel)); + + + I2cWrite16(Ads1115.address, ADS1115_REG_POINTER_CONFIG, config); +} + +int16_t Ads1115GetConversion(uint8_t channel) +{ + Ads1115StartComparator(channel, ADS1115_REG_CONFIG_MODE_SINGLE); + + delay(ADS1115_CONVERSIONDELAY); + + I2cRead16(Ads1115.address, ADS1115_REG_POINTER_CONVERT); + + Ads1115StartComparator(channel, ADS1115_REG_CONFIG_MODE_CONTIN); + delay(ADS1115_CONVERSIONDELAY); + + uint16_t res = I2cRead16(Ads1115.address, ADS1115_REG_POINTER_CONVERT); + return (int16_t)res; +} + + + +void Ads1115Detect(void) +{ + for (uint32_t i = 0; i < sizeof(Ads1115.addresses); i++) { + if (!Ads1115.found[i]) { + Ads1115.address = Ads1115.addresses[i]; + if (I2cActive(Ads1115.address)) { continue; } + uint16_t buffer; + if (I2cValidRead16(&buffer, Ads1115.address, ADS1115_REG_POINTER_CONVERT) && + I2cValidRead16(&buffer, Ads1115.address, ADS1115_REG_POINTER_CONFIG)) { + Ads1115StartComparator(i, ADS1115_REG_CONFIG_MODE_CONTIN); + I2cSetActiveFound(Ads1115.address, "ADS1115"); + Ads1115.found[i] = 1; + Ads1115.count++; + } + } + } +} + +void Ads1115Show(bool json) +{ + int16_t values[4]; + + for (uint32_t t = 0; t < sizeof(Ads1115.addresses); t++) { + + if (Ads1115.found[t]) { + + uint8_t old_address = Ads1115.address; + Ads1115.address = Ads1115.addresses[t]; + for (uint32_t i = 0; i < 4; i++) { + values[i] = Ads1115GetConversion(i); + + } + Ads1115.address = old_address; + + char label[15]; + if (1 == Ads1115.count) { + + snprintf_P(label, sizeof(label), PSTR("ADS1115")); + } else { + + snprintf_P(label, sizeof(label), PSTR("ADS1115%c%02x"), IndexSeparator(), Ads1115.addresses[t]); + } + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{"), label); + for (uint32_t i = 0; i < 4; i++) { + ResponseAppend_P(PSTR("%s\"A%d\":%d"), (0 == i) ? "" : ",", i, values[i]); + } + ResponseJsonEnd(); + } +#ifdef USE_WEBSERVER + else { + for (uint32_t i = 0; i < 4; i++) { + WSContentSend_PD(HTTP_SNS_ANALOG, label, i, values[i]); + } + } +#endif + } + } +} + + + + + +bool Xsns12(uint8_t function) +{ + if (!I2cEnabled(XI2C_13)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Ads1115Detect(); + } + else if (Ads1115.count) { + switch (function) { + case FUNC_JSON_APPEND: + Ads1115Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Ads1115Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_13_ina219.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_13_ina219.ino" +#ifdef USE_I2C +#ifdef USE_INA219 +# 30 "S:/Development/Tasmota/tasmota/xsns_13_ina219.ino" +#define XSNS_13 13 +#define XI2C_14 14 + +#define INA219_ADDRESS1 (0x40) +#define INA219_ADDRESS2 (0x41) +#define INA219_ADDRESS3 (0x44) +#define INA219_ADDRESS4 (0x45) + +#define INA219_READ (0x01) +#define INA219_REG_CONFIG (0x00) + +#define INA219_CONFIG_RESET (0x8000) + +#define INA219_CONFIG_BVOLTAGERANGE_MASK (0x2000) +#define INA219_CONFIG_BVOLTAGERANGE_16V (0x0000) +#define INA219_CONFIG_BVOLTAGERANGE_32V (0x2000) + +#define INA219_CONFIG_GAIN_MASK (0x1800) +#define INA219_CONFIG_GAIN_1_40MV (0x0000) +#define INA219_CONFIG_GAIN_2_80MV (0x0800) +#define INA219_CONFIG_GAIN_4_160MV (0x1000) +#define INA219_CONFIG_GAIN_8_320MV (0x1800) + +#define INA219_CONFIG_BADCRES_MASK (0x0780) +#define INA219_CONFIG_BADCRES_9BIT (0x0080) +#define INA219_CONFIG_BADCRES_10BIT (0x0100) +#define INA219_CONFIG_BADCRES_11BIT (0x0200) +#define INA219_CONFIG_BADCRES_12BIT (0x0400) + +#define INA219_CONFIG_SADCRES_MASK (0x0078) +#define INA219_CONFIG_SADCRES_9BIT_1S_84US (0x0000) +#define INA219_CONFIG_SADCRES_10BIT_1S_148US (0x0008) +#define INA219_CONFIG_SADCRES_11BIT_1S_276US (0x0010) +#define INA219_CONFIG_SADCRES_12BIT_1S_532US (0x0018) +#define INA219_CONFIG_SADCRES_12BIT_2S_1060US (0x0048) +#define INA219_CONFIG_SADCRES_12BIT_4S_2130US (0x0050) +#define INA219_CONFIG_SADCRES_12BIT_8S_4260US (0x0058) +#define INA219_CONFIG_SADCRES_12BIT_16S_8510US (0x0060) +#define INA219_CONFIG_SADCRES_12BIT_32S_17MS (0x0068) +#define INA219_CONFIG_SADCRES_12BIT_64S_34MS (0x0070) +#define INA219_CONFIG_SADCRES_12BIT_128S_69MS (0x0078) + +#define INA219_CONFIG_MODE_MASK (0x0007) +#define INA219_CONFIG_MODE_POWERDOWN (0x0000) +#define INA219_CONFIG_MODE_SVOLT_TRIGGERED (0x0001) +#define INA219_CONFIG_MODE_BVOLT_TRIGGERED (0x0002) +#define INA219_CONFIG_MODE_SANDBVOLT_TRIGGERED (0x0003) +#define INA219_CONFIG_MODE_ADCOFF (0x0004) +#define INA219_CONFIG_MODE_SVOLT_CONTINUOUS (0x0005) +#define INA219_CONFIG_MODE_BVOLT_CONTINUOUS (0x0006) +#define INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS (0x0007) + +#define INA219_REG_SHUNTVOLTAGE (0x01) +#define INA219_REG_BUSVOLTAGE (0x02) +#define INA219_REG_POWER (0x03) +#define INA219_REG_CURRENT (0x04) +#define INA219_REG_CALIBRATION (0x05) + +uint8_t ina219_type[4] = {0,0,0,0}; +uint8_t ina219_addresses[] = { INA219_ADDRESS1, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 }; + +uint32_t ina219_cal_value = 0; + +uint32_t ina219_current_divider_ma = 0; + +uint8_t ina219_valid[4] = {0,0,0,0}; +float ina219_voltage[4] = {0,0,0,0}; +float ina219_current[4] = {0,0,0,0}; +char ina219_types[] = "INA219"; +uint8_t ina219_count = 0; + +bool Ina219SetCalibration(uint8_t mode, uint16_t addr) +{ + uint16_t config = 0; + + switch (mode &3) { + case 0: + case 3: + ina219_cal_value = 4096; + ina219_current_divider_ma = 10; + config = INA219_CONFIG_BVOLTAGERANGE_32V | INA219_CONFIG_GAIN_8_320MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; + break; + case 1: + ina219_cal_value = 10240; + ina219_current_divider_ma = 25; + config |= INA219_CONFIG_BVOLTAGERANGE_32V | INA219_CONFIG_GAIN_8_320MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; + break; + case 2: + ina219_cal_value = 8192; + ina219_current_divider_ma = 20; + config |= INA219_CONFIG_BVOLTAGERANGE_16V | INA219_CONFIG_GAIN_1_40MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; + break; + } + + bool success = I2cWrite16(addr, INA219_REG_CALIBRATION, ina219_cal_value); + if (success) { + + I2cWrite16(addr, INA219_REG_CONFIG, config); + } + return success; +} + +float Ina219GetShuntVoltage_mV(uint16_t addr) +{ + + int16_t value = I2cReadS16(addr, INA219_REG_SHUNTVOLTAGE); + + return value * 0.01; +} + +float Ina219GetBusVoltage_V(uint16_t addr) +{ + + + int16_t value = (int16_t)(((uint16_t)I2cReadS16(addr, INA219_REG_BUSVOLTAGE) >> 3) * 4); + + return value * 0.001; +} + +float Ina219GetCurrent_mA(uint16_t addr) +{ + + + + I2cWrite16(addr, INA219_REG_CALIBRATION, ina219_cal_value); + + + float value = I2cReadS16(addr, INA219_REG_CURRENT); + value /= ina219_current_divider_ma; + + return value; +} + +bool Ina219Read(void) +{ + for (int i=0; i= 0) && (XdrvMailbox.payload <= 2)) { + Settings.ina219_mode = XdrvMailbox.payload; + restart_flag = 2; + } + Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_13, Settings.ina219_mode); + + return true; +} + + + +void Ina219Detect(void) +{ + for (uint32_t i = 0; i < sizeof(ina219_type); i++) { + uint16_t addr = ina219_addresses[i]; + if (I2cActive(addr)) { continue; } + if (Ina219SetCalibration(Settings.ina219_mode, addr)) { + I2cSetActiveFound(addr, ina219_types); + ina219_type[i] = 1; + ina219_count++; + } + } +} + +void Ina219EverySecond(void) +{ + + Ina219Read(); +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_INA219_DATA[] PROGMEM = + "{s}%s " D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" + "{s}%s " D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" + "{s}%s " D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"; +#endif + +void Ina219Show(bool json) +{ + int num_found=0; + for (int i=0; i1) + snprintf_P(name, sizeof(name), PSTR("%s%c%d"), ina219_types, IndexSeparator(), sensor_num); + else + snprintf_P(name, sizeof(name), PSTR("%s"), ina219_types); + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"Id\":%02x,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_POWERUSAGE "\":%s}"), + name, ina219_addresses[i], voltage, current, power); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_VOLTAGE, voltage); + DomoticzSensor(DZ_CURRENT, current); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_INA219_DATA, name, voltage, name, current, name, power); +#endif + } + } +} + + + + + +bool Xsns13(uint8_t function) +{ + if (!I2cEnabled(XI2C_14)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Ina219Detect(); + } + else if (ina219_count) { + switch (function) { + case FUNC_COMMAND_SENSOR: + if (XSNS_13 == XdrvMailbox.index) { + result = Ina219CommandSensor(); + } + break; + case FUNC_EVERY_SECOND: + Ina219EverySecond(); + break; + case FUNC_JSON_APPEND: + Ina219Show(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Ina219Show(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_14_sht3x.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_14_sht3x.ino" +#ifdef USE_I2C +#ifdef USE_SHT3X + + + + + + +#define XSNS_14 14 +#define XI2C_15 15 + +#define SHT3X_ADDR_GND 0x44 +#define SHT3X_ADDR_VDD 0x45 +#define SHTC3_ADDR 0x70 + +#define SHT3X_MAX_SENSORS 3 + +const char kShtTypes[] PROGMEM = "SHT3X|SHT3X|SHTC3"; +uint8_t sht3x_addresses[] = { SHT3X_ADDR_GND, SHT3X_ADDR_VDD, SHTC3_ADDR }; + +uint8_t sht3x_count = 0; +struct SHT3XSTRUCT { + uint8_t address; + char types[6]; +} sht3x_sensors[SHT3X_MAX_SENSORS]; + +bool Sht3xRead(float &t, float &h, uint8_t sht3x_address) +{ + unsigned int data[6]; + + t = NAN; + h = NAN; + + Wire.beginTransmission(sht3x_address); + if (SHTC3_ADDR == sht3x_address) { + Wire.write(0x35); + Wire.write(0x17); + Wire.endTransmission(); + Wire.beginTransmission(sht3x_address); + Wire.write(0x78); + Wire.write(0x66); + } else { + Wire.write(0x2C); + Wire.write(0x06); + } + if (Wire.endTransmission() != 0) { + return false; + } + delay(30); + Wire.requestFrom(sht3x_address, (uint8_t)6); + for (uint32_t i = 0; i < 6; i++) { + data[i] = Wire.read(); + }; + t = ConvertTemp((float)((((data[0] << 8) | data[1]) * 175) / 65535.0) - 45); + h = ConvertHumidity((float)((((data[3] << 8) | data[4]) * 100) / 65535.0)); + return (!isnan(t) && !isnan(h) && (h != 0)); +} + + + +void Sht3xDetect(void) +{ + for (uint32_t i = 0; i < SHT3X_MAX_SENSORS; i++) { + if (I2cActive(sht3x_addresses[i])) { continue; } + float t; + float h; + if (Sht3xRead(t, h, sht3x_addresses[i])) { + sht3x_sensors[sht3x_count].address = sht3x_addresses[i]; + GetTextIndexed(sht3x_sensors[sht3x_count].types, sizeof(sht3x_sensors[sht3x_count].types), i, kShtTypes); + I2cSetActiveFound(sht3x_sensors[sht3x_count].address, sht3x_sensors[sht3x_count].types); + sht3x_count++; + } + } +} + +void Sht3xShow(bool json) +{ + for (uint32_t i = 0; i < sht3x_count; i++) { + float t; + float h; + if (Sht3xRead(t, h, sht3x_sensors[i].address)) { + char temperature[33]; + dtostrfd(t, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(h, Settings.flag2.humidity_resolution, humidity); + char types[11]; + snprintf_P(types, sizeof(types), PSTR("%s%c0x%02X"), sht3x_sensors[i].types, IndexSeparator(), sht3x_sensors[i].address); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, types, temperature, humidity); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif + +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, t); + KnxSensor(KNX_HUMIDITY, h); + } +#endif + +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, types, humidity); +#endif + } + } + } +} + + + + + +bool Xsns14(uint8_t function) +{ + if (!I2cEnabled(XI2C_15)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Sht3xDetect(); + } + else if (sht3x_count) { + switch (function) { + case FUNC_JSON_APPEND: + Sht3xShow(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Sht3xShow(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_15_mhz19.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_15_mhz19.ino" +#ifdef USE_MHZ19 +# 33 "S:/Development/Tasmota/tasmota/xsns_15_mhz19.ino" +#define XSNS_15 15 + +enum MhzFilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FILTER_FAST, MHZ19_FILTER_MEDIUM, MHZ19_FILTER_SLOW}; + +#define MHZ19_FILTER_OPTION MHZ19_FILTER_FAST +# 58 "S:/Development/Tasmota/tasmota/xsns_15_mhz19.ino" +#include + +#ifndef CO2_LOW +#define CO2_LOW 800 +#endif +#ifndef CO2_HIGH +#define CO2_HIGH 1200 +#endif + +#define MHZ19_READ_TIMEOUT 400 +#define MHZ19_RETRY_COUNT 8 + +TasmotaSerial *MhzSerial; + +const char kMhzModels[] PROGMEM = "|B"; + +const char ABC_ENABLED[] = "ABC is Enabled"; +const char ABC_DISABLED[] = "ABC is Disabled"; + +enum MhzCommands { MHZ_CMND_READPPM, MHZ_CMND_ABCENABLE, MHZ_CMND_ABCDISABLE, MHZ_CMND_ZEROPOINT, MHZ_CMND_RESET, MHZ_CMND_RANGE_1000, MHZ_CMND_RANGE_2000, MHZ_CMND_RANGE_3000, MHZ_CMND_RANGE_5000 }; +const uint8_t kMhzCommands[][4] PROGMEM = { + + {0x86,0x00,0x00,0x00}, + {0x79,0xA0,0x00,0x00}, + {0x79,0x00,0x00,0x00}, + {0x87,0x00,0x00,0x00}, + {0x8D,0x00,0x00,0x00}, + {0x99,0x00,0x03,0xE8}, + {0x99,0x00,0x07,0xD0}, + {0x99,0x00,0x0B,0xB8}, + {0x99,0x00,0x13,0x88}}; + +uint8_t mhz_type = 1; +uint16_t mhz_last_ppm = 0; +uint8_t mhz_filter = MHZ19_FILTER_OPTION; +bool mhz_abc_must_apply = false; + +float mhz_temperature = 0; +uint8_t mhz_retry = MHZ19_RETRY_COUNT; +uint8_t mhz_received = 0; +uint8_t mhz_state = 0; + + + +uint8_t MhzCalculateChecksum(uint8_t *array) +{ + uint8_t checksum = 0; + for (uint32_t i = 1; i < 8; i++) { + checksum += array[i]; + } + checksum = 255 - checksum; + return (checksum +1); +} + +size_t MhzSendCmd(uint8_t command_id) +{ + uint8_t mhz_send[9] = { 0 }; + + mhz_send[0] = 0xFF; + mhz_send[1] = 0x01; + memcpy_P(&mhz_send[2], kMhzCommands[command_id], sizeof(uint16_t)); + + + + + memcpy_P(&mhz_send[6], kMhzCommands[command_id] + sizeof(uint16_t), sizeof(uint16_t)); + mhz_send[8] = MhzCalculateChecksum(mhz_send); + + + + return MhzSerial->write(mhz_send, sizeof(mhz_send)); +} + + + +bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s) +{ + if (1 == s) { + return false; + } + if (mhz_last_ppm < 400 || mhz_last_ppm > 5000) { + + + mhz_last_ppm = ppm; + return true; + } + int32_t difference = ppm - mhz_last_ppm; + if (s > 0 && s < 64 && mhz_filter != MHZ19_FILTER_OFF) { +# 154 "S:/Development/Tasmota/tasmota/xsns_15_mhz19.ino" + difference *= s; + difference /= 64; + } + if (MHZ19_FILTER_OFF == mhz_filter) { + if (s != 0 && s != 64) { + return false; + } + } else { + difference >>= (mhz_filter -1); + } + mhz_last_ppm = static_cast(mhz_last_ppm + difference); + return true; +} + +void MhzEverySecond(void) +{ + mhz_state++; + if (8 == mhz_state) { + mhz_state = 0; + + if (mhz_retry) { + mhz_retry--; + if (!mhz_retry) { + mhz_last_ppm = 0; + mhz_temperature = 0; + } + } + + MhzSerial->flush(); + MhzSendCmd(MHZ_CMND_READPPM); + mhz_received = 0; + } + + if ((mhz_state > 2) && !mhz_received) { + uint8_t mhz_response[9]; + + unsigned long start = millis(); + uint8_t counter = 0; + while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) { + if (MhzSerial->available() > 0) { + mhz_response[counter++] = MhzSerial->read(); + } else { + delay(5); + } + } + + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, mhz_response, counter); + + if (counter < 9) { + + return; + } + + uint8_t crc = MhzCalculateChecksum(mhz_response); + if (mhz_response[8] != crc) { + + return; + } + if (0xFF != mhz_response[0] || 0x86 != mhz_response[1]) { + + return; + } + + mhz_received = 1; + + uint16_t u = (mhz_response[6] << 8) | mhz_response[7]; + if (15000 == u) { + if (Settings.SensorBits1.mhz19b_abc_disable) { + + + mhz_abc_must_apply = true; + } + } else { + uint16_t ppm = (mhz_response[2] << 8) | mhz_response[3]; + mhz_temperature = ConvertTemp((float)mhz_response[4] - 40); + uint8_t s = mhz_response[5]; + mhz_type = (s) ? 1 : 2; + if (MhzCheckAndApplyFilter(ppm, s)) { + mhz_retry = MHZ19_RETRY_COUNT; + LightSetSignal(CO2_LOW, CO2_HIGH, mhz_last_ppm); + + if (0 == s || 64 == s) { + if (mhz_abc_must_apply) { + mhz_abc_must_apply = false; + if (!Settings.SensorBits1.mhz19b_abc_disable) { + MhzSendCmd(MHZ_CMND_ABCENABLE); + } else { + MhzSendCmd(MHZ_CMND_ABCDISABLE); + } + } + } + + } + } + + } +} +# 266 "S:/Development/Tasmota/tasmota/xsns_15_mhz19.ino" +#define D_JSON_RANGE_1000 "1000 ppm range" +#define D_JSON_RANGE_2000 "2000 ppm range" +#define D_JSON_RANGE_3000 "3000 ppm range" +#define D_JSON_RANGE_5000 "5000 ppm range" + +bool MhzCommandSensor(void) +{ + bool serviced = true; + + switch (XdrvMailbox.payload) { + case 0: + Settings.SensorBits1.mhz19b_abc_disable = true; + MhzSendCmd(MHZ_CMND_ABCDISABLE); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED); + break; + case 1: + Settings.SensorBits1.mhz19b_abc_disable = false; + MhzSendCmd(MHZ_CMND_ABCENABLE); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED); + break; + case 2: + MhzSendCmd(MHZ_CMND_ZEROPOINT); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_ZERO_POINT_CALIBRATION); + break; + case 9: + MhzSendCmd(MHZ_CMND_RESET); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RESET); + break; + case 1000: + MhzSendCmd(MHZ_CMND_RANGE_1000); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_1000); + break; + case 2000: + MhzSendCmd(MHZ_CMND_RANGE_2000); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_2000); + break; + case 3000: + MhzSendCmd(MHZ_CMND_RANGE_3000); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_3000); + break; + case 5000: + MhzSendCmd(MHZ_CMND_RANGE_5000); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_5000); + break; + default: + if (!Settings.SensorBits1.mhz19b_abc_disable) { + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED); + } else { + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED); + } + } + + return serviced; +} + + + +void MhzInit(void) +{ + mhz_type = 0; + if ((pin[GPIO_MHZ_RXD] < 99) && (pin[GPIO_MHZ_TXD] < 99)) { + MhzSerial = new TasmotaSerial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD], 1); + if (MhzSerial->begin(9600)) { + if (MhzSerial->hardwareSerial()) { ClaimSerial(); } + mhz_type = 1; + } + + } +} + +void MhzShow(bool json) +{ + char types[7] = "MHZ19B"; + char temperature[33]; + dtostrfd(mhz_temperature, Settings.flag2.temperature_resolution, temperature); + char model[3]; + GetTextIndexed(model, sizeof(model), mhz_type -1, kMhzModels); + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_MODEL "\":\"%s\",\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s}"), types, model, mhz_last_ppm, temperature); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_AIRQUALITY, mhz_last_ppm); + DomoticzSensor(DZ_TEMP, temperature); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_CO2, types, mhz_last_ppm); + WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit()); +#endif + } +} + + + + + +bool Xsns15(uint8_t function) +{ + bool result = false; + + if (mhz_type) { + switch (function) { + case FUNC_INIT: + MhzInit(); + break; + case FUNC_EVERY_SECOND: + MhzEverySecond(); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_15 == XdrvMailbox.index) { + result = MhzCommandSensor(); + } + break; + case FUNC_JSON_APPEND: + MhzShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MhzShow(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_16_tsl2561.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_16_tsl2561.ino" +#ifdef USE_I2C +#ifdef USE_TSL2561 +# 30 "S:/Development/Tasmota/tasmota/xsns_16_tsl2561.ino" +#define XSNS_16 16 +#define XI2C_16 16 + +#include + +Tsl2561 Tsl(Wire); + +uint8_t tsl2561_type = 0; +uint8_t tsl2561_valid = 0; +uint32_t tsl2561_milliLux = 0; +char tsl2561_types[] = "TSL2561"; + +bool Tsl2561Read(void) +{ + if (tsl2561_valid) { tsl2561_valid--; } + + uint8_t id; + bool gain; + Tsl2561::exposure_t exposure; + uint16_t scaledFull, scaledIr; + uint32_t full, ir; + + if (Tsl.on()) { + if (Tsl.id(id) + && Tsl2561Util::autoGain(Tsl, gain, exposure, scaledFull, scaledIr) + && Tsl2561Util::normalizedLuminosity(gain, exposure, full = scaledFull, ir = scaledIr) + && Tsl2561Util::milliLux(full, ir, tsl2561_milliLux, Tsl2561::packageCS(id))) { + } else{ + tsl2561_milliLux = 0; + } + } + tsl2561_valid = SENSOR_MAX_MISS; + return true; +} + +void Tsl2561Detect(void) +{ + if (I2cSetDevice(0x29) || I2cSetDevice(0x39) || I2cSetDevice(0x49)) { + uint8_t id; + Tsl.begin(); + if (!Tsl.id(id)) return; + if (Tsl.on()) { + tsl2561_type = 1; + I2cSetActiveFound(Tsl.address(), tsl2561_types); + } + } +} + +void Tsl2561EverySecond(void) +{ + if (!(uptime %2)) { + + if (!Tsl2561Read()) { + AddLogMissed(tsl2561_types, tsl2561_valid); + } + } +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_TSL2561[] PROGMEM = + "{s}TSL2561 " D_ILLUMINANCE "{m}%u.%03u " D_UNIT_LUX "{e}"; +#endif + +void Tsl2561Show(bool json) +{ + if (tsl2561_valid) { + if (json) { + ResponseAppend_P(PSTR(",\"TSL2561\":{\"" D_JSON_ILLUMINANCE "\":%u.%03u}"), + tsl2561_milliLux / 1000, tsl2561_milliLux % 1000); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, (tsl2561_milliLux + 500) / 1000); } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TSL2561, tsl2561_milliLux / 1000, tsl2561_milliLux % 1000); +#endif + } + } +} + + + + + +bool Xsns16(uint8_t function) +{ + if (!I2cEnabled(XI2C_16)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Tsl2561Detect(); + } + else if (tsl2561_type) { + switch (function) { + case FUNC_EVERY_SECOND: + Tsl2561EverySecond(); + break; + case FUNC_JSON_APPEND: + Tsl2561Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Tsl2561Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_17_senseair.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_17_senseair.ino" +#ifdef USE_SENSEAIR +# 29 "S:/Development/Tasmota/tasmota/xsns_17_senseair.ino" +#define XSNS_17 17 + +#define SENSEAIR_MODBUS_SPEED 9600 +#define SENSEAIR_DEVICE_ADDRESS 0xFE +#define SENSEAIR_READ_REGISTER 0x04 + +#ifndef CO2_LOW +#define CO2_LOW 800 +#endif +#ifndef CO2_HIGH +#define CO2_HIGH 1200 +#endif + +#include +TasmotaModbus *SenseairModbus; + +const char kSenseairTypes[] PROGMEM = "Kx0|S8"; + +uint8_t senseair_type = 1; +char senseair_types[7]; + +uint16_t senseair_co2 = 0; +float senseair_temperature = 0; +float senseair_humidity = 0; + + + +const uint8_t start_addresses[] { 0x1A, 0x00, 0x03, 0x04, 0x05, 0x1C, 0x0A }; + +uint8_t senseair_read_state = 0; +uint8_t senseair_send_retry = 0; + +void Senseair250ms(void) +{ + + + + + uint16_t value = 0; + bool data_ready = SenseairModbus->ReceiveReady(); + + if (data_ready) { + uint8_t error = SenseairModbus->Receive16BitRegister(&value); + if (error) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir response error %d"), error); + } else { + switch(senseair_read_state) { + case 0: + senseair_type = 2; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir type id low %04X"), value); + break; + case 1: + if (value) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir error %04X"), value); + } + break; + case 2: + senseair_co2 = value; + LightSetSignal(CO2_LOW, CO2_HIGH, senseair_co2); + break; + case 3: + senseair_temperature = ConvertTemp((float)value / 100); + break; + case 4: + senseair_humidity = ConvertHumidity((float)value / 100); + break; + case 5: + { + bool relay_state = value >> 8 & 1; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir relay state %d"), relay_state); + break; + } + case 6: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir temp adjustment %d"), value); + break; + } + } + senseair_read_state++; + if (2 == senseair_type) { + if (3 == senseair_read_state) { + senseair_read_state = 1; + } + } else { + if (sizeof(start_addresses) == senseair_read_state) { + senseair_read_state = 1; + } + } + } + + if (0 == senseair_send_retry || data_ready) { + senseair_send_retry = 5; + SenseairModbus->Send(SENSEAIR_DEVICE_ADDRESS, SENSEAIR_READ_REGISTER, (uint16_t)start_addresses[senseair_read_state], 1); + } else { + senseair_send_retry--; + } + + +} + + + +void SenseairInit(void) +{ + senseair_type = 0; + if ((pin[GPIO_SAIR_RX] < 99) && (pin[GPIO_SAIR_TX] < 99)) { + SenseairModbus = new TasmotaModbus(pin[GPIO_SAIR_RX], pin[GPIO_SAIR_TX]); + uint8_t result = SenseairModbus->Begin(SENSEAIR_MODBUS_SPEED); + if (result) { + if (2 == result) { ClaimSerial(); } + senseair_type = 1; + } + } +} + +void SenseairShow(bool json) +{ + char temperature[33]; + dtostrfd(senseair_temperature, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(senseair_humidity, Settings.flag2.temperature_resolution, humidity); + GetTextIndexed(senseair_types, sizeof(senseair_types), senseair_type -1, kSenseairTypes); + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_CO2 "\":%d"), senseair_types, senseair_co2); + if (senseair_type != 2) { + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s"), temperature, humidity); + } + ResponseJsonEnd(); +#ifdef USE_DOMOTICZ + if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, senseair_co2); +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_CO2, senseair_types, senseair_co2); + if (senseair_type != 2) { + WSContentSend_PD(HTTP_SNS_TEMP, senseair_types, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, senseair_types, humidity); + } +#endif + } +} + + + + + +bool Xsns17(uint8_t function) +{ + bool result = false; + + if (senseair_type) { + switch (function) { + case FUNC_INIT: + SenseairInit(); + break; + case FUNC_EVERY_250_MSECOND: + Senseair250ms(); + break; + case FUNC_JSON_APPEND: + SenseairShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + SenseairShow(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_18_pms5003.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_18_pms5003.ino" +#ifdef USE_PMS5003 +# 31 "S:/Development/Tasmota/tasmota/xsns_18_pms5003.ino" +#define XSNS_18 18 + +#include + +TasmotaSerial *PmsSerial; + +uint8_t pms_type = 1; +uint8_t pms_valid = 0; + +struct pmsX003data { + uint16_t framelen; + uint16_t pm10_standard, pm25_standard, pm100_standard; + uint16_t pm10_env, pm25_env, pm100_env; +#ifdef PMS_MODEL_PMS3003 + uint16_t reserved1, reserved2, reserved3; +#else + uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um; + uint16_t unused; +#endif + uint16_t checksum; +} pms_data; + + + +bool PmsReadData(void) +{ + if (! PmsSerial->available()) { + return false; + } + while ((PmsSerial->peek() != 0x42) && PmsSerial->available()) { + PmsSerial->read(); + } +#ifdef PMS_MODEL_PMS3003 + if (PmsSerial->available() < 22) { +#else + if (PmsSerial->available() < 32) { +#endif + return false; + } + +#ifdef PMS_MODEL_PMS3003 + uint8_t buffer[22]; + PmsSerial->readBytes(buffer, 22); +#else + uint8_t buffer[32]; + PmsSerial->readBytes(buffer, 32); +#endif + uint16_t sum = 0; + PmsSerial->flush(); + +#ifdef PMS_MODEL_PMS3003 + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 22); +#else + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 32); +#endif + + +#ifdef PMS_MODEL_PMS3003 + for (uint32_t i = 0; i < 20; i++) { +#else + for (uint32_t i = 0; i < 30; i++) { +#endif + sum += buffer[i]; + } + +#ifdef PMS_MODEL_PMS3003 + uint16_t buffer_u16[10]; + for (uint32_t i = 0; i < 10; i++) { +#else + uint16_t buffer_u16[15]; + for (uint32_t i = 0; i < 15; i++) { +#endif + buffer_u16[i] = buffer[2 + i*2 + 1]; + buffer_u16[i] += (buffer[2 + i*2] << 8); + } +#ifdef PMS_MODEL_PMS3003 + if (sum != buffer_u16[9]) { +#else + if (sum != buffer_u16[14]) { +#endif + AddLog_P(LOG_LEVEL_DEBUG, PSTR("PMS: " D_CHECKSUM_FAILURE)); + return false; + } + +#ifdef PMS_MODEL_PMS3003 + memcpy((void *)&pms_data, (void *)buffer_u16, 20); +#else + memcpy((void *)&pms_data, (void *)buffer_u16, 30); +#endif + pms_valid = 10; + + return true; +} + + + +void PmsSecond(void) +{ + if (PmsReadData()) { + pms_valid = 10; + } else { + if (pms_valid) { + pms_valid--; + } + } +} + + + +void PmsInit(void) +{ + pms_type = 0; + if (pin[GPIO_PMS5003] < 99) { + PmsSerial = new TasmotaSerial(pin[GPIO_PMS5003], -1, 1); + if (PmsSerial->begin(9600)) { + if (PmsSerial->hardwareSerial()) { ClaimSerial(); } + pms_type = 1; + } + } +} + +#ifdef USE_WEBSERVER +#ifdef PMS_MODEL_PMS3003 +const char HTTP_PMS3003_SNS[] PROGMEM = + + + + "{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"; +#else +const char HTTP_PMS5003_SNS[] PROGMEM = + + + + "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}PMS5003 " D_PARTICALS_BEYOND " 0.3 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}PMS5003 " D_PARTICALS_BEYOND " 0.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}PMS5003 " D_PARTICALS_BEYOND " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}PMS5003 " D_PARTICALS_BEYOND " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}PMS5003 " D_PARTICALS_BEYOND " 5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}PMS5003 " D_PARTICALS_BEYOND " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"; +#endif +#endif + +void PmsShow(bool json) +{ + if (pms_valid) { + if (json) { +#ifdef PMS_MODEL_PMS3003 + ResponseAppend_P(PSTR(",\"PMS3003\":{\"CF1\":%d,\"CF2.5\":%d,\"CF10\":%d,\"PM1\":%d,\"PM2.5\":%d,\"PM10\":%d}"), + pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard, + pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env); +#else + ResponseAppend_P(PSTR(",\"PMS5003\":{\"CF1\":%d,\"CF2.5\":%d,\"CF10\":%d,\"PM1\":%d,\"PM2.5\":%d,\"PM10\":%d,\"PB0.3\":%d,\"PB0.5\":%d,\"PB1\":%d,\"PB2.5\":%d,\"PB5\":%d,\"PB10\":%d}"), + pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard, + pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env, + pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um); +#endif +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_COUNT, pms_data.pm10_env); + DomoticzSensor(DZ_VOLTAGE, pms_data.pm25_env); + DomoticzSensor(DZ_CURRENT, pms_data.pm100_env); + } +#endif +#ifdef USE_WEBSERVER + } else { + +#ifdef PMS_MODEL_PMS3003 + WSContentSend_PD(HTTP_PMS3003_SNS, + + pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env); +#else + WSContentSend_PD(HTTP_PMS5003_SNS, + + pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env, + pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um); +#endif +#endif + } + } +} + + + + + +bool Xsns18(uint8_t function) +{ + bool result = false; + + if (pms_type) { + switch (function) { + case FUNC_INIT: + PmsInit(); + break; + case FUNC_EVERY_SECOND: + PmsSecond(); + break; + case FUNC_JSON_APPEND: + PmsShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + PmsShow(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_19_mgs.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_19_mgs.ino" +#ifdef USE_I2C +#ifdef USE_MGS + + + + + + + +#define XSNS_19 19 +#define XI2C_17 17 + +#ifndef MGS_SENSOR_ADDR +#define MGS_SENSOR_ADDR 0x04 +#endif + +#include "MutichannelGasSensor.h" + +bool mgs_detected = false; + +void MGSInit(void) { + gas.begin(MGS_SENSOR_ADDR); +} + +void MGSPrepare(void) +{ + if (I2cActive(MGS_SENSOR_ADDR)) { return; } + + gas.begin(MGS_SENSOR_ADDR); + if (!gas.isError()) { + I2cSetActiveFound(MGS_SENSOR_ADDR, "MultiGas"); + mgs_detected = true; + } +} + +char* measure_gas(int gas_type, char* buffer) +{ + float f = gas.calcGas(gas_type); + dtostrfd(f, 2, buffer); + return buffer; +} + +#ifdef USE_WEBSERVER +const char HTTP_MGS_GAS[] PROGMEM = "{s}MGS %s{m}%s " D_UNIT_PARTS_PER_MILLION "{e}"; +#endif + +void MGSShow(bool json) +{ + char buffer[33]; + if (json) { + ResponseAppend_P(PSTR(",\"MGS\":{\"NH3\":%s"), measure_gas(NH3, buffer)); + ResponseAppend_P(PSTR(",\"CO\":%s"), measure_gas(CO, buffer)); + ResponseAppend_P(PSTR(",\"NO2\":%s"), measure_gas(NO2, buffer)); + ResponseAppend_P(PSTR(",\"C3H8\":%s"), measure_gas(C3H8, buffer)); + ResponseAppend_P(PSTR(",\"C4H10\":%s"), measure_gas(C4H10, buffer)); + ResponseAppend_P(PSTR(",\"CH4\":%s"), measure_gas(GAS_CH4, buffer)); + ResponseAppend_P(PSTR(",\"H2\":%s"), measure_gas(H2, buffer)); + ResponseAppend_P(PSTR(",\"C2H5OH\":%s}"), measure_gas(C2H5OH, buffer)); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_MGS_GAS, "NH3", measure_gas(NH3, buffer)); + WSContentSend_PD(HTTP_MGS_GAS, "CO", measure_gas(CO, buffer)); + WSContentSend_PD(HTTP_MGS_GAS, "NO2", measure_gas(NO2, buffer)); + WSContentSend_PD(HTTP_MGS_GAS, "C3H8", measure_gas(C3H8, buffer)); + WSContentSend_PD(HTTP_MGS_GAS, "C4H10", measure_gas(C4H10, buffer)); + WSContentSend_PD(HTTP_MGS_GAS, "CH4", measure_gas(GAS_CH4, buffer)); + WSContentSend_PD(HTTP_MGS_GAS, "H2", measure_gas(H2, buffer)); + WSContentSend_PD(HTTP_MGS_GAS, "C2H5OH", measure_gas(C2H5OH, buffer)); +#endif + } +} + + + + + +bool Xsns19(uint8_t function) +{ + if (!I2cEnabled(XI2C_17)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + MGSPrepare(); + } + else if (mgs_detected) { + switch (function) { + case FUNC_JSON_APPEND: + MGSShow(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MGSShow(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_20_novasds.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_20_novasds.ino" +#ifdef USE_NOVA_SDS +# 30 "S:/Development/Tasmota/tasmota/xsns_20_novasds.ino" +#define XSNS_20 20 + +#include + +#ifndef STARTING_OFFSET +#define STARTING_OFFSET 30 +#endif +#if STARTING_OFFSET < 10 +#error "Please set STARTING_OFFSET >= 10" +#endif +#ifndef NOVA_SDS_RECDATA_TIMEOUT +#define NOVA_SDS_RECDATA_TIMEOUT 150 +#endif +#ifndef NOVA_SDS_DEVICE_ID +#define NOVA_SDS_DEVICE_ID 0xFFFF +#endif + +TasmotaSerial *NovaSdsSerial; + +uint8_t novasds_type = 1; +uint8_t novasds_valid = 0; +uint8_t cont_mode = 1; + +struct sds011data { + uint16_t pm100; + uint16_t pm25; +} novasds_data; +uint16_t pm100_sum; +uint16_t pm25_sum; + + +#define NOVA_SDS_REPORTING_MODE 2 +#define NOVA_SDS_QUERY_DATA 4 +#define NOVA_SDS_SET_DEVICE_ID 5 +#define NOVA_SDS_SLEEP_AND_WORK 6 +#define NOVA_SDS_WORKING_PERIOD 8 +#define NOVA_SDS_CHECK_FIRMWARE_VER 7 + #define NOVA_SDS_QUERY_MODE 0 + #define NOVA_SDS_SET_MODE 1 + #define NOVA_SDS_REPORT_ACTIVE 0 + #define NOVA_SDS_REPORT_QUERY 1 + #define NOVA_SDS_SLEEP 0 + #define NOVA_SDS_WORK 1 + + +bool NovaSdsCommand(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint16_t sensorid, uint8_t *buffer) +{ + uint8_t novasds_cmnd[19] = {0xAA, 0xB4, byte1, byte2, byte3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (uint8_t)(sensorid & 0xFF), (uint8_t)((sensorid>>8) & 0xFF), 0x00, 0xAB}; + + + for (uint32_t i = 2; i < 17; i++) { + novasds_cmnd[17] += novasds_cmnd[i]; + } + + + + + + NovaSdsSerial->write(novasds_cmnd, sizeof(novasds_cmnd)); + NovaSdsSerial->flush(); + + + unsigned long cmndtime = millis(); + while ( (TimePassedSince(cmndtime) < NOVA_SDS_RECDATA_TIMEOUT) && ( ! NovaSdsSerial->available() ) ); + if ( ! NovaSdsSerial->available() ) { + + return false; + } + uint8_t recbuf[10]; + memset(recbuf, 0, sizeof(recbuf)); + + while ( (TimePassedSince(cmndtime) < NOVA_SDS_RECDATA_TIMEOUT) && ( NovaSdsSerial->available() > 0) && (0xAA != (recbuf[0] = NovaSdsSerial->read())) ); + if ( 0xAA != recbuf[0] ) { + + return false; + } + + + NovaSdsSerial->readBytes(&recbuf[1], 9); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, recbuf, sizeof(recbuf)); + + if ( nullptr != buffer ) { + + memcpy(buffer, recbuf, sizeof(recbuf)); + } + + + if ((0xAB != recbuf[9] ) || (recbuf[8] != ((recbuf[2] + recbuf[3] + recbuf[4] + recbuf[5] + recbuf[6] + recbuf[7]) & 0xFF))) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("SDS: " D_CHECKSUM_FAILURE)); + return false; + } + + return true; +} + +void NovaSdsSetWorkPeriod(void) +{ + + NovaSdsCommand(NOVA_SDS_WORKING_PERIOD, NOVA_SDS_SET_MODE, 0, NOVA_SDS_DEVICE_ID, nullptr); + + NovaSdsCommand(NOVA_SDS_REPORTING_MODE, NOVA_SDS_SET_MODE, NOVA_SDS_REPORT_QUERY, NOVA_SDS_DEVICE_ID, nullptr); +} + +bool NovaSdsReadData(void) +{ + uint8_t d[10]; + if ( ! NovaSdsCommand(NOVA_SDS_QUERY_DATA, 0, 0, NOVA_SDS_DEVICE_ID, d) ) { + return false; + } + novasds_data.pm25 = (d[2] + 256 * d[3]); + novasds_data.pm100 = (d[4] + 256 * d[5]); + + return true; +} + + + +void NovaSdsSecond(void) +{ + if (!novasds_valid) + { + NovaSdsSetWorkPeriod(); + novasds_valid=1; + } + if((Settings.tele_period - Settings.novasds_startingoffset <= 0)) + { + if(!cont_mode) + { + cont_mode = 1; + NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_WORK, NOVA_SDS_DEVICE_ID, nullptr); + } + } + else + cont_mode = 0; + + if(tele_period == Settings.tele_period - Settings.novasds_startingoffset && !cont_mode) + { + NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_WORK, NOVA_SDS_DEVICE_ID, nullptr); + } + if(tele_period >= Settings.tele_period-5 && tele_period <= Settings.tele_period-2) + { + if(!(NovaSdsReadData())) novasds_valid=0; + pm100_sum += novasds_data.pm100; + pm25_sum += novasds_data.pm25; + } + if(tele_period == Settings.tele_period-1) + { + novasds_data.pm100 = pm100_sum >> 2; + novasds_data.pm25 = pm25_sum >> 2; + if(!cont_mode) + NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_SLEEP, NOVA_SDS_DEVICE_ID, nullptr); + pm100_sum = pm25_sum = 0; + } +} + + + + + + + +bool NovaSdsCommandSensor(void) +{ + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 256)) { + if( XdrvMailbox.payload < 10 ) Settings.novasds_startingoffset = 10; + else Settings.novasds_startingoffset = XdrvMailbox.payload; + } + Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_20, Settings.novasds_startingoffset); + + return true; +} + +void NovaSdsInit(void) +{ + novasds_type = 0; + if (pin[GPIO_SDS0X1_RX] < 99 && pin[GPIO_SDS0X1_TX] < 99) { + NovaSdsSerial = new TasmotaSerial(pin[GPIO_SDS0X1_RX], pin[GPIO_SDS0X1_TX], 1); + if (NovaSdsSerial->begin(9600)) { + if (NovaSdsSerial->hardwareSerial()) { + ClaimSerial(); + } + novasds_type = 1; + NovaSdsSetWorkPeriod(); + } + } +} + +#ifdef USE_WEBSERVER +const char HTTP_SDS0X1_SNS[] PROGMEM = + "{s}SDS0X1 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}SDS0X1 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"; +#endif + +void NovaSdsShow(bool json) +{ + if (novasds_valid) { + float pm10f = (float)(novasds_data.pm100) / 10.0f; + float pm2_5f = (float)(novasds_data.pm25) / 10.0f; + char pm10[33]; + dtostrfd(pm10f, 1, pm10); + char pm2_5[33]; + dtostrfd(pm2_5f, 1, pm2_5); + if (json) { + ResponseAppend_P(PSTR(",\"SDS0X1\":{\"PM2.5\":%s,\"PM10\":%s}"), pm2_5, pm10); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_VOLTAGE, pm2_5); + DomoticzSensor(DZ_CURRENT, pm10); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SDS0X1_SNS, pm2_5, pm10); +#endif + } + } +} + + + + + +bool Xsns20(uint8_t function) +{ + bool result = false; + + if (novasds_type) { + switch (function) { + case FUNC_INIT: + NovaSdsInit(); + break; + case FUNC_EVERY_SECOND: + NovaSdsSecond(); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_20 == XdrvMailbox.index) { + result = NovaSdsCommandSensor(); + } + break; + case FUNC_JSON_APPEND: + NovaSdsShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + NovaSdsShow(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_21_sgp30.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_21_sgp30.ino" +#ifdef USE_I2C +#ifdef USE_SGP30 +# 30 "S:/Development/Tasmota/tasmota/xsns_21_sgp30.ino" +#define XSNS_21 21 +#define XI2C_18 18 + +#define SGP30_ADDRESS 0x58 + +#include "Adafruit_SGP30.h" +Adafruit_SGP30 sgp; + +bool sgp30_type = false; +bool sgp30_ready = false; +float sgp30_abshum; + + + +void sgp30_Init(void) +{ + if (I2cActive(SGP30_ADDRESS)) { return; } + + if (sgp.begin()) { + sgp30_type = true; + + I2cSetActiveFound(SGP30_ADDRESS, "SGP30"); + } +} + + +#define POW_FUNC FastPrecisePow + +float sgp30_AbsoluteHumidity(float temperature, float humidity,char tempUnit) { + + + + + + float temp = NAN; + const float mw = 18.01534; + const float r = 8.31447215; + + if (isnan(temperature) || isnan(humidity) ) { + return NAN; + } + + if (tempUnit != 'C') { + temperature = (temperature - 32.0) * (5.0 / 9.0); + } + + temp = POW_FUNC(2.718281828, (17.67 * temperature) / (temperature + 243.5)); + + + return (6.112 * temp * humidity * mw) / ((273.15 + temperature) * r); +} + +#define SAVE_PERIOD 30 + +void Sgp30Update(void) +{ + sgp30_ready = false; + if (!sgp.IAQmeasure()) { + return; + } + if (global_update && (global_humidity > 0) && (global_temperature != 9999)) { + + sgp30_abshum=sgp30_AbsoluteHumidity(global_temperature,global_humidity,TempUnit()); + sgp.setHumidity(sgp30_abshum*1000); + } + sgp30_ready = true; + + + if (!(uptime%SAVE_PERIOD)) { + + uint16_t TVOC_base; + uint16_t eCO2_base; + + if (!sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) return; + + } +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_SGP30[] PROGMEM = + "{s}SGP30 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" + "{s}SGP30 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}"; +const char HTTP_SNS_AHUM[] PROGMEM = "{s}SGP30 Abs Humidity{m}%s g/m3{e}"; +#endif + +#define D_JSON_AHUM "aHumidity" + +void Sgp30Show(bool json) +{ + if (sgp30_ready) { + char abs_hum[33]; + + if (json) { + ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d"), sgp.eCO2, sgp.TVOC); + if (global_update && global_humidity>0 && global_temperature!=9999) { + + dtostrfd(sgp30_abshum,4,abs_hum); + ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"),abs_hum); + } + ResponseJsonEnd(); +#ifdef USE_DOMOTICZ + if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, sgp.eCO2); +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_SGP30, sgp.eCO2, sgp.TVOC); + if (global_update) { + WSContentSend_PD(HTTP_SNS_AHUM, abs_hum); + } +#endif + } + } +} + + + + + +bool Xsns21(uint8_t function) +{ + if (!I2cEnabled(XI2C_18)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + sgp30_Init(); + } + else if (sgp30_type) { + switch (function) { + case FUNC_EVERY_SECOND: + Sgp30Update(); + break; + case FUNC_JSON_APPEND: + Sgp30Show(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Sgp30Show(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_22_sr04.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_22_sr04.ino" +#ifdef USE_SR04 + +#include +#include +# 32 "S:/Development/Tasmota/tasmota/xsns_22_sr04.ino" +#define XSNS_22 22 + +uint8_t sr04_type = 1; +int sr04_echo_pin = 0; +int sr04_trig_pin = 0; +real64_t distance; + +NewPing* sonar = nullptr; +TasmotaSerial* sonar_serial = nullptr; + + + +uint8_t Sr04TModeDetect(void) +{ + sr04_type = 0; + if (pin[GPIO_SR04_ECHO]>=99) return sr04_type; + + sr04_echo_pin = pin[GPIO_SR04_ECHO]; + sr04_trig_pin = (pin[GPIO_SR04_TRIG] < 99) ? pin[GPIO_SR04_TRIG] : -1; + sonar_serial = new TasmotaSerial(sr04_echo_pin, sr04_trig_pin, 1); + + if (sonar_serial->begin(9600,1)) { + DEBUG_SENSOR_LOG(PSTR("SR04: Detect mode")); + + if (sr04_trig_pin!=-1) { + sr04_type = (Sr04TMiddleValue(Sr04TMode3Distance(),Sr04TMode3Distance(),Sr04TMode3Distance())!=NO_ECHO)?3:1; + } else { + sr04_type = 2; + } + } else { + sr04_type = 1; + } + + if (sr04_type < 2) { + delete sonar_serial; + sonar_serial = nullptr; + sonar = new NewPing(sr04_trig_pin, sr04_echo_pin, 300); + } else { + if (sonar_serial->hardwareSerial()) { + ClaimSerial(); + } + } + + AddLog_P2(LOG_LEVEL_INFO,PSTR("SR04: Mode %d"), sr04_type); + return sr04_type; +} + +uint16_t Sr04TMiddleValue(uint16_t first, uint16_t second, uint16_t third) +{ + uint16_t ret = first; + if (first > second) { + first = second; + second = ret; + } + + if (third < first) { + return first; + } else if (third > second) { + return second; + } else { + return third; + } +} + +uint16_t Sr04TMode3Distance() { + + sonar_serial->write(0x55); + sonar_serial->flush(); + + return Sr04TMode2Distance(); +} + +uint16_t Sr04TMode2Distance(void) +{ + sonar_serial->setTimeout(300); + const char startByte = 0xff; + + if (!sonar_serial->find(startByte)) { + + return NO_ECHO; + } + + delay(5); + + uint8_t crc = sonar_serial->read(); + + uint16_t distance = ((uint16_t)crc) << 8; + + + distance += sonar_serial->read(); + crc += distance & 0x00ff; + crc += 0x00FF; + + + if (crc != sonar_serial->read()) { + AddLog_P2(LOG_LEVEL_ERROR,PSTR("SR04: Reading CRC error.")); + return NO_ECHO; + } + + return distance; +} + +void Sr04TReading(void) { + + if (sonar_serial==nullptr && sonar==nullptr) { + Sr04TModeDetect(); + } + + switch (sr04_type) { + case 3: + distance = (real64_t)(Sr04TMiddleValue(Sr04TMode3Distance(),Sr04TMode3Distance(),Sr04TMode3Distance()))/ 10; + break; + case 2: + + while(sonar_serial->available()) sonar_serial->read(); + distance = (real64_t)(Sr04TMiddleValue(Sr04TMode2Distance(),Sr04TMode2Distance(),Sr04TMode2Distance()))/10; + break; + case 1: + distance = (real64_t)(sonar->ping_median(5))/ US_ROUNDTRIP_CM; + break; + default: + distance = NO_ECHO; + } + + return; +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_DISTANCE[] PROGMEM = + "{s}SR04 " D_DISTANCE "{m}%s" D_UNIT_CENTIMETER "{e}"; +#endif + +void Sr04Show(bool json) +{ + + if (distance != 0) { + char distance_chr[33]; + dtostrfd(distance, 3, distance_chr); + + if(json) { + ResponseAppend_P(PSTR(",\"SR04\":{\"" D_JSON_DISTANCE "\":%s}"), distance_chr); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_COUNT, distance_chr); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_DISTANCE, distance_chr); +#endif + } + } +} + + + + + +bool Xsns22(uint8_t function) +{ + bool result = false; + + if (sr04_type) { + switch (function) { + case FUNC_INIT: + result = (pin[GPIO_SR04_ECHO]<99); + break; + case FUNC_EVERY_SECOND: + Sr04TReading(); + result = true; + break; + case FUNC_JSON_APPEND: + Sr04Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Sr04Show(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_24_si1145.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_24_si1145.ino" +#ifdef USE_I2C +#ifdef USE_SI1145 +# 30 "S:/Development/Tasmota/tasmota/xsns_24_si1145.ino" +#define XSNS_24 24 +#define XI2C_19 19 + +#define SI114X_ADDR 0X60 + + + +#define SI114X_QUERY 0X80 +#define SI114X_SET 0XA0 +#define SI114X_NOP 0X00 +#define SI114X_RESET 0X01 +#define SI114X_BUSADDR 0X02 +#define SI114X_PS_FORCE 0X05 +#define SI114X_GET_CAL 0X12 +#define SI114X_ALS_FORCE 0X06 +#define SI114X_PSALS_FORCE 0X07 +#define SI114X_PS_PAUSE 0X09 +#define SI114X_ALS_PAUSE 0X0A +#define SI114X_PSALS_PAUSE 0X0B +#define SI114X_PS_AUTO 0X0D +#define SI114X_ALS_AUTO 0X0E +#define SI114X_PSALS_AUTO 0X0F + + + +#define SI114X_PART_ID 0X00 +#define SI114X_REV_ID 0X01 +#define SI114X_SEQ_ID 0X02 +#define SI114X_INT_CFG 0X03 +#define SI114X_IRQ_ENABLE 0X04 +#define SI114X_IRQ_MODE1 0x05 +#define SI114X_IRQ_MODE2 0x06 +#define SI114X_HW_KEY 0X07 +#define SI114X_MEAS_RATE0 0X08 +#define SI114X_MEAS_RATE1 0X09 +#define SI114X_PS_RATE 0X0A +#define SI114X_PS_LED21 0X0F +#define SI114X_PS_LED3 0X10 +#define SI114X_UCOEFF0 0X13 +#define SI114X_UCOEFF1 0X14 +#define SI114X_UCOEFF2 0X15 +#define SI114X_UCOEFF3 0X16 +#define SI114X_WR 0X17 +#define SI114X_COMMAND 0X18 +#define SI114X_RESPONSE 0X20 +#define SI114X_IRQ_STATUS 0X21 +#define SI114X_ALS_VIS_DATA0 0X22 +#define SI114X_ALS_VIS_DATA1 0X23 +#define SI114X_ALS_IR_DATA0 0X24 +#define SI114X_ALS_IR_DATA1 0X25 +#define SI114X_PS1_DATA0 0X26 +#define SI114X_PS1_DATA1 0X27 +#define SI114X_PS2_DATA0 0X28 +#define SI114X_PS2_DATA1 0X29 +#define SI114X_PS3_DATA0 0X2A +#define SI114X_PS3_DATA1 0X2B +#define SI114X_AUX_DATA0_UVINDEX0 0X2C +#define SI114X_AUX_DATA1_UVINDEX1 0X2D +#define SI114X_RD 0X2E +#define SI114X_CHIP_STAT 0X30 + + + +#define SI114X_CHLIST 0X01 +#define SI114X_CHLIST_ENUV 0x80 +#define SI114X_CHLIST_ENAUX 0x40 +#define SI114X_CHLIST_ENALSIR 0x20 +#define SI114X_CHLIST_ENALSVIS 0x10 +#define SI114X_CHLIST_ENPS1 0x01 +#define SI114X_CHLIST_ENPS2 0x02 +#define SI114X_CHLIST_ENPS3 0x04 + +#define SI114X_PSLED12_SELECT 0X02 +#define SI114X_PSLED3_SELECT 0X03 + +#define SI114X_PS_ENCODE 0X05 +#define SI114X_ALS_ENCODE 0X06 + +#define SI114X_PS1_ADCMUX 0X07 +#define SI114X_PS2_ADCMUX 0X08 +#define SI114X_PS3_ADCMUX 0X09 + +#define SI114X_PS_ADC_COUNTER 0X0A +#define SI114X_PS_ADC_GAIN 0X0B +#define SI114X_PS_ADC_MISC 0X0C + +#define SI114X_ALS_IR_ADC_MUX 0X0E +#define SI114X_AUX_ADC_MUX 0X0F + +#define SI114X_ALS_VIS_ADC_COUNTER 0X10 +#define SI114X_ALS_VIS_ADC_GAIN 0X11 +#define SI114X_ALS_VIS_ADC_MISC 0X12 + +#define SI114X_LED_REC 0X1C + +#define SI114X_ALS_IR_ADC_COUNTER 0X1D +#define SI114X_ALS_IR_ADC_GAIN 0X1E +#define SI114X_ALS_IR_ADC_MISC 0X1F + + + + +#define SI114X_ADCMUX_SMALL_IR 0x00 +#define SI114X_ADCMUX_VISIABLE 0x02 +#define SI114X_ADCMUX_LARGE_IR 0x03 +#define SI114X_ADCMUX_NO 0x06 +#define SI114X_ADCMUX_GND 0x25 +#define SI114X_ADCMUX_TEMPERATURE 0x65 +#define SI114X_ADCMUX_VDD 0x75 + +#define SI114X_PSLED12_SELECT_PS1_NONE 0x00 +#define SI114X_PSLED12_SELECT_PS1_LED1 0x01 +#define SI114X_PSLED12_SELECT_PS1_LED2 0x02 +#define SI114X_PSLED12_SELECT_PS1_LED3 0x04 +#define SI114X_PSLED12_SELECT_PS2_NONE 0x00 +#define SI114X_PSLED12_SELECT_PS2_LED1 0x10 +#define SI114X_PSLED12_SELECT_PS2_LED2 0x20 +#define SI114X_PSLED12_SELECT_PS2_LED3 0x40 +#define SI114X_PSLED3_SELECT_PS2_NONE 0x00 +#define SI114X_PSLED3_SELECT_PS2_LED1 0x10 +#define SI114X_PSLED3_SELECT_PS2_LED2 0x20 +#define SI114X_PSLED3_SELECT_PS2_LED3 0x40 + +#define SI114X_ADC_GAIN_DIV1 0X00 +#define SI114X_ADC_GAIN_DIV2 0X01 +#define SI114X_ADC_GAIN_DIV4 0X02 +#define SI114X_ADC_GAIN_DIV8 0X03 +#define SI114X_ADC_GAIN_DIV16 0X04 +#define SI114X_ADC_GAIN_DIV32 0X05 + +#define SI114X_LED_CURRENT_5MA 0X01 +#define SI114X_LED_CURRENT_11MA 0X02 +#define SI114X_LED_CURRENT_22MA 0X03 +#define SI114X_LED_CURRENT_45MA 0X04 + +#define SI114X_ADC_COUNTER_1ADCCLK 0X00 +#define SI114X_ADC_COUNTER_7ADCCLK 0X01 +#define SI114X_ADC_COUNTER_15ADCCLK 0X02 +#define SI114X_ADC_COUNTER_31ADCCLK 0X03 +#define SI114X_ADC_COUNTER_63ADCCLK 0X04 +#define SI114X_ADC_COUNTER_127ADCCLK 0X05 +#define SI114X_ADC_COUNTER_255ADCCLK 0X06 +#define SI114X_ADC_COUNTER_511ADCCLK 0X07 + +#define SI114X_ADC_MISC_LOWRANGE 0X00 +#define SI114X_ADC_MISC_HIGHRANGE 0X20 +#define SI114X_ADC_MISC_ADC_NORMALPROXIMITY 0X00 +#define SI114X_ADC_MISC_ADC_RAWADC 0X04 + +#define SI114X_INT_CFG_INTOE 0X01 + +#define SI114X_IRQEN_ALS 0x01 +#define SI114X_IRQEN_PS1 0x04 +#define SI114X_IRQEN_PS2 0x08 +#define SI114X_IRQEN_PS3 0x10 + +uint16_t si1145_visible; +uint16_t si1145_infrared; +uint16_t si1145_uvindex; + +bool si1145_type = false; +uint8_t si1145_valid = 0; + + + +uint8_t Si1145ReadByte(uint8_t reg) +{ + return I2cRead8(SI114X_ADDR, reg); +} + +uint16_t Si1145ReadHalfWord(uint8_t reg) +{ + return I2cRead16LE(SI114X_ADDR, reg); +} + +bool Si1145WriteByte(uint8_t reg, uint16_t val) +{ + I2cWrite8(SI114X_ADDR, reg, val); +} + +uint8_t Si1145WriteParamData(uint8_t p, uint8_t v) +{ + Si1145WriteByte(SI114X_WR, v); + Si1145WriteByte(SI114X_COMMAND, p | SI114X_SET); + return Si1145ReadByte(SI114X_RD); +} + + + +bool Si1145Present(void) +{ + return (Si1145ReadByte(SI114X_PART_ID) == 0X45); +} + +void Si1145Reset(void) +{ + Si1145WriteByte(SI114X_MEAS_RATE0, 0); + Si1145WriteByte(SI114X_MEAS_RATE1, 0); + Si1145WriteByte(SI114X_IRQ_ENABLE, 0); + Si1145WriteByte(SI114X_IRQ_MODE1, 0); + Si1145WriteByte(SI114X_IRQ_MODE2, 0); + Si1145WriteByte(SI114X_INT_CFG, 0); + Si1145WriteByte(SI114X_IRQ_STATUS, 0xFF); + + Si1145WriteByte(SI114X_COMMAND, SI114X_RESET); + delay(10); + Si1145WriteByte(SI114X_HW_KEY, 0x17); + delay(10); +} + +void Si1145DeInit(void) +{ + + + Si1145WriteByte(SI114X_UCOEFF0, 0x29); + Si1145WriteByte(SI114X_UCOEFF1, 0x89); + Si1145WriteByte(SI114X_UCOEFF2, 0x02); + Si1145WriteByte(SI114X_UCOEFF3, 0x00); + Si1145WriteParamData(SI114X_CHLIST, SI114X_CHLIST_ENUV | SI114X_CHLIST_ENALSIR | SI114X_CHLIST_ENALSVIS | SI114X_CHLIST_ENPS1); + + + + Si1145WriteParamData(SI114X_PS1_ADCMUX, SI114X_ADCMUX_LARGE_IR); + Si1145WriteByte(SI114X_PS_LED21, SI114X_LED_CURRENT_22MA); + Si1145WriteParamData(SI114X_PSLED12_SELECT, SI114X_PSLED12_SELECT_PS1_LED1); + + + + Si1145WriteParamData(SI114X_PS_ADC_GAIN, SI114X_ADC_GAIN_DIV1); + Si1145WriteParamData(SI114X_PS_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK); + Si1145WriteParamData(SI114X_PS_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE | SI114X_ADC_MISC_ADC_RAWADC); + + + + Si1145WriteParamData(SI114X_ALS_VIS_ADC_GAIN, SI114X_ADC_GAIN_DIV1); + Si1145WriteParamData(SI114X_ALS_VIS_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK); + Si1145WriteParamData(SI114X_ALS_VIS_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE); + + + + Si1145WriteParamData(SI114X_ALS_IR_ADC_GAIN, SI114X_ADC_GAIN_DIV1); + Si1145WriteParamData(SI114X_ALS_IR_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK); + Si1145WriteParamData(SI114X_ALS_IR_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE); + + + + Si1145WriteByte(SI114X_INT_CFG, SI114X_INT_CFG_INTOE); + Si1145WriteByte(SI114X_IRQ_ENABLE, SI114X_IRQEN_ALS); + + + + Si1145WriteByte(SI114X_MEAS_RATE0, 0xFF); + Si1145WriteByte(SI114X_COMMAND, SI114X_PSALS_AUTO); +} + +bool Si1145Begin(void) +{ + if (!Si1145Present()) { return false; } + + Si1145Reset(); + Si1145DeInit(); + return true; +} + + +uint16_t Si1145ReadUV(void) +{ + return Si1145ReadHalfWord(SI114X_AUX_DATA0_UVINDEX0); +} + + +uint16_t Si1145ReadVisible(void) +{ + return Si1145ReadHalfWord(SI114X_ALS_VIS_DATA0); +} + + +uint16_t Si1145ReadIR(void) +{ + return Si1145ReadHalfWord(SI114X_ALS_IR_DATA0); +} + + + +bool Si1145Read(void) +{ + if (si1145_valid) { si1145_valid--; } + + if (!Si1145Present()) { return false; } + + si1145_visible = Si1145ReadVisible(); + si1145_infrared = Si1145ReadIR(); + si1145_uvindex = Si1145ReadUV(); + si1145_valid = SENSOR_MAX_MISS; + return true; +} + +void Si1145Detect(void) +{ + if (I2cActive(SI114X_ADDR)) { return; } + + if (Si1145Begin()) { + si1145_type = true; + I2cSetActiveFound(SI114X_ADDR, "SI1145"); + } +} + +void Si1145Update(void) +{ + if (!Si1145Read()) { + AddLogMissed("SI1145", si1145_valid); + } +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_SI1145[] PROGMEM = + "{s}SI1145 " D_ILLUMINANCE "{m}%d " D_UNIT_LUX "{e}" + "{s}SI1145 " D_INFRARED "{m}%d " D_UNIT_LUX "{e}" + "{s}SI1145 " D_UV_INDEX "{m}%d.%d{e}"; +#endif + +void Si1145Show(bool json) +{ + if (si1145_valid) { + if (json) { + ResponseAppend_P(PSTR(",\"SI1145\":{\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_INFRARED "\":%d,\"" D_JSON_UV_INDEX "\":%d.%d}"), + si1145_visible, si1145_infrared, si1145_uvindex /100, si1145_uvindex %100); +#ifdef USE_DOMOTICZ + if (0 == tele_period) DomoticzSensor(DZ_ILLUMINANCE, si1145_visible); +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_SI1145, si1145_visible, si1145_infrared, si1145_uvindex /100, si1145_uvindex %100); +#endif + } + } +} + + + + + +bool Xsns24(uint8_t function) +{ + if (!I2cEnabled(XI2C_19)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Si1145Detect(); + } + else if (si1145_type) { + switch (function) { + case FUNC_EVERY_SECOND: + Si1145Update(); + break; + case FUNC_JSON_APPEND: + Si1145Show(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Si1145Show(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_26_lm75ad.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_26_lm75ad.ino" +#ifdef USE_I2C +#ifdef USE_LM75AD +# 31 "S:/Development/Tasmota/tasmota/xsns_26_lm75ad.ino" +#define XSNS_26 26 +#define XI2C_20 20 + +#define LM75AD_ADDRESS1 0x48 +#define LM75AD_ADDRESS2 0x49 +#define LM75AD_ADDRESS3 0x4A +#define LM75AD_ADDRESS4 0x4B +#define LM75AD_ADDRESS5 0x4C +#define LM75AD_ADDRESS6 0x4D +#define LM75AD_ADDRESS7 0x4E +#define LM75AD_ADDRESS8 0x4F + +#define LM75_TEMP_REGISTER 0x00 +#define LM75_CONF_REGISTER 0x01 +#define LM75_THYST_REGISTER 0x02 +#define LM75_TOS_REGISTER 0x03 + +bool lm75ad_type = false; +uint8_t lm75ad_address; +uint8_t lm75ad_addresses[] = { LM75AD_ADDRESS1, LM75AD_ADDRESS2, LM75AD_ADDRESS3, LM75AD_ADDRESS4, LM75AD_ADDRESS5, LM75AD_ADDRESS6, LM75AD_ADDRESS7, LM75AD_ADDRESS8 }; + +void LM75ADDetect(void) +{ + for (uint32_t i = 0; i < sizeof(lm75ad_addresses); i++) { + lm75ad_address = lm75ad_addresses[i]; + if (I2cActive(lm75ad_address)) { + continue; } + if (!I2cSetDevice(lm75ad_address)) { + break; + } + uint16_t buffer; + if (I2cValidRead16(&buffer, lm75ad_address, LM75_THYST_REGISTER)) { + if (buffer == 0x4B00) { + lm75ad_type = true; + I2cSetActiveFound(lm75ad_address, "LM75AD"); + break; + } + } + } +} + +float LM75ADGetTemp(void) +{ + int16_t sign = 1; + + uint16_t t = I2cRead16(lm75ad_address, LM75_TEMP_REGISTER); + if (t & 0x8000) { + t = (~t) +0x20; + sign = -1; + } + t = t >> 5; + return ConvertTemp(sign * t * 0.125); +} + +void LM75ADShow(bool json) +{ + float t = LM75ADGetTemp(); + char temperature[33]; + dtostrfd(t, Settings.flag2.temperature_resolution, temperature); + + if (json) { + ResponseAppend_P(PSTR(",\"LM75AD\":{\"" D_JSON_TEMPERATURE "\":%s}"), temperature); +#ifdef USE_DOMOTICZ + if (0 == tele_period) DomoticzSensor(DZ_TEMP, temperature); +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, "LM75AD", temperature, TempUnit()); +#endif + } +} + + + + + +bool Xsns26(uint8_t function) +{ + if (!I2cEnabled(XI2C_20)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + LM75ADDetect(); + } + else if (lm75ad_type) { + switch (function) { + case FUNC_JSON_APPEND: + LM75ADShow(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + LM75ADShow(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +# 28 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +#ifdef USE_I2C +#ifdef USE_APDS9960 +# 39 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +#define XSNS_27 27 +#define XI2C_21 21 + +#if defined(USE_SHT) || defined(USE_VEML6070) || defined(USE_TSL2561) + #warning **** Turned off conflicting drivers SHT and VEML6070 **** + #ifdef USE_SHT + #undef USE_SHT + #endif + #ifdef USE_VEML6070 + #undef USE_VEML6070 + #endif + #ifdef USE_TSL2561 + #undef USE_TSL2561 + #endif +#endif + +#define APDS9960_I2C_ADDR 0x39 + +#define APDS9960_CHIPID_1 0xAB +#define APDS9960_CHIPID_2 0x9C + +#define APDS9930_CHIPID_1 0x12 +#define APDS9930_CHIPID_2 0x39 + + +#define GESTURE_THRESHOLD_OUT 10 +#define GESTURE_SENSITIVITY_1 50 +#define GESTURE_SENSITIVITY_2 20 + +uint8_t APDS9960addr; +uint8_t APDS9960type = 0; +char APDS9960stype[] = "APDS9960"; +char currentGesture[6]; +uint8_t gesture_mode = 1; + + +volatile uint8_t recovery_loop_counter = 0; +#define APDS9960_LONG_RECOVERY 50 +#define APDS9960_MAX_GESTURE_CYCLES 50 +bool APDS9960_overload = false; + +#ifdef USE_WEBSERVER +const char HTTP_APDS_9960_SNS[] PROGMEM = + "{s}" "Red" "{m}%s{e}" + "{s}" "Green" "{m}%s{e}" + "{s}" "Blue" "{m}%s{e}" + "{s}" "Ambient" "{m}%s " D_UNIT_LUX "{e}" + "{s}" "CCT" "{m}%s " "K" "{e}" + "{s}" "Proximity" "{m}%s{e}"; +#endif +# 97 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +#define FIFO_PAUSE_TIME 30 + + +#define APDS9960_ENABLE 0x80 +#define APDS9960_ATIME 0x81 +#define APDS9960_WTIME 0x83 +#define APDS9960_AILTL 0x84 +#define APDS9960_AILTH 0x85 +#define APDS9960_AIHTL 0x86 +#define APDS9960_AIHTH 0x87 +#define APDS9960_PILT 0x89 +#define APDS9960_PIHT 0x8B +#define APDS9960_PERS 0x8C +#define APDS9960_CONFIG1 0x8D +#define APDS9960_PPULSE 0x8E +#define APDS9960_CONTROL 0x8F +#define APDS9960_CONFIG2 0x90 +#define APDS9960_ID 0x92 +#define APDS9960_STATUS 0x93 +#define APDS9960_CDATAL 0x94 +#define APDS9960_CDATAH 0x95 +#define APDS9960_RDATAL 0x96 +#define APDS9960_RDATAH 0x97 +#define APDS9960_GDATAL 0x98 +#define APDS9960_GDATAH 0x99 +#define APDS9960_BDATAL 0x9A +#define APDS9960_BDATAH 0x9B +#define APDS9960_PDATA 0x9C +#define APDS9960_POFFSET_UR 0x9D +#define APDS9960_POFFSET_DL 0x9E +#define APDS9960_CONFIG3 0x9F +#define APDS9960_GPENTH 0xA0 +#define APDS9960_GEXTH 0xA1 +#define APDS9960_GCONF1 0xA2 +#define APDS9960_GCONF2 0xA3 +#define APDS9960_GOFFSET_U 0xA4 +#define APDS9960_GOFFSET_D 0xA5 +#define APDS9960_GOFFSET_L 0xA7 +#define APDS9960_GOFFSET_R 0xA9 +#define APDS9960_GPULSE 0xA6 +#define APDS9960_GCONF3 0xAA +#define APDS9960_GCONF4 0xAB +#define APDS9960_GFLVL 0xAE +#define APDS9960_GSTATUS 0xAF +#define APDS9960_IFORCE 0xE4 +#define APDS9960_PICLEAR 0xE5 +#define APDS9960_CICLEAR 0xE6 +#define APDS9960_AICLEAR 0xE7 +#define APDS9960_GFIFO_U 0xFC +#define APDS9960_GFIFO_D 0xFD +#define APDS9960_GFIFO_L 0xFE +#define APDS9960_GFIFO_R 0xFF + + +#define APDS9960_PON 0b00000001 +#define APDS9960_AEN 0b00000010 +#define APDS9960_PEN 0b00000100 +#define APDS9960_WEN 0b00001000 +#define APSD9960_AIEN 0b00010000 +#define APDS9960_PIEN 0b00100000 +#define APDS9960_GEN 0b01000000 +#define APDS9960_GVALID 0b00000001 + + +#define OFF 0 +#define ON 1 + + +#define POWER 0 +#define AMBIENT_LIGHT 1 +#define PROXIMITY 2 +#define WAIT 3 +#define AMBIENT_LIGHT_INT 4 +#define PROXIMITY_INT 5 +#define GESTURE 6 +#define ALL 7 + + +#define LED_DRIVE_100MA 0 +#define LED_DRIVE_50MA 1 +#define LED_DRIVE_25MA 2 +#define LED_DRIVE_12_5MA 3 + + +#define PGAIN_1X 0 +#define PGAIN_2X 1 +#define PGAIN_4X 2 +#define PGAIN_8X 3 + + +#define AGAIN_1X 0 +#define AGAIN_4X 1 +#define AGAIN_16X 2 +#define AGAIN_64X 3 + + +#define GGAIN_1X 0 +#define GGAIN_2X 1 +#define GGAIN_4X 2 +#define GGAIN_8X 3 + + +#define LED_BOOST_100 0 +#define LED_BOOST_150 1 +#define LED_BOOST_200 2 +#define LED_BOOST_300 3 + + +#define GWTIME_0MS 0 +#define GWTIME_2_8MS 1 +#define GWTIME_5_6MS 2 +#define GWTIME_8_4MS 3 +#define GWTIME_14_0MS 4 +#define GWTIME_22_4MS 5 +#define GWTIME_30_8MS 6 +#define GWTIME_39_2MS 7 + + +#define DEFAULT_ATIME 0xdb +#define DEFAULT_WTIME 246 +#define DEFAULT_PROX_PPULSE 0x87 +#define DEFAULT_GESTURE_PPULSE 0x89 +#define DEFAULT_POFFSET_UR 0 +#define DEFAULT_POFFSET_DL 0 +#define DEFAULT_CONFIG1 0x60 +#define DEFAULT_LDRIVE LED_DRIVE_100MA +#define DEFAULT_PGAIN PGAIN_4X +#define DEFAULT_AGAIN AGAIN_4X +#define DEFAULT_PILT 0 +#define DEFAULT_PIHT 50 +#define DEFAULT_AILT 0xFFFF +#define DEFAULT_AIHT 0 +#define DEFAULT_PERS 0x11 +#define DEFAULT_CONFIG2 0x01 +#define DEFAULT_CONFIG3 0 +#define DEFAULT_GPENTH 40 +#define DEFAULT_GEXTH 30 +#define DEFAULT_GCONF1 0x40 +#define DEFAULT_GGAIN GGAIN_4X +#define DEFAULT_GLDRIVE LED_DRIVE_100MA +#define DEFAULT_GWTIME GWTIME_2_8MS +#define DEFAULT_GOFFSET 0 +#define DEFAULT_GPULSE 0xC9 +#define DEFAULT_GCONF3 0 +#define DEFAULT_GIEN 0 + +#define ERROR 0xFF + + +enum { + DIR_NONE, + DIR_LEFT, + DIR_RIGHT, + DIR_UP, + DIR_DOWN, + DIR_ALL +}; + + +enum { + APDS9960_NA_STATE, + APDS9960_ALL_STATE +}; + + +typedef struct gesture_data_type { + uint8_t u_data[32]; + uint8_t d_data[32]; + uint8_t l_data[32]; + uint8_t r_data[32]; + uint8_t index; + uint8_t total_gestures; + uint8_t in_threshold; + uint8_t out_threshold; +} gesture_data_type; + + + gesture_data_type gesture_data_; + int16_t gesture_ud_delta_ = 0; + int16_t gesture_lr_delta_ = 0; + int16_t gesture_ud_count_ = 0; + int16_t gesture_lr_count_ = 0; + int16_t gesture_state_ = 0; + int16_t gesture_motion_ = DIR_NONE; + + typedef struct color_data_type { + uint16_t a; + uint16_t r; + uint16_t g; + uint16_t b; + uint8_t p; + uint16_t cct; + uint16_t lux; + } color_data_type; + + color_data_type color_data; + uint8_t APDS9960_aTime = DEFAULT_ATIME; +# 306 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + bool wireWriteByte(uint8_t val) + { + Wire.beginTransmission(APDS9960_I2C_ADDR); + Wire.write(val); + if( Wire.endTransmission() != 0 ) { + return false; + } + + return true; + } +# 325 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +int8_t wireReadDataBlock( uint8_t reg, + uint8_t *val, + uint16_t len) +{ + unsigned char i = 0; + + + if (!wireWriteByte(reg)) { + return -1; + } + + + Wire.requestFrom(APDS9960_I2C_ADDR, len); + while (Wire.available()) { + if (i >= len) { + return -1; + } + val[i] = Wire.read(); + i++; + } + + return i; +} + + + + + + + +void calculateColorTemperature(void) +{ + float X, Y, Z; + float xc, yc; + float n; + float cct; + + + + + + X = (-0.14282F * color_data.r) + (1.54924F * color_data.g) + (-0.95641F * color_data.b); + Y = (-0.32466F * color_data.r) + (1.57837F * color_data.g) + (-0.73191F * color_data.b); + Z = (-0.68202F * color_data.r) + (0.77073F * color_data.g) + ( 0.56332F * color_data.b); + + + xc = (X) / (X + Y + Z); + yc = (Y) / (X + Y + Z); + + + n = (xc - 0.3320F) / (0.1858F - yc); + + + color_data.cct = (449.0F * FastPrecisePowf(n, 3)) + (3525.0F * FastPrecisePowf(n, 2)) + (6823.3F * n) + 5520.33F; + + return; +} +# 392 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + uint8_t getProxIntLowThresh(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PILT) ; + return val; + } + + + + + + + void setProxIntLowThresh(uint8_t threshold) + { + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PILT, threshold); + } + + + + + + + uint8_t getProxIntHighThresh(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PIHT) ; + return val; + } + + + + + + + + void setProxIntHighThresh(uint8_t threshold) + { + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PIHT, threshold); + } +# 448 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + uint8_t getLEDDrive(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL) ; + + val = (val >> 6) & 0b00000011; + + return val; + } +# 471 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void setLEDDrive(uint8_t drive) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL); + + + drive &= 0b00000011; + drive = drive << 6; + val &= 0b00111111; + val |= drive; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val); + } +# 500 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + uint8_t getProximityGain(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL) ; + + val = (val >> 2) & 0b00000011; + + return val; + } +# 523 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void setProximityGain(uint8_t drive) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL); + + + drive &= 0b00000011; + drive = drive << 2; + val &= 0b11110011; + val |= drive; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val); + } +# 564 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void setAmbientLightGain(uint8_t drive) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL); + + + drive &= 0b00000011; + val &= 0b11111100; + val |= drive; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val); + } +# 591 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + uint8_t getLEDBoost(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG2) ; + + + val = (val >> 4) & 0b00000011; + + return val; + } +# 615 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void setLEDBoost(uint8_t boost) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG2) ; + + boost &= 0b00000011; + boost = boost << 4; + val &= 0b11001111; + val |= boost; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG2, val) ; + } + + + + + + + uint8_t getProxGainCompEnable(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ; + + + val = (val >> 5) & 0b00000001; + + return val; + } + + + + + + + void setProxGainCompEnable(uint8_t enable) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ; + + + enable &= 0b00000001; + enable = enable << 5; + val &= 0b11011111; + val |= enable; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, val) ; + } +# 683 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + uint8_t getProxPhotoMask(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ; + + + val &= 0b00001111; + + return val; + } +# 708 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void setProxPhotoMask(uint8_t mask) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ; + + + mask &= 0b00001111; + val &= 0b11110000; + val |= mask; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, val) ; + } + + + + + + + uint8_t getGestureEnterThresh(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GPENTH) ; + + return val; + } + + + + + + + void setGestureEnterThresh(uint8_t threshold) + { + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GPENTH, threshold) ; + + } + + + + + + + uint8_t getGestureExitThresh(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GEXTH) ; + + return val; + } + + + + + + + void setGestureExitThresh(uint8_t threshold) + { + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GEXTH, threshold) ; + } +# 786 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + uint8_t getGestureGain(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; + + + val = (val >> 5) & 0b00000011; + + return val; + } +# 810 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void setGestureGain(uint8_t gain) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; + + + gain &= 0b00000011; + gain = gain << 5; + val &= 0b10011111; + val |= gain; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ; + } +# 838 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + uint8_t getGestureLEDDrive(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; + + + val = (val >> 3) & 0b00000011; + + return val; + } +# 862 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void setGestureLEDDrive(uint8_t drive) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; + + + drive &= 0b00000011; + drive = drive << 3; + val &= 0b11100111; + val |= drive; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ; + } +# 894 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + uint8_t getGestureWaitTime(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; + + + val &= 0b00000111; + + return val; + } +# 922 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void setGestureWaitTime(uint8_t time) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; + + + time &= 0b00000111; + val &= 0b11111000; + val |= time; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ; + } + + + + + + + void getLightIntLowThreshold(uint16_t &threshold) + { + uint8_t val_byte; + threshold = 0; + + + val_byte = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AILTL) ; + threshold = val_byte; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTH, val_byte) ; + threshold = threshold + ((uint16_t)val_byte << 8); + } + + + + + + + + void setLightIntLowThreshold(uint16_t threshold) + { + uint8_t val_low; + uint8_t val_high; + + + val_low = threshold & 0x00FF; + val_high = (threshold & 0xFF00) >> 8; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTL, val_low) ; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTH, val_high) ; + + } + + + + + + + + void getLightIntHighThreshold(uint16_t &threshold) + { + uint8_t val_byte; + threshold = 0; + + + val_byte = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AIHTL); + threshold = val_byte; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTH, val_byte) ; + threshold = threshold + ((uint16_t)val_byte << 8); + } + + + + + + + void setLightIntHighThreshold(uint16_t threshold) + { + uint8_t val_low; + uint8_t val_high; + + + val_low = threshold & 0x00FF; + val_high = (threshold & 0xFF00) >> 8; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTL, val_low); + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTH, val_high) ; + } + + + + + + + + void getProximityIntLowThreshold(uint8_t &threshold) + { + threshold = 0; + + + threshold = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PILT); + + } + + + + + + + void setProximityIntLowThreshold(uint8_t threshold) + { + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PILT, threshold) ; + } +# 1055 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" + void getProximityIntHighThreshold(uint8_t &threshold) + { + threshold = 0; + + + threshold = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PIHT) ; + + } + + + + + + + void setProximityIntHighThreshold(uint8_t threshold) + { + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PIHT, threshold) ; + } + + + + + + + uint8_t getAmbientLightIntEnable(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ; + + + val = (val >> 4) & 0b00000001; + + return val; + } + + + + + + + void setAmbientLightIntEnable(uint8_t enable) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE); + + + enable &= 0b00000001; + enable = enable << 4; + val &= 0b11101111; + val |= enable; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, val) ; + } + + + + + + + uint8_t getProximityIntEnable(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ; + + + val = (val >> 5) & 0b00000001; + + return val; + } + + + + + + + void setProximityIntEnable(uint8_t enable) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ; + + + enable &= 0b00000001; + enable = enable << 5; + val &= 0b11011111; + val |= enable; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, val) ; + } + + + + + + + uint8_t getGestureIntEnable(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ; + + + val = (val >> 1) & 0b00000001; + + return val; + } + + + + + + + void setGestureIntEnable(uint8_t enable) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ; + + + enable &= 0b00000001; + enable = enable << 1; + val &= 0b11111101; + val |= enable; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF4, val) ; + } + + + + + + void clearAmbientLightInt(void) + { + uint8_t throwaway; + throwaway = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AICLEAR); + } + + + + + + void clearProximityInt(void) + { + uint8_t throwaway; + throwaway = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PICLEAR) ; + + } + + + + + + + uint8_t getGestureMode(void) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ; + + + val &= 0b00000001; + + return val; + } + + + + + + + void setGestureMode(uint8_t mode) + { + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ; + + + mode &= 0b00000001; + val &= 0b11111110; + val |= mode; + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF4, val) ; + } + + +bool APDS9960_init(void) +{ + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, DEFAULT_ATIME) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_WTIME, DEFAULT_WTIME) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PPULSE, DEFAULT_PROX_PPULSE) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_POFFSET_UR, DEFAULT_POFFSET_UR) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_POFFSET_DL, DEFAULT_POFFSET_DL) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG1, DEFAULT_CONFIG1) ; + + setLEDDrive(DEFAULT_LDRIVE); + + setProximityGain(DEFAULT_PGAIN); + + setAmbientLightGain(DEFAULT_AGAIN); + + setProxIntLowThresh(DEFAULT_PILT) ; + + setProxIntHighThresh(DEFAULT_PIHT); + + setLightIntLowThreshold(DEFAULT_AILT) ; + + setLightIntHighThreshold(DEFAULT_AIHT) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PERS, DEFAULT_PERS) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG2, DEFAULT_CONFIG2) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, DEFAULT_CONFIG3) ; + + + setGestureEnterThresh(DEFAULT_GPENTH); + + setGestureExitThresh(DEFAULT_GEXTH) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF1, DEFAULT_GCONF1) ; + + setGestureGain(DEFAULT_GGAIN) ; + + setGestureLEDDrive(DEFAULT_GLDRIVE) ; + + setGestureWaitTime(DEFAULT_GWTIME) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_U, DEFAULT_GOFFSET) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_D, DEFAULT_GOFFSET) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_L, DEFAULT_GOFFSET) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_R, DEFAULT_GOFFSET) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GPULSE, DEFAULT_GPULSE) ; + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF3, DEFAULT_GCONF3) ; + + setGestureIntEnable(DEFAULT_GIEN); + + disablePower(); + + return true; +} +# 1333 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +uint8_t getMode(void) +{ + uint8_t enable_value; + + + enable_value = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ; + + return enable_value; +} + + + + + + + +void setMode(uint8_t mode, uint8_t enable) +{ + uint8_t reg_val; + + + reg_val = getMode(); + + + + enable = enable & 0x01; + if( mode >= 0 && mode <= 6 ) { + if (enable) { + reg_val |= (1 << mode); + } else { + reg_val &= ~(1 << mode); + } + } else if( mode == ALL ) { + if (enable) { + reg_val = 0x7F; + } else { + reg_val = 0x00; + } + } + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, reg_val) ; +} + + + + + + +void enableLightSensor(void) +{ + + setAmbientLightGain(DEFAULT_AGAIN); + setAmbientLightIntEnable(0); + enablePower() ; + setMode(AMBIENT_LIGHT, 1) ; +} + + + + + +void disableLightSensor(void) +{ + setAmbientLightIntEnable(0) ; + setMode(AMBIENT_LIGHT, 0) ; +} + + + + + + +void enableProximitySensor(void) +{ + + setProximityGain(DEFAULT_PGAIN); + setLEDDrive(DEFAULT_LDRIVE) ; + setProximityIntEnable(0) ; + enablePower(); + setMode(PROXIMITY, 1) ; +} + + + + + +void disableProximitySensor(void) +{ + setProximityIntEnable(0) ; + setMode(PROXIMITY, 0) ; +} + + + + + + +void enableGestureSensor(void) +{ + + + + + + + + resetGestureParameters(); + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_WTIME, 0xFF) ; + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PPULSE, DEFAULT_GESTURE_PPULSE) ; + setLEDBoost(LED_BOOST_100); + setGestureIntEnable(0) ; + setGestureMode(1); + enablePower() ; + setMode(WAIT, 1) ; + setMode(PROXIMITY, 1) ; + setMode(GESTURE, 1); +} + + + + + +void disableGestureSensor(void) +{ + resetGestureParameters(); + setGestureIntEnable(0) ; + setGestureMode(0) ; + setMode(GESTURE, 0) ; +} + + + + + + +bool isGestureAvailable(void) +{ + uint8_t val; + + + val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GSTATUS) ; + + + val &= APDS9960_GVALID; + + + if( val == 1) { + return true; + } else { + return false; + } +} + + + + + + +int16_t readGesture(void) +{ + uint8_t fifo_level = 0; + uint8_t bytes_read = 0; + uint8_t fifo_data[128]; + uint8_t gstatus; + uint16_t motion; + uint16_t i; + uint8_t gesture_loop_counter = 0; + + + if( !isGestureAvailable() || !(getMode() & 0b01000001) ) { + return DIR_NONE; + } + + + while(1) { + if (gesture_loop_counter == APDS9960_MAX_GESTURE_CYCLES){ + disableGestureSensor(); + APDS9960_overload = true; + AddLog_P(LOG_LEVEL_DEBUG, PSTR("Sensor overload")); + } + gesture_loop_counter += 1; + + delay(FIFO_PAUSE_TIME); + + + gstatus = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GSTATUS); + + + if( (gstatus & APDS9960_GVALID) == APDS9960_GVALID ) { + + + fifo_level = I2cRead8(APDS9960_I2C_ADDR,APDS9960_GFLVL) ; + + + if( fifo_level > 0) { + bytes_read = wireReadDataBlock( APDS9960_GFIFO_U, + (uint8_t*)fifo_data, + (fifo_level * 4) ); + if( bytes_read == -1 ) { + return ERROR; + } + + + if( bytes_read >= 4 ) { + for( i = 0; i < bytes_read; i += 4 ) { + gesture_data_.u_data[gesture_data_.index] = \ + fifo_data[i + 0]; + gesture_data_.d_data[gesture_data_.index] = \ + fifo_data[i + 1]; + gesture_data_.l_data[gesture_data_.index] = \ + fifo_data[i + 2]; + gesture_data_.r_data[gesture_data_.index] = \ + fifo_data[i + 3]; + gesture_data_.index++; + gesture_data_.total_gestures++; + } + + if( processGestureData() ) { + if( decodeGesture() ) { + + } + } + + gesture_data_.index = 0; + gesture_data_.total_gestures = 0; + } + } + } else { + + + delay(FIFO_PAUSE_TIME); + decodeGesture(); + motion = gesture_motion_; + resetGestureParameters(); + return motion; + } + } +} + + + + + +void enablePower(void) +{ + setMode(POWER, 1) ; +} + + + + + +void disablePower(void) +{ + setMode(POWER, 0) ; +} +# 1600 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +void readAllColorAndProximityData(void) +{ + if (I2cReadBuffer(APDS9960_I2C_ADDR, APDS9960_CDATAL, (uint8_t *) &color_data, (uint16_t)9)) + { + + + } +} +# 1616 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +void resetGestureParameters(void) +{ + gesture_data_.index = 0; + gesture_data_.total_gestures = 0; + + gesture_ud_delta_ = 0; + gesture_lr_delta_ = 0; + + gesture_ud_count_ = 0; + gesture_lr_count_ = 0; + + gesture_state_ = 0; + gesture_motion_ = DIR_NONE; +} + + + + + + +bool processGestureData(void) +{ + uint8_t u_first = 0; + uint8_t d_first = 0; + uint8_t l_first = 0; + uint8_t r_first = 0; + uint8_t u_last = 0; + uint8_t d_last = 0; + uint8_t l_last = 0; + uint8_t r_last = 0; + uint16_t ud_ratio_first; + uint16_t lr_ratio_first; + uint16_t ud_ratio_last; + uint16_t lr_ratio_last; + uint16_t ud_delta; + uint16_t lr_delta; + uint16_t i; + + + if( gesture_data_.total_gestures <= 4 ) { + return false; + } + + + if( (gesture_data_.total_gestures <= 32) && \ + (gesture_data_.total_gestures > 0) ) { + + + for( i = 0; i < gesture_data_.total_gestures; i++ ) { + if( (gesture_data_.u_data[i] > GESTURE_THRESHOLD_OUT) && + (gesture_data_.d_data[i] > GESTURE_THRESHOLD_OUT) && + (gesture_data_.l_data[i] > GESTURE_THRESHOLD_OUT) && + (gesture_data_.r_data[i] > GESTURE_THRESHOLD_OUT) ) { + + u_first = gesture_data_.u_data[i]; + d_first = gesture_data_.d_data[i]; + l_first = gesture_data_.l_data[i]; + r_first = gesture_data_.r_data[i]; + break; + } + } + + + if( (u_first == 0) || (d_first == 0) || \ + (l_first == 0) || (r_first == 0) ) { + + return false; + } + + for( i = gesture_data_.total_gestures - 1; i >= 0; i-- ) { + + if( (gesture_data_.u_data[i] > GESTURE_THRESHOLD_OUT) && + (gesture_data_.d_data[i] > GESTURE_THRESHOLD_OUT) && + (gesture_data_.l_data[i] > GESTURE_THRESHOLD_OUT) && + (gesture_data_.r_data[i] > GESTURE_THRESHOLD_OUT) ) { + + u_last = gesture_data_.u_data[i]; + d_last = gesture_data_.d_data[i]; + l_last = gesture_data_.l_data[i]; + r_last = gesture_data_.r_data[i]; + break; + } + } + } + + + ud_ratio_first = ((u_first - d_first) * 100) / (u_first + d_first); + lr_ratio_first = ((l_first - r_first) * 100) / (l_first + r_first); + ud_ratio_last = ((u_last - d_last) * 100) / (u_last + d_last); + lr_ratio_last = ((l_last - r_last) * 100) / (l_last + r_last); + + + ud_delta = ud_ratio_last - ud_ratio_first; + lr_delta = lr_ratio_last - lr_ratio_first; + + + gesture_ud_delta_ += ud_delta; + gesture_lr_delta_ += lr_delta; + + + if( gesture_ud_delta_ >= GESTURE_SENSITIVITY_1 ) { + gesture_ud_count_ = 1; + } else if( gesture_ud_delta_ <= -GESTURE_SENSITIVITY_1 ) { + gesture_ud_count_ = -1; + } else { + gesture_ud_count_ = 0; + } + + + if( gesture_lr_delta_ >= GESTURE_SENSITIVITY_1 ) { + gesture_lr_count_ = 1; + } else if( gesture_lr_delta_ <= -GESTURE_SENSITIVITY_1 ) { + gesture_lr_count_ = -1; + } else { + gesture_lr_count_ = 0; + } + return false; +} + + + + + + +bool decodeGesture(void) +{ + + + if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == 0) ) { + gesture_motion_ = DIR_UP; + } else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == 0) ) { + gesture_motion_ = DIR_DOWN; + } else if( (gesture_ud_count_ == 0) && (gesture_lr_count_ == 1) ) { + gesture_motion_ = DIR_RIGHT; + } else if( (gesture_ud_count_ == 0) && (gesture_lr_count_ == -1) ) { + gesture_motion_ = DIR_LEFT; + } else if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == 1) ) { + if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) { + gesture_motion_ = DIR_UP; + } else { + gesture_motion_ = DIR_RIGHT; + } + } else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == -1) ) { + if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) { + gesture_motion_ = DIR_DOWN; + } else { + gesture_motion_ = DIR_LEFT; + } + } else if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == -1) ) { + if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) { + gesture_motion_ = DIR_UP; + } else { + gesture_motion_ = DIR_LEFT; + } + } else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == 1) ) { + if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) { + gesture_motion_ = DIR_DOWN; + } else { + gesture_motion_ = DIR_RIGHT; + } + } else { + return false; + } + + return true; +} + +void handleGesture(void) { + if (isGestureAvailable() ) { + switch (readGesture()) { + case DIR_UP: + AddLog_P(LOG_LEVEL_DEBUG, PSTR("UP")); + snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Up")); + break; + case DIR_DOWN: + AddLog_P(LOG_LEVEL_DEBUG, PSTR("DOWN")); + snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Down")); + break; + case DIR_LEFT: + AddLog_P(LOG_LEVEL_DEBUG, PSTR("LEFT")); + snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Left")); + break; + case DIR_RIGHT: + AddLog_P(LOG_LEVEL_DEBUG, PSTR("RIGHT")); + snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Right")); + break; + default: + if(APDS9960_overload) + { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("LONG")); + snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Long")); + } + else{ + AddLog_P(LOG_LEVEL_DEBUG, PSTR("NONE")); + snprintf_P(currentGesture, sizeof(currentGesture), PSTR("None")); + } + } + MqttPublishSensor(); + } +} + +void APDS9960_adjustATime(void) +{ + + I2cValidRead16LE(&color_data.a, APDS9960_I2C_ADDR, APDS9960_CDATAL); + + + if (color_data.a < (uint16_t)20){ + APDS9960_aTime = 0x40; + } + else if (color_data.a < (uint16_t)40){ + APDS9960_aTime = 0x80; + } + else if (color_data.a < (uint16_t)50){ + APDS9960_aTime = DEFAULT_ATIME; + } + else if (color_data.a < (uint16_t)70){ + APDS9960_aTime = 0xc0; + } + if (color_data.a < 200){ + APDS9960_aTime = 0xe9; + } + + + + else{ + APDS9960_aTime = 0xff; + } + + + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, APDS9960_aTime); + enablePower(); + enableLightSensor(); + delay(20); +} + + +void APDS9960_loop(void) +{ + if (recovery_loop_counter > 0){ + recovery_loop_counter -= 1; + } + if (recovery_loop_counter == 1 && APDS9960_overload){ + enableGestureSensor(); + APDS9960_overload = false; + Response_P(PSTR("{\"Gesture\":\"On\"}")); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data); + gesture_mode = 1; + } + + if (gesture_mode) { + if (recovery_loop_counter == 0){ + handleGesture(); + + if (APDS9960_overload) + { + disableGestureSensor(); + recovery_loop_counter = APDS9960_LONG_RECOVERY; + Response_P(PSTR("{\"Gesture\":\"Off\"}")); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data); + gesture_mode = 0; + } + } + } +} + +void APDS9960_detect(void) +{ + if (APDS9960type || I2cActive(APDS9960_I2C_ADDR)) { return; } + + APDS9960type = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ID); + if (APDS9960type == APDS9960_CHIPID_1 || APDS9960type == APDS9960_CHIPID_2) { + if (APDS9960_init()) { + I2cSetActiveFound(APDS9960_I2C_ADDR, APDS9960stype); + + enableProximitySensor(); + enableGestureSensor(); + } else { + APDS9960type = 0; + } + } else { + APDS9960type = 0; + } + currentGesture[0] = '\0'; +} + + + + + +void APDS9960_show(bool json) +{ + if (!APDS9960type) { return; } + + if (!gesture_mode && !APDS9960_overload) { + char red_chr[10]; + char green_chr[10]; + char blue_chr[10]; + char ambient_chr[10]; + char cct_chr[10]; + char prox_chr[10]; + + readAllColorAndProximityData(); + + sprintf (ambient_chr, "%u", color_data.a/4); + sprintf (red_chr, "%u", color_data.r); + sprintf (green_chr, "%u", color_data.g); + sprintf (blue_chr, "%u", color_data.b ); + sprintf (prox_chr, "%u", color_data.p ); + + + + + + calculateColorTemperature(); + sprintf (cct_chr, "%u", color_data.cct); + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"Red\":%s,\"Green\":%s,\"Blue\":%s,\"Ambient\":%s,\"CCT\":%s,\"Proximity\":%s}"), + APDS9960stype, red_chr, green_chr, blue_chr, ambient_chr, cct_chr, prox_chr); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_APDS_9960_SNS, red_chr, green_chr, blue_chr, ambient_chr, cct_chr, prox_chr ); +#endif + } + } + else { + if (json && (currentGesture[0] != '\0' )) { + ResponseAppend_P(PSTR(",\"%s\":{\"%s\":1}"), APDS9960stype, currentGesture); + currentGesture[0] = '\0'; + } + } +} +# 1961 "S:/Development/Tasmota/tasmota/xsns_27_apds9960.ino" +bool APDS9960CommandSensor(void) +{ + bool serviced = true; + + switch (XdrvMailbox.payload) { + case 0: + disableGestureSensor(); + gesture_mode = 0; + enableLightSensor(); + APDS9960_overload = false; + break; + case 1: + if (APDS9960type) { + setGestureGain(DEFAULT_GGAIN); + setProximityGain(DEFAULT_PGAIN); + disableLightSensor(); + enableGestureSensor(); + gesture_mode = 1; + } + break; + case 2: + if (APDS9960type) { + setGestureGain(GGAIN_2X); + setProximityGain(PGAIN_2X); + disableLightSensor(); + enableGestureSensor(); + gesture_mode = 1; + } + break; + default: + int temp_aTime = (uint8_t)XdrvMailbox.payload; + if (temp_aTime > 2 && temp_aTime < 256){ + disablePower(); + I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, temp_aTime); + enablePower(); + enableLightSensor(); + } + break; + } + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_27, GetStateText(gesture_mode)); + + return serviced; +} + + + + + +bool Xsns27(uint8_t function) +{ + if (!I2cEnabled(XI2C_21)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + APDS9960_detect(); + } + else if (APDS9960type) { + switch (function) { + case FUNC_EVERY_50_MSECOND: + APDS9960_loop(); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_27 == XdrvMailbox.index) { + result = APDS9960CommandSensor(); + } + break; + case FUNC_JSON_APPEND: + APDS9960_show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + APDS9960_show(0); + break; +#endif + } + } + return result; +} +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_28_tm1638.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_28_tm1638.ino" +#ifdef USE_TM1638 + + + + + + +#define XSNS_28 28 + +#define TM1638_COLOR_NONE 0 +#define TM1638_COLOR_RED 1 +#define TM1638_COLOR_GREEN 2 + +#define TM1638_CLOCK_DELAY 1 + +uint8_t tm1638_type = 1; +uint8_t tm1638_clock_pin = 0; +uint8_t tm1638_data_pin = 0; +uint8_t tm1638_strobe_pin = 0; +uint8_t tm1638_displays = 8; +uint8_t tm1638_active_display = 1; +uint8_t tm1638_intensity = 0; +uint8_t tm1638_state = 0; + + + + + + +void Tm16XXSend(uint8_t data) +{ + for (uint32_t i = 0; i < 8; i++) { + digitalWrite(tm1638_data_pin, !!(data & (1 << i))); + digitalWrite(tm1638_clock_pin, LOW); + delayMicroseconds(TM1638_CLOCK_DELAY); + digitalWrite(tm1638_clock_pin, HIGH); + } +} + +void Tm16XXSendCommand(uint8_t cmd) +{ + digitalWrite(tm1638_strobe_pin, LOW); + Tm16XXSend(cmd); + digitalWrite(tm1638_strobe_pin, HIGH); +} + +void TM16XXSendData(uint8_t address, uint8_t data) +{ + Tm16XXSendCommand(0x44); + digitalWrite(tm1638_strobe_pin, LOW); + Tm16XXSend(0xC0 | address); + Tm16XXSend(data); + digitalWrite(tm1638_strobe_pin, HIGH); +} + +uint8_t Tm16XXReceive(void) +{ + uint8_t temp = 0; + + + pinMode(tm1638_data_pin, INPUT); + digitalWrite(tm1638_data_pin, HIGH); + + for (uint32_t i = 0; i < 8; ++i) { + digitalWrite(tm1638_clock_pin, LOW); + delayMicroseconds(TM1638_CLOCK_DELAY); + temp |= digitalRead(tm1638_data_pin) << i; + digitalWrite(tm1638_clock_pin, HIGH); + } + + + pinMode(tm1638_data_pin, OUTPUT); + digitalWrite(tm1638_data_pin, LOW); + + return temp; +} + + + +void Tm16XXClearDisplay(void) +{ + for (uint32_t i = 0; i < tm1638_displays; i++) { + TM16XXSendData(i << 1, 0); + } +} + +void Tm1638SetLED(uint8_t color, uint8_t pos) +{ + TM16XXSendData((pos << 1) + 1, color); +} + +void Tm1638SetLEDs(word leds) +{ + for (uint32_t i = 0; i < tm1638_displays; i++) { + uint8_t color = 0; + + if ((leds & (1 << i)) != 0) { + color |= TM1638_COLOR_RED; + } + + if ((leds & (1 << (i + 8))) != 0) { + color |= TM1638_COLOR_GREEN; + } + + Tm1638SetLED(color, i); + } +} + +uint8_t Tm1638GetButtons(void) +{ + uint8_t keys = 0; + + digitalWrite(tm1638_strobe_pin, LOW); + Tm16XXSend(0x42); + for (uint32_t i = 0; i < 4; i++) { + keys |= Tm16XXReceive() << i; + } + digitalWrite(tm1638_strobe_pin, HIGH); + + return keys; +} + + + +void TmInit(void) +{ + tm1638_type = 0; + if ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99)) { + tm1638_clock_pin = pin[GPIO_TM16CLK]; + tm1638_data_pin = pin[GPIO_TM16DIO]; + tm1638_strobe_pin = pin[GPIO_TM16STB]; + + pinMode(tm1638_data_pin, OUTPUT); + pinMode(tm1638_clock_pin, OUTPUT); + pinMode(tm1638_strobe_pin, OUTPUT); + + digitalWrite(tm1638_strobe_pin, HIGH); + digitalWrite(tm1638_clock_pin, HIGH); + + Tm16XXSendCommand(0x40); + Tm16XXSendCommand(0x80 | (tm1638_active_display ? 8 : 0) | tmin(7, tm1638_intensity)); + + digitalWrite(tm1638_strobe_pin, LOW); + Tm16XXSend(0xC0); + for (uint32_t i = 0; i < 16; i++) { + Tm16XXSend(0x00); + } + digitalWrite(tm1638_strobe_pin, HIGH); + + tm1638_type = 1; + tm1638_state = 1; + } +} + +void TmLoop(void) +{ + if (tm1638_state) { + uint8_t buttons = Tm1638GetButtons(); + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { + SwitchSetVirtual(i, (buttons &1) ^1); + uint8_t color = (SwitchGetVirtual(i)) ? TM1638_COLOR_NONE : TM1638_COLOR_RED; + Tm1638SetLED(color, i); + buttons >>= 1; + } + SwitchHandler(1); + } +} +# 201 "S:/Development/Tasmota/tasmota/xsns_28_tm1638.ino" +bool Xsns28(uint8_t function) +{ + bool result = false; + + if (tm1638_type) { + switch (function) { + case FUNC_INIT: + TmInit(); + break; + case FUNC_EVERY_50_MSECOND: + TmLoop(); + break; +# 223 "S:/Development/Tasmota/tasmota/xsns_28_tm1638.ino" + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_29_mcp230xx.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_29_mcp230xx.ino" +#ifdef USE_I2C +#ifdef USE_MCP230xx +# 31 "S:/Development/Tasmota/tasmota/xsns_29_mcp230xx.ino" +#define XSNS_29 29 +#define XI2C_22 22 + + + + + +uint8_t MCP230xx_IODIR = 0x00; +uint8_t MCP230xx_GPINTEN = 0x02; +uint8_t MCP230xx_IOCON = 0x05; +uint8_t MCP230xx_GPPU = 0x06; +uint8_t MCP230xx_INTF = 0x07; +uint8_t MCP230xx_INTCAP = 0x08; +uint8_t MCP230xx_GPIO = 0x09; + +uint8_t mcp230xx_type = 0; +uint8_t mcp230xx_pincount = 0; +uint8_t mcp230xx_int_en = 0; +uint8_t mcp230xx_int_prio_counter = 0; +uint8_t mcp230xx_int_counter_en = 0; +uint8_t mcp230xx_int_retainer_en = 0; +uint8_t mcp230xx_int_sec_counter = 0; + +uint8_t mcp230xx_int_report_defer_counter[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +uint16_t mcp230xx_int_counter[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +uint8_t mcp230xx_int_retainer[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +unsigned long int_millis[16]; + +const char MCP230XX_SENSOR_RESPONSE[] PROGMEM = "{\"Sensor29_D%i\":{\"MODE\":%i,\"PULL_UP\":\"%s\",\"INT_MODE\":\"%s\",\"STATE\":\"%s\"}}"; + +const char MCP230XX_INTCFG_RESPONSE[] PROGMEM = "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}"; + +#ifdef USE_MCP230xx_OUTPUT +const char MCP230XX_CMND_RESPONSE[] PROGMEM = "{\"S29cmnd_D%i\":{\"COMMAND\":\"%s\",\"STATE\":\"%s\"}}"; +#endif + +void MCP230xx_CheckForIntCounter(void) { + uint8_t en = 0; + for (uint32_t ca=0;ca<16;ca++) { + if (Settings.mcp230xx_config[ca].int_count_en) { + en=1; + } + } + if (!Settings.mcp230xx_int_timer) en=0; + mcp230xx_int_counter_en=en; + if (!mcp230xx_int_counter_en) { + for (uint32_t ca=0;ca<16;ca++) { + mcp230xx_int_counter[ca] = 0; + } + } +} + +void MCP230xx_CheckForIntRetainer(void) { + uint8_t en = 0; + for (uint32_t ca=0;ca<16;ca++) { + if (Settings.mcp230xx_config[ca].int_retain_flag) { + en=1; + } + } + mcp230xx_int_retainer_en=en; + if (!mcp230xx_int_retainer_en) { + for (uint32_t ca=0;ca<16;ca++) { + mcp230xx_int_retainer[ca] = 0; + } + } +} + +const char* ConvertNumTxt(uint8_t statu, uint8_t pinmod=0) { +#ifdef USE_MCP230xx_OUTPUT +if ((6 == pinmod) && (statu < 2)) { statu = abs(statu-1); } +#endif + switch (statu) { + case 0: + return "OFF"; + break; + case 1: + return "ON"; + break; +#ifdef USE_MCP230xx_OUTPUT + case 2: + return "TOGGLE"; + break; +#endif + } + return ""; +} + +const char* IntModeTxt(uint8_t intmo) { + switch (intmo) { + case 0: + return "ALL"; + break; + case 1: + return "EVENT"; + break; + case 2: + return "TELE"; + break; + case 3: + return "DISABLED"; + break; + } + return ""; +} + +uint8_t MCP230xx_readGPIO(uint8_t port) { + return I2cRead8(USE_MCP230xx_ADDR, MCP230xx_GPIO + port); +} + +void MCP230xx_ApplySettings(void) +{ + uint8_t int_en = 0; + for (uint32_t mcp230xx_port = 0; mcp230xx_port < mcp230xx_type; mcp230xx_port++) { + uint8_t reg_gppu = 0; + uint8_t reg_gpinten = 0; + uint8_t reg_iodir = 0xFF; +#ifdef USE_MCP230xx_OUTPUT + uint8_t reg_portpins = 0x00; +#endif + for (uint32_t idx = 0; idx < 8; idx++) { + switch (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pinmode) { + case 0 ... 1: + reg_iodir |= (1 << idx); + break; + case 2 ... 4: + reg_iodir |= (1 << idx); + reg_gpinten |= (1 << idx); + int_en = 1; + break; +#ifdef USE_MCP230xx_OUTPUT + case 5 ... 6: + reg_iodir &= ~(1 << idx); + if (Settings.flag.save_state) { + reg_portpins |= (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].saved_state << idx); + } else { + if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) { + reg_portpins |= (1 << idx); + } + } + break; +#endif + default: + break; + } +#ifdef USE_MCP230xx_OUTPUT + if ((Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) && (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pinmode < 5)) { + reg_gppu |= (1 << idx); + } +#else + if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) { + reg_gppu |= (1 << idx); + } +#endif + } + I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPPU+mcp230xx_port, reg_gppu); + I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPINTEN+mcp230xx_port, reg_gpinten); + I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IODIR+mcp230xx_port, reg_iodir); +#ifdef USE_MCP230xx_OUTPUT + I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPIO+mcp230xx_port, reg_portpins); +#endif + } + for (uint32_t idx=0;idx 0) { + if (I2cValidRead8(&mcp230xx_intcap, USE_MCP230xx_ADDR, MCP230xx_INTCAP+mcp230xx_port)) { + for (uint32_t intp = 0; intp < 8; intp++) { + if ((intf >> intp) & 0x01) { + report_int = 0; + if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].pinmode > 1) { + switch (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].pinmode) { + case 2: + report_int = 1; + break; + case 3: + if (((mcp230xx_intcap >> intp) & 0x01) == 0) report_int = 1; + break; + case 4: + if (((mcp230xx_intcap >> intp) & 0x01) == 1) report_int = 1; + break; + default: + break; + } + + if ((mcp230xx_int_counter_en) && (report_int)) { + if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_count_en) { + mcp230xx_int_counter[intp+(mcp230xx_port*8)]++; + } + } + + if (report_int) { + if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_defer) { + mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)]++; + if (mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)] >= Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_defer) { + mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)]=0; + } else { + report_int = 0; + } + } + } + + if (report_int) { + if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_retain_flag) { + mcp230xx_int_retainer[intp+(mcp230xx_port*8)] = 1; + report_int = 0; + } + } + if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_count_en) { + report_int = 0; + } + if (report_int) { + bool int_tele = false; + bool int_event = false; + unsigned long millis_now = millis(); + unsigned long millis_since_last_int = millis_now - int_millis[intp+(mcp230xx_port*8)]; + int_millis[intp+(mcp230xx_port*8)]=millis_now; + switch (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_mode) { + case 0: + int_tele=true; + int_event=true; + break; + case 1: + int_event=true; + break; + case 2: + int_tele=true; + break; + } + if (int_tele) { + ResponseTime_P(PSTR(",\"MCP230XX_INT\":{\"D%i\":%i,\"MS\":%lu}}"), + intp+(mcp230xx_port*8), ((mcp230xx_intcap >> intp) & 0x01),millis_since_last_int); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("MCP230XX_INT")); + } + if (int_event) { + char command[19]; + sprintf(command,"event MCPINT_D%i=%i",intp+(mcp230xx_port*8),((mcp230xx_intcap >> intp) & 0x01)); + ExecuteCommand(command, SRC_RULE); + } + } + } + } + } + } + } + } + } +} + +void MCP230xx_Show(bool json) +{ + if (json) { + uint8_t gpio = MCP230xx_readGPIO(0); + ResponseAppend_P(PSTR(",\"MCP230XX\":{\"D0\":%i,\"D1\":%i,\"D2\":%i,\"D3\":%i,\"D4\":%i,\"D5\":%i,\"D6\":%i,\"D7\":%i"), + (gpio>>0)&1,(gpio>>1)&1,(gpio>>2)&1,(gpio>>3)&1,(gpio>>4)&1,(gpio>>5)&1,(gpio>>6)&1,(gpio>>7)&1); + if (2 == mcp230xx_type) { + gpio = MCP230xx_readGPIO(1); + ResponseAppend_P(PSTR(",\"D8\":%i,\"D9\":%i,\"D10\":%i,\"D11\":%i,\"D12\":%i,\"D13\":%i,\"D14\":%i,\"D15\":%i"), + (gpio>>0)&1,(gpio>>1)&1,(gpio>>2)&1,(gpio>>3)&1,(gpio>>4)&1,(gpio>>5)&1,(gpio>>6)&1,(gpio>>7)&1); + } + ResponseJsonEnd(); + } +} + +#ifdef USE_MCP230xx_OUTPUT + +void MCP230xx_SetOutPin(uint8_t pin,uint8_t pinstate) { + uint8_t portpins; + uint8_t port = 0; + uint8_t pinmo = Settings.mcp230xx_config[pin].pinmode; + uint8_t interlock = Settings.flag.interlock; + int pinadd = (pin % 2)+1-(3*(pin % 2)); + char cmnd[7], stt[4]; + if (pin > 7) { port = 1; } + portpins = MCP230xx_readGPIO(port); + if (interlock && (pinmo == Settings.mcp230xx_config[pin+pinadd].pinmode)) { + if (pinstate < 2) { + if (6 == pinmo) { + if (pinstate) portpins |= (1 << (pin-(port*8))); else portpins |= (1 << (pin+pinadd-(port*8))),portpins &= ~(1 << (pin-(port*8))); + } else { + if (pinstate) portpins &= ~(1 << (pin+pinadd-(port*8))),portpins |= (1 << (pin-(port*8))); else portpins &= ~(1 << (pin-(port*8))); + } + } else { + if (6 == pinmo) { + portpins |= (1 << (pin+pinadd-(port*8))),portpins ^= (1 << (pin-(port*8))); + } else { + portpins &= ~(1 << (pin+pinadd-(port*8))),portpins ^= (1 << (pin-(port*8))); + } + } + } else { + if (pinstate < 2) { + if (pinstate) portpins |= (1 << (pin-(port*8))); else portpins &= ~(1 << (pin-(port*8))); + } else { + portpins ^= (1 << (pin-(port*8))); + } + } + I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPIO + port, portpins); + if (Settings.flag.save_state) { + Settings.mcp230xx_config[pin].saved_state=portpins>>(pin-(port*8))&1; + Settings.mcp230xx_config[pin+pinadd].saved_state=portpins>>(pin+pinadd-(port*8))&1; + } + sprintf(cmnd,ConvertNumTxt(pinstate, pinmo)); + sprintf(stt,ConvertNumTxt((portpins >> (pin-(port*8))&1), pinmo)); + if (interlock && (pinmo == Settings.mcp230xx_config[pin+pinadd].pinmode)) { + char stt1[4]; + sprintf(stt1,ConvertNumTxt((portpins >> (pin+pinadd-(port*8))&1), pinmo)); + Response_P(PSTR("{\"S29cmnd_D%i\":{\"COMMAND\":\"%s\",\"STATE\":\"%s\"},\"S29cmnd_D%i\":{\"STATE\":\"%s\"}}"),pin, cmnd, stt, pin+pinadd, stt1); + } else { + Response_P(MCP230XX_CMND_RESPONSE, pin, cmnd, stt); + } +} + +#endif + +void MCP230xx_Reset(uint8_t pinmode) { + uint8_t pullup = 0; + if ((pinmode > 1) && (pinmode < 5)) { pullup=1; } + for (uint32_t pinx=0;pinx<16;pinx++) { + Settings.mcp230xx_config[pinx].pinmode=pinmode; + Settings.mcp230xx_config[pinx].pullup=pullup; + Settings.mcp230xx_config[pinx].saved_state=0; + if ((pinmode > 1) && (pinmode < 5)) { + Settings.mcp230xx_config[pinx].int_report_mode=0; + } else { + Settings.mcp230xx_config[pinx].int_report_mode=3; + } + Settings.mcp230xx_config[pinx].int_report_defer=0; + Settings.mcp230xx_config[pinx].int_count_en=0; + Settings.mcp230xx_config[pinx].int_retain_flag=0; + Settings.mcp230xx_config[pinx].spare13=0; + Settings.mcp230xx_config[pinx].spare14=0; + Settings.mcp230xx_config[pinx].spare15=0; + } + Settings.mcp230xx_int_prio = 0; + Settings.mcp230xx_int_timer = 0; + MCP230xx_ApplySettings(); + char pulluptxt[7]; + char intmodetxt[9]; + sprintf(pulluptxt,ConvertNumTxt(pullup)); + uint8_t intmode = 3; + if ((pinmode > 1) && (pinmode < 5)) { intmode = 0; } + sprintf(intmodetxt,IntModeTxt(intmode)); + Response_P(MCP230XX_SENSOR_RESPONSE,99,pinmode,pulluptxt,intmodetxt,""); +} + +bool MCP230xx_Command(void) +{ + bool serviced = true; + bool validpin = false; + uint8_t paramcount = 0; + if (XdrvMailbox.data_len > 0) { + paramcount=1; + } else { + serviced = false; + return serviced; + } + char sub_string[XdrvMailbox.data_len]; + for (uint32_t ca=0;ca 1) { + uint8_t intpri = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + if ((intpri >= 0) && (intpri <= 20)) { + Settings.mcp230xx_int_prio = intpri; + Response_P(MCP230XX_INTCFG_RESPONSE,"PRI",99,Settings.mcp230xx_int_prio); + return serviced; + } + } else { + Response_P(MCP230XX_INTCFG_RESPONSE,"PRI",99,Settings.mcp230xx_int_prio); + return serviced; + } + } + + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTTIMER")) { + if (paramcount > 1) { + uint8_t inttim = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + if ((inttim >= 0) && (inttim <= 3600)) { + Settings.mcp230xx_int_timer = inttim; + MCP230xx_CheckForIntCounter(); + Response_P(MCP230XX_INTCFG_RESPONSE,"TIMER",99,Settings.mcp230xx_int_timer); + return serviced; + } + } else { + Response_P(MCP230XX_INTCFG_RESPONSE,"TIMER",99,Settings.mcp230xx_int_timer); + return serviced; + } + } + + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTDEF")) { + if (paramcount > 1) { + uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + if (pin < mcp230xx_pincount) { + if (pin == 0) { + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true; + } else { + validpin = true; + } + } + if (validpin) { + if (paramcount > 2) { + uint8_t intdef = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); + if ((intdef >= 0) && (intdef <= 15)) { + Settings.mcp230xx_config[pin].int_report_defer=intdef; + if (Settings.mcp230xx_config[pin].int_count_en) { + Settings.mcp230xx_config[pin].int_count_en=0; + MCP230xx_CheckForIntCounter(); + AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled INTCNT for pin D%i"),pin); + } + Response_P(MCP230XX_INTCFG_RESPONSE,"DEF",pin,Settings.mcp230xx_config[pin].int_report_defer); + return serviced; + } else { + serviced=false; + return serviced; + } + } else { + Response_P(MCP230XX_INTCFG_RESPONSE,"DEF",pin,Settings.mcp230xx_config[pin].int_report_defer); + return serviced; + } + } + serviced = false; + return serviced; + } else { + serviced = false; + return serviced; + } + } + + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTCNT")) { + if (paramcount > 1) { + uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + if (pin < mcp230xx_pincount) { + if (pin == 0) { + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true; + } else { + validpin = true; + } + } + if (validpin) { + if (paramcount > 2) { + uint8_t intcnt = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); + if ((intcnt >= 0) && (intcnt <= 1)) { + Settings.mcp230xx_config[pin].int_count_en=intcnt; + if (Settings.mcp230xx_config[pin].int_report_defer) { + Settings.mcp230xx_config[pin].int_report_defer=0; + AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled INTDEF for pin D%i"),pin); + } + if (Settings.mcp230xx_config[pin].int_report_mode < 3) { + Settings.mcp230xx_config[pin].int_report_mode=3; + AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled immediate interrupt/telemetry reporting for pin D%i"),pin); + } + if ((Settings.mcp230xx_config[pin].int_count_en) && (!Settings.mcp230xx_int_timer)) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - INTCNT enabled for pin D%i but global INTTIMER is disabled!"),pin); + } + MCP230xx_CheckForIntCounter(); + Response_P(MCP230XX_INTCFG_RESPONSE,"CNT",pin,Settings.mcp230xx_config[pin].int_count_en); + return serviced; + } else { + serviced=false; + return serviced; + } + } else { + Response_P(MCP230XX_INTCFG_RESPONSE,"CNT",pin,Settings.mcp230xx_config[pin].int_count_en); + return serviced; + } + } + serviced = false; + return serviced; + } else { + serviced = false; + return serviced; + } + } + + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTRETAIN")) { + if (paramcount > 1) { + uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + if (pin < mcp230xx_pincount) { + if (pin == 0) { + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true; + } else { + validpin = true; + } + } + if (validpin) { + if (paramcount > 2) { + uint8_t int_retain = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); + if ((int_retain >= 0) && (int_retain <= 1)) { + Settings.mcp230xx_config[pin].int_retain_flag=int_retain; + Response_P(MCP230XX_INTCFG_RESPONSE,"INT_RETAIN",pin,Settings.mcp230xx_config[pin].int_retain_flag); + MCP230xx_CheckForIntRetainer(); + return serviced; + } else { + serviced=false; + return serviced; + } + } else { + Response_P(MCP230XX_INTCFG_RESPONSE,"INT_RETAIN",pin,Settings.mcp230xx_config[pin].int_retain_flag); + return serviced; + } + } + serviced = false; + return serviced; + } else { + serviced = false; + return serviced; + } + } + + uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 1)); + + if (pin < mcp230xx_pincount) { + if (0 == pin) { + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1), "0")) validpin=true; + } else { + validpin=true; + } + } + if (validpin && (paramcount > 1)) { + if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "?")) { + uint8_t port = 0; + if (pin > 7) { port = 1; } + uint8_t portdata = MCP230xx_readGPIO(port); + char pulluptxtr[7],pinstatustxtr[7]; + char intmodetxt[9]; + sprintf(intmodetxt,IntModeTxt(Settings.mcp230xx_config[pin].int_report_mode)); + sprintf(pulluptxtr,ConvertNumTxt(Settings.mcp230xx_config[pin].pullup)); +#ifdef USE_MCP230xx_OUTPUT + uint8_t pinmod = Settings.mcp230xx_config[pin].pinmode; + sprintf(pinstatustxtr,ConvertNumTxt(portdata>>(pin-(port*8))&1,pinmod)); + Response_P(MCP230XX_SENSOR_RESPONSE,pin,pinmod,pulluptxtr,intmodetxt,pinstatustxtr); +#else + sprintf(pinstatustxtr,ConvertNumTxt(portdata>>(pin-(port*8))&1)); + Response_P(MCP230XX_SENSOR_RESPONSE,pin,Settings.mcp230xx_config[pin].pinmode,pulluptxtr,intmodetxt,pinstatustxtr); +#endif + return serviced; + } +#ifdef USE_MCP230xx_OUTPUT + if (Settings.mcp230xx_config[pin].pinmode >= 5) { + uint8_t pincmd = Settings.mcp230xx_config[pin].pinmode - 5; + if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "ON")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "1"))) { + MCP230xx_SetOutPin(pin,abs(pincmd-1)); + return serviced; + } + if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "OFF")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0"))) { + MCP230xx_SetOutPin(pin,pincmd); + return serviced; + } + if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "T")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "2"))) { + MCP230xx_SetOutPin(pin,2); + return serviced; + } + } +#endif + uint8_t pinmode = 0; + uint8_t pullup = 0; + uint8_t intmode = 0; + if (paramcount > 1) { + pinmode = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); + } + if (paramcount > 2) { + pullup = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); + } + if (paramcount > 3) { + intmode = atoi(subStr(sub_string, XdrvMailbox.data, ",", 4)); + } +#ifdef USE_MCP230xx_OUTPUT + if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 7) && (pullup < 2) && (paramcount > 2)) { +#else + if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 5) && (pullup < 2) && (paramcount > 2)) { +#endif + Settings.mcp230xx_config[pin].pinmode=pinmode; + Settings.mcp230xx_config[pin].pullup=pullup; + if ((pinmode > 1) && (pinmode < 5)) { + if ((intmode >= 0) && (intmode <= 3)) { + Settings.mcp230xx_config[pin].int_report_mode=intmode; + } + } else { + Settings.mcp230xx_config[pin].int_report_mode=3; + } + MCP230xx_ApplySettings(); + uint8_t port = 0; + if (pin > 7) { port = 1; } + uint8_t portdata = MCP230xx_readGPIO(port); + char pulluptxtc[7], pinstatustxtc[7]; + char intmodetxt[9]; + sprintf(pulluptxtc,ConvertNumTxt(pullup)); + sprintf(intmodetxt,IntModeTxt(Settings.mcp230xx_config[pin].int_report_mode)); +#ifdef USE_MCP230xx_OUTPUT + sprintf(pinstatustxtc,ConvertNumTxt(portdata>>(pin-(port*8))&1,Settings.mcp230xx_config[pin].pinmode)); +#else + sprintf(pinstatustxtc,ConvertNumTxt(portdata>>(pin-(port*8))&1)); +#endif + Response_P(MCP230XX_SENSOR_RESPONSE,pin,pinmode,pulluptxtc,intmodetxt,pinstatustxtc); + return serviced; + } + } else { + serviced=false; + return serviced; + } + return serviced; +} + +#ifdef USE_MCP230xx_DISPLAYOUTPUT + +const char HTTP_SNS_MCP230xx_OUTPUT[] PROGMEM = "{s}MCP230XX D%d{m}%s{e}"; + +void MCP230xx_UpdateWebData(void) +{ + uint8_t gpio1 = MCP230xx_readGPIO(0); + uint8_t gpio2 = 0; + if (2 == mcp230xx_type) { + gpio2 = MCP230xx_readGPIO(1); + } + uint16_t gpio = (gpio2 << 8) + gpio1; + for (uint32_t pin = 0; pin < mcp230xx_pincount; pin++) { + if (Settings.mcp230xx_config[pin].pinmode >= 5) { + char stt[7]; + sprintf(stt,ConvertNumTxt((gpio>>pin)&1,Settings.mcp230xx_config[pin].pinmode)); + WSContentSend_PD(HTTP_SNS_MCP230xx_OUTPUT, pin, stt); + } + } +} + +#endif + +#ifdef USE_MCP230xx_OUTPUT + +void MCP230xx_OutputTelemetry(void) +{ + uint8_t outputcount = 0; + uint16_t gpiototal = 0; + uint8_t gpioa = 0; + uint8_t gpiob = 0; + gpioa=MCP230xx_readGPIO(0); + if (2 == mcp230xx_type) { gpiob=MCP230xx_readGPIO(1); } + gpiototal=((uint16_t)gpiob << 8) | gpioa; + for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) { + if (Settings.mcp230xx_config[pinx].pinmode >= 5) outputcount++; + } + if (outputcount) { + char stt[7]; + ResponseTime_P(PSTR(",\"MCP230_OUT\":{")); + for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) { + if (Settings.mcp230xx_config[pinx].pinmode >= 5) { + sprintf(stt,ConvertNumTxt(((gpiototal>>pinx)&1),Settings.mcp230xx_config[pinx].pinmode)); + ResponseAppend_P(PSTR("\"OUT_D%i\":\"%s\","),pinx,stt); + } + } + ResponseAppend_P(PSTR("\"END\":1}}")); + MqttPublishTeleSensor(); + } +} + +#endif + +void MCP230xx_Interrupt_Counter_Report(void) { + ResponseTime_P(PSTR(",\"MCP230_INTTIMER\":{")); + for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) { + if (Settings.mcp230xx_config[pinx].int_count_en) { + ResponseAppend_P(PSTR("\"INTCNT_D%i\":%i,"),pinx,mcp230xx_int_counter[pinx]); + mcp230xx_int_counter[pinx]=0; + } + } + ResponseAppend_P(PSTR("\"END\":1}}")); + MqttPublishTeleSensor(); + mcp230xx_int_sec_counter = 0; +} + +void MCP230xx_Interrupt_Retain_Report(void) { + uint16_t retainresult = 0; + ResponseTime_P(PSTR(",\"MCP_INTRETAIN\":{")); + for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) { + if (Settings.mcp230xx_config[pinx].int_retain_flag) { + ResponseAppend_P(PSTR("\"D%i\":%i,"),pinx,mcp230xx_int_retainer[pinx]); + retainresult |= (((mcp230xx_int_retainer[pinx])&1) << pinx); + mcp230xx_int_retainer[pinx]=0; + } + } + ResponseAppend_P(PSTR("\"Value\":%u}}"),retainresult); + MqttPublishTeleSensor(); +} + + + + + +bool Xsns29(uint8_t function) +{ + if (!I2cEnabled(XI2C_22)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + MCP230xx_Detect(); + } + else if (mcp230xx_type) { + switch (function) { + case FUNC_EVERY_50_MSECOND: + if (mcp230xx_int_en) { + mcp230xx_int_prio_counter++; + if ((mcp230xx_int_prio_counter) >= (Settings.mcp230xx_int_prio)) { + MCP230xx_CheckForInterrupt(); + mcp230xx_int_prio_counter=0; + } + } + break; + case FUNC_EVERY_SECOND: + if (mcp230xx_int_counter_en) { + mcp230xx_int_sec_counter++; + if (mcp230xx_int_sec_counter >= Settings.mcp230xx_int_timer) { + MCP230xx_Interrupt_Counter_Report(); + } + } + if (tele_period == 0) { + if (mcp230xx_int_retainer_en) { + MCP230xx_Interrupt_Retain_Report(); + } +#ifdef USE_MCP230xx_OUTPUT + MCP230xx_OutputTelemetry(); +#endif + } + break; + case FUNC_JSON_APPEND: + MCP230xx_Show(1); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_29 == XdrvMailbox.index) { + result = MCP230xx_Command(); + } + break; +#ifdef USE_WEBSERVER +#ifdef USE_MCP230xx_OUTPUT +#ifdef USE_MCP230xx_DISPLAYOUTPUT + case FUNC_WEB_SENSOR: + MCP230xx_UpdateWebData(); + break; +#endif +#endif +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_30_mpr121.ino" +# 46 "S:/Development/Tasmota/tasmota/xsns_30_mpr121.ino" +#ifdef USE_I2C +#ifdef USE_MPR121 + + + + + +#define XSNS_30 30 +#define XI2C_23 23 + + + + + + + +#define MPR121_ELEX_REG 0x00 + + +#define MPR121_MHDR_REG 0x2B + + +#define MPR121_MHDR_VAL 0x01 + + +#define MPR121_NHDR_REG 0x2C + + +#define MPR121_NHDR_VAL 0x01 + + +#define MPR121_NCLR_REG 0x2D + + +#define MPR121_NCLR_VAL 0x0E + + +#define MPR121_MHDF_REG 0x2F + + +#define MPR121_MHDF_VAL 0x01 + + +#define MPR121_NHDF_REG 0x30 + + +#define MPR121_NHDF_VAL 0x05 + + +#define MPR121_NCLF_REG 0x31 + + +#define MPR121_NCLF_VAL 0x01 + + +#define MPR121_MHDPROXR_REG 0x36 + + +#define MPR121_MHDPROXR_VAL 0x3F + + +#define MPR121_NHDPROXR_REG 0x37 + + +#define MPR121_NHDPROXR_VAL 0x5F + + +#define MPR121_NCLPROXR_REG 0x38 + + +#define MPR121_NCLPROXR_VAL 0x04 + + +#define MPR121_FDLPROXR_REG 0x39 + + +#define MPR121_FDLPROXR_VAL 0x00 + + +#define MPR121_MHDPROXF_REG 0x3A + + +#define MPR121_MHDPROXF_VAL 0x01 + + +#define MPR121_NHDPROXF_REG 0x3B + + +#define MPR121_NHDPROXF_VAL 0x01 + + +#define MPR121_NCLPROXF_REG 0x3C + + +#define MPR121_NCLPROXF_VAL 0x1F + + +#define MPR121_FDLPROXF_REG 0x3D + + +#define MPR121_FDLPROXF_VAL 0x04 + + +#define MPR121_E0TTH_REG 0x41 + + +#define MPR121_E0TTH_VAL 12 + + +#define MPR121_E0RTH_REG 0x42 + + +#define MPR121_E0RTH_VAL 6 + + +#define MPR121_CDT_REG 0x5D + + +#define MPR121_CDT_VAL 0x20 + + +#define MPR121_ECR_REG 0x5E + + +#define MPR121_ECR_VAL 0x8F + + + +#define MPR121_SRST_REG 0x80 + + +#define MPR121_SRST_VAL 0x63 + + +#define BITC(sensor,position) ((pS->current[sensor] >> position) & 1) + + +#define BITP(sensor,position) ((pS->previous[sensor] >> position) & 1) +# 195 "S:/Development/Tasmota/tasmota/xsns_30_mpr121.ino" +typedef struct mpr121 mpr121; +struct mpr121 { + const uint8_t i2c_addr[4] = { 0x5A, 0x5B, 0x5C, 0x5D }; + const char id[4] = { 'A', 'B', 'C', 'D' }; + bool connected[4] = { false, false, false, false }; + bool running[4] = { false, false, false, false }; + uint16_t current[4] = { 0x0000, 0x0000, 0x0000, 0x0000 }; + uint16_t previous[4] = { 0x0000, 0x0000, 0x0000, 0x0000 }; +}; + +bool mpr21_found = false; +# 217 "S:/Development/Tasmota/tasmota/xsns_30_mpr121.ino" +void Mpr121Init(struct mpr121 *pS, bool initial) +{ + + for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) { + + if (initial && I2cActive(pS->i2c_addr[i])) { continue; } + + + pS->connected[i] = (I2cWrite8(pS->i2c_addr[i], MPR121_SRST_REG, MPR121_SRST_VAL) + && (0x24 == I2cRead8(pS->i2c_addr[i], 0x5D))); + if (pS->connected[i]) { + + + mpr21_found = true; + char device_name[16]; + snprintf_P(device_name, sizeof(device_name), PSTR("MPR121(%c)"), pS->id[i]); + I2cSetActiveFound(pS->i2c_addr[i], device_name); + + + for (uint32_t j = 0; j < 13; j++) { + + + I2cWrite8(pS->i2c_addr[i], MPR121_E0TTH_REG + 2 * j, MPR121_E0TTH_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_E0RTH_REG + 2 * j, MPR121_E0RTH_VAL); + } + + + I2cWrite8(pS->i2c_addr[i], MPR121_MHDR_REG, MPR121_MHDR_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_NHDR_REG, MPR121_NHDR_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_NCLR_REG, MPR121_NCLR_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_MHDF_REG, MPR121_MHDF_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_NHDF_REG, MPR121_NHDF_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_NCLF_REG, MPR121_NCLF_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_MHDPROXR_REG, MPR121_MHDPROXR_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_NHDPROXR_REG, MPR121_NHDPROXR_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_NCLPROXR_REG, MPR121_NCLPROXR_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_FDLPROXR_REG, MPR121_FDLPROXR_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_MHDPROXF_REG, MPR121_MHDPROXF_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_NHDPROXF_REG, MPR121_NHDPROXF_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_NCLPROXF_REG, MPR121_NCLPROXF_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_FDLPROXF_REG, MPR121_FDLPROXF_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_CDT_REG, MPR121_CDT_VAL); + + + I2cWrite8(pS->i2c_addr[i], MPR121_ECR_REG, MPR121_ECR_VAL); + + + pS->running[i] = (0x00 != I2cRead8(pS->i2c_addr[i], MPR121_ECR_REG)); + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_I2C "MPR121%c: %sRunning"), pS->id[i], (pS->running[i]) ? "" : "NOT"); + + } else { + + + pS->running[i] = false; + } + } + + + if (!(pS->connected[0] || pS->connected[1] || pS->connected[2] + || pS->connected[3])) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C "MPR121: No sensors found")); + } +} +# 326 "S:/Development/Tasmota/tasmota/xsns_30_mpr121.ino" +void Mpr121Show(struct mpr121 *pS, uint8_t function) +{ + + + for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) { + + + if (pS->connected[i]) { + + + if (!I2cValidRead16LE(&pS->current[i], pS->i2c_addr[i], MPR121_ELEX_REG)) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_I2C "MPR121%c: ERROR: Cannot read data!"), pS->id[i]); + Mpr121Init(pS, false); + return; + } + + if (BITC(i, 15)) { + + + I2cWrite8(pS->i2c_addr[i], MPR121_ELEX_REG, 0x00); + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_I2C "MPR121%c: ERROR: Excess current detected! Fix circuits if it happens repeatedly! Soft-resetting MPR121 ..."), pS->id[i]); + Mpr121Init(pS, false); + return; + } + } + + if (pS->running[i]) { + + + if (FUNC_JSON_APPEND == function) { + ResponseAppend_P(PSTR(",\"MPR121%c\":{"), pS->id[i]); + } + + for (uint32_t j = 0; j < 13; j++) { + + + if ((FUNC_EVERY_50_MSECOND == function) + && (BITC(i, j) != BITP(i, j))) { + Response_P(PSTR("{\"MPR121%c\":{\"Button%i\":%i}}"), pS->id[i], j, BITC(i, j)); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, mqtt_data); + } + +#ifdef USE_WEBSERVER + if (FUNC_WEB_SENSOR == function) { + WSContentSend_PD(PSTR("{s}MPR121%c Button%d{m}%d{e}"), pS->id[i], j, BITC(i, j)); + } +#endif + + + if (FUNC_JSON_APPEND == function) { + ResponseAppend_P(PSTR("%s\"Button%i\":%i"), (j > 0 ? "," : ""), j, BITC(i, j)); + } + } + + + pS->previous[i] = pS->current[i]; + + + if (FUNC_JSON_APPEND == function) { + ResponseJsonEnd(); + } + } + } +} +# 410 "S:/Development/Tasmota/tasmota/xsns_30_mpr121.ino" +bool Xsns30(uint8_t function) +{ + if (!I2cEnabled(XI2C_23)) { return false; } + + bool result = false; + + + static struct mpr121 mpr121; + + if (FUNC_INIT == function) { + + Mpr121Init(&mpr121, true); + } + else if (mpr21_found) { + + switch (function) { + + + case FUNC_EVERY_50_MSECOND: + Mpr121Show(&mpr121, FUNC_EVERY_50_MSECOND); + break; + + + case FUNC_JSON_APPEND: + Mpr121Show(&mpr121, FUNC_JSON_APPEND); + break; + +#ifdef USE_WEBSERVER + + case FUNC_WEB_SENSOR: + Mpr121Show(&mpr121, FUNC_WEB_SENSOR); + break; +#endif + } + } + + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_31_ccs811.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_31_ccs811.ino" +#ifdef USE_I2C +#ifdef USE_CCS811 +# 30 "S:/Development/Tasmota/tasmota/xsns_31_ccs811.ino" +#define XSNS_31 31 +#define XI2C_24 24 + +#define EVERYNSECONDS 5 + +#include "Adafruit_CCS811.h" + +Adafruit_CCS811 ccs; +uint8_t CCS811_ready = 0; +uint8_t CCS811_type = 0;; +uint16_t eCO2; +uint16_t TVOC; +uint8_t tcnt = 0; +uint8_t ecnt = 0; + + + +void CCS811Detect(void) +{ + if (I2cActive(CCS811_ADDRESS)) { return; } + + if (!ccs.begin(CCS811_ADDRESS)) { + CCS811_type = 1; + I2cSetActiveFound(CCS811_ADDRESS, "CCS811"); + } +} + +void CCS811Update(void) +{ + tcnt++; + if (tcnt >= EVERYNSECONDS) { + tcnt = 0; + CCS811_ready = 0; + if (ccs.available()) { + if (!ccs.readData()){ + TVOC = ccs.getTVOC(); + eCO2 = ccs.geteCO2(); + CCS811_ready = 1; + if (global_update && global_humidity>0 && global_temperature!=9999) { ccs.setEnvironmentalData((uint8_t)global_humidity, global_temperature); } + ecnt = 0; + } + } else { + + ecnt++; + if (ecnt > 6) { + + ccs.begin(CCS811_ADDRESS); + } + } + } +} + +const char HTTP_SNS_CCS811[] PROGMEM = + "{s}CCS811 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" + "{s}CCS811 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}"; + +void CCS811Show(bool json) +{ + if (CCS811_ready) { + if (json) { + ResponseAppend_P(PSTR(",\"CCS811\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d}"), eCO2,TVOC); +#ifdef USE_DOMOTICZ + if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, eCO2); +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_CCS811, eCO2, TVOC); +#endif + } + } +} + + + + + +bool Xsns31(uint8_t function) +{ + if (!I2cEnabled(XI2C_24)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + CCS811Detect(); + } + else if (CCS811_type) { + switch (function) { + case FUNC_EVERY_SECOND: + CCS811Update(); + break; + case FUNC_JSON_APPEND: + CCS811Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + CCS811Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_32_mpu6050.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_32_mpu6050.ino" +#ifdef USE_I2C +#ifdef USE_MPU6050 +# 30 "S:/Development/Tasmota/tasmota/xsns_32_mpu6050.ino" +#define XSNS_32 32 +#define XI2C_25 25 + +#define D_SENSOR_MPU6050 "MPU6050" + +#define MPU_6050_ADDR_AD0_LOW 0x68 +#define MPU_6050_ADDR_AD0_HIGH 0x69 + +uint8_t MPU_6050_address; +uint8_t MPU_6050_addresses[] = { MPU_6050_ADDR_AD0_LOW, MPU_6050_ADDR_AD0_HIGH }; +uint8_t MPU_6050_found; + +int16_t MPU_6050_ax = 0, MPU_6050_ay = 0, MPU_6050_az = 0; +int16_t MPU_6050_gx = 0, MPU_6050_gy = 0, MPU_6050_gz = 0; +int16_t MPU_6050_temperature = 0; + +#ifdef USE_MPU6050_DMP + #include "MPU6050_6Axis_MotionApps20.h" + #include "I2Cdev.h" + #include + typedef struct MPU6050_DMP{ + uint8_t devStatus; + uint16_t packetSize; + uint16_t fifoCount; + uint8_t fifoBuffer[64]; + Quaternion q; + VectorInt16 aa; + VectorInt16 aaReal; + VectorFloat gravity; + float euler[3]; + float yawPitchRoll[3]; + } MPU6050_DMP; + + MPU6050_DMP MPU6050_dmp; +#else + #include +#endif +MPU6050 mpu6050; + +void MPU_6050PerformReading(void) +{ +#ifdef USE_MPU6050_DMP + mpu6050.resetFIFO(); + MPU6050_dmp.fifoCount = mpu6050.getFIFOCount(); + while (MPU6050_dmp.fifoCount < MPU6050_dmp.packetSize) MPU6050_dmp.fifoCount = mpu6050.getFIFOCount(); + mpu6050.getFIFOBytes(MPU6050_dmp.fifoBuffer, MPU6050_dmp.packetSize); + MPU6050_dmp.fifoCount -= MPU6050_dmp.packetSize; + + mpu6050.dmpGetQuaternion(&MPU6050_dmp.q, MPU6050_dmp.fifoBuffer); + mpu6050.dmpGetEuler(MPU6050_dmp.euler, &MPU6050_dmp.q); + mpu6050.dmpGetAccel(&MPU6050_dmp.aa, MPU6050_dmp.fifoBuffer); + mpu6050.dmpGetGravity(&MPU6050_dmp.gravity, &MPU6050_dmp.q); + mpu6050.dmpGetLinearAccel(&MPU6050_dmp.aaReal, &MPU6050_dmp.aa, &MPU6050_dmp.gravity); + mpu6050.dmpGetYawPitchRoll(MPU6050_dmp.yawPitchRoll, &MPU6050_dmp.q, &MPU6050_dmp.gravity); + MPU_6050_gx = MPU6050_dmp.euler[0] * 180/M_PI; + MPU_6050_gy = MPU6050_dmp.euler[1] * 180/M_PI; + MPU_6050_gz = MPU6050_dmp.euler[2] * 180/M_PI; + MPU_6050_ax = MPU6050_dmp.aaReal.x; + MPU_6050_ay = MPU6050_dmp.aaReal.y; + MPU_6050_az = MPU6050_dmp.aaReal.z; +#else + mpu6050.getMotion6( + &MPU_6050_ax, + &MPU_6050_ay, + &MPU_6050_az, + &MPU_6050_gx, + &MPU_6050_gy, + &MPU_6050_gz + ); +#endif + MPU_6050_temperature = mpu6050.getTemperature(); +} +# 119 "S:/Development/Tasmota/tasmota/xsns_32_mpu6050.ino" +void MPU_6050Detect(void) +{ + for (uint32_t i = 0; i < sizeof(MPU_6050_addresses); i++) + { + MPU_6050_address = MPU_6050_addresses[i]; + if (!I2cSetDevice(MPU_6050_address)) { break; } + mpu6050.setAddr(MPU_6050_addresses[i]); + +#ifdef USE_MPU6050_DMP + MPU6050_dmp.devStatus = mpu6050.dmpInitialize(); + mpu6050.setXGyroOffset(220); + mpu6050.setYGyroOffset(76); + mpu6050.setZGyroOffset(-85); + mpu6050.setZAccelOffset(1788); + if (MPU6050_dmp.devStatus == 0) { + mpu6050.setDMPEnabled(true); + MPU6050_dmp.packetSize = mpu6050.dmpGetFIFOPacketSize(); + MPU_6050_found = true; + } +#else + mpu6050.initialize(); + MPU_6050_found = mpu6050.testConnection(); +#endif + Settings.flag2.axis_resolution = 2; + } + + if (MPU_6050_found) { + I2cSetActiveFound(MPU_6050_address, D_SENSOR_MPU6050); + } +} + +#define D_YAW "Yaw" +#define D_PITCH "Pitch" +#define D_ROLL "Roll" + +#ifdef USE_WEBSERVER +const char HTTP_SNS_AXIS[] PROGMEM = + "{s}" D_SENSOR_MPU6050 " " D_AX_AXIS "{m}%s{e}" + "{s}" D_SENSOR_MPU6050 " " D_AY_AXIS "{m}%s{e}" + "{s}" D_SENSOR_MPU6050 " " D_AZ_AXIS "{m}%s{e}" + "{s}" D_SENSOR_MPU6050 " " D_GX_AXIS "{m}%s{e}" + "{s}" D_SENSOR_MPU6050 " " D_GY_AXIS "{m}%s{e}" + "{s}" D_SENSOR_MPU6050 " " D_GZ_AXIS "{m}%s{e}"; +#ifdef USE_MPU6050_DMP +const char HTTP_SNS_YPR[] PROGMEM = + "{s}" D_SENSOR_MPU6050 " " D_YAW "{m}%s{e}" + "{s}" D_SENSOR_MPU6050 " " D_PITCH "{m}%s{e}" + "{s}" D_SENSOR_MPU6050 " " D_ROLL "{m}%s{e}"; +#endif +#endif + +#define D_JSON_AXIS_AX "AccelXAxis" +#define D_JSON_AXIS_AY "AccelYAxis" +#define D_JSON_AXIS_AZ "AccelZAxis" +#define D_JSON_AXIS_GX "GyroXAxis" +#define D_JSON_AXIS_GY "GyroYAxis" +#define D_JSON_AXIS_GZ "GyroZAxis" +#define D_JSON_YAW "Yaw" +#define D_JSON_PITCH "Pitch" +#define D_JSON_ROLL "Roll" + +void MPU_6050Show(bool json) +{ + MPU_6050PerformReading(); + + double tempConv = (MPU_6050_temperature / 340.0 + 35.53); + char temperature[33]; + dtostrfd(tempConv, Settings.flag2.temperature_resolution, temperature); + char axis_ax[33]; + dtostrfd(MPU_6050_ax, Settings.flag2.axis_resolution, axis_ax); + char axis_ay[33]; + dtostrfd(MPU_6050_ay, Settings.flag2.axis_resolution, axis_ay); + char axis_az[33]; + dtostrfd(MPU_6050_az, Settings.flag2.axis_resolution, axis_az); + char axis_gx[33]; + dtostrfd(MPU_6050_gx, Settings.flag2.axis_resolution, axis_gx); + char axis_gy[33]; + dtostrfd(MPU_6050_gy, Settings.flag2.axis_resolution, axis_gy); + char axis_gz[33]; + dtostrfd(MPU_6050_gz, Settings.flag2.axis_resolution, axis_gz); +#ifdef USE_MPU6050_DMP + char axis_yaw[33]; + dtostrfd(MPU6050_dmp.yawPitchRoll[0] / PI * 180.0, Settings.flag2.axis_resolution, axis_yaw); + char axis_pitch[33]; + dtostrfd(MPU6050_dmp.yawPitchRoll[1] / PI * 180.0, Settings.flag2.axis_resolution, axis_pitch); + char axis_roll[33]; + dtostrfd(MPU6050_dmp.yawPitchRoll[2] / PI * 180.0, Settings.flag2.axis_resolution, axis_roll); +#endif + + if (json) { + char json_axis_ax[25]; + snprintf_P(json_axis_ax, sizeof(json_axis_ax), PSTR(",\"" D_JSON_AXIS_AX "\":%s"), axis_ax); + char json_axis_ay[25]; + snprintf_P(json_axis_ay, sizeof(json_axis_ay), PSTR(",\"" D_JSON_AXIS_AY "\":%s"), axis_ay); + char json_axis_az[25]; + snprintf_P(json_axis_az, sizeof(json_axis_az), PSTR(",\"" D_JSON_AXIS_AZ "\":%s"), axis_az); + char json_axis_gx[25]; + snprintf_P(json_axis_gx, sizeof(json_axis_gx), PSTR(",\"" D_JSON_AXIS_GX "\":%s"), axis_gx); + char json_axis_gy[25]; + snprintf_P(json_axis_gy, sizeof(json_axis_gy), PSTR(",\"" D_JSON_AXIS_GY "\":%s"), axis_gy); + char json_axis_gz[25]; + snprintf_P(json_axis_gz, sizeof(json_axis_gz), PSTR(",\"" D_JSON_AXIS_GZ "\":%s"), axis_gz); +#ifdef USE_MPU6050_DMP + char json_ypr_y[25]; + snprintf_P(json_ypr_y, sizeof(json_ypr_y), PSTR(",\"" D_JSON_YAW "\":%s"), axis_yaw); + char json_ypr_p[25]; + snprintf_P(json_ypr_p, sizeof(json_ypr_p), PSTR(",\"" D_JSON_PITCH "\":%s"), axis_pitch); + char json_ypr_r[25]; + snprintf_P(json_ypr_r, sizeof(json_ypr_r), PSTR(",\"" D_JSON_ROLL "\":%s"), axis_roll); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s%s%s%s}"), + D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz, + json_ypr_y, json_ypr_p, json_ypr_r); +#else + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s}"), + D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz); +#endif +#ifdef USE_DOMOTICZ + DomoticzSensor(DZ_TEMP, temperature); +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, D_SENSOR_MPU6050, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_AXIS, axis_ax, axis_ay, axis_az, axis_gx, axis_gy, axis_gz); +#ifdef USE_MPU6050_DMP + WSContentSend_PD(HTTP_SNS_YPR, axis_yaw, axis_pitch, axis_roll); +#endif +#endif + } +} + + + + + +bool Xsns32(uint8_t function) +{ + if (!I2cEnabled(XI2C_25)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + MPU_6050Detect(); + } + else if (MPU_6050_found) { + switch (function) { + case FUNC_EVERY_SECOND: + if (tele_period == Settings.tele_period -3) { + MPU_6050PerformReading(); + } + break; + case FUNC_JSON_APPEND: + MPU_6050Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MPU_6050Show(0); + MPU_6050PerformReading(); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_33_ds3231.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_33_ds3231.ino" +#ifdef USE_I2C +#ifdef USE_DS3231 +# 35 "S:/Development/Tasmota/tasmota/xsns_33_ds3231.ino" +#define XSNS_33 33 +#define XI2C_26 26 + + +#ifndef USE_RTC_ADDR +#define USE_RTC_ADDR 0x68 +#endif + + +#define RTC_SECONDS 0x00 +#define RTC_MINUTES 0x01 +#define RTC_HOURS 0x02 +#define RTC_DAY 0x03 +#define RTC_DATE 0x04 +#define RTC_MONTH 0x05 +#define RTC_YEAR 0x06 +#define RTC_CONTROL 0x0E +#define RTC_STATUS 0x0F + +#define OSF 7 +#define EOSC 7 +#define BBSQW 6 +#define CONV 5 +#define RS2 4 +#define RS1 3 +#define INTCN 2 + + +#define HR1224 6 +#define CENTURY 7 +#define DYDT 6 +bool ds3231ReadStatus = false; +bool ds3231WriteStatus = false; +bool DS3231chipDetected = false; + + + + +void DS3231Detect(void) +{ + if (I2cActive(USE_RTC_ADDR)) { return; } + + if (I2cValidRead(USE_RTC_ADDR, RTC_STATUS, 1)) { + I2cSetActiveFound(USE_RTC_ADDR, "DS3231"); + DS3231chipDetected = true; + } +} + + + + +uint8_t bcd2dec(uint8_t n) +{ + return n - 6 * (n >> 4); +} + + + + +uint8_t dec2bcd(uint8_t n) +{ + return n + 6 * (n / 10); +} + + + + +uint32_t ReadFromDS3231(void) +{ + TIME_T tm; + tm.second = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_SECONDS)); + tm.minute = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_MINUTES)); + tm.hour = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_HOURS) & ~_BV(HR1224)); + tm.day_of_week = I2cRead8(USE_RTC_ADDR, RTC_DAY); + tm.day_of_month = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_DATE)); + tm.month = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_MONTH) & ~_BV(CENTURY)); + tm.year = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_YEAR)); + return MakeTime(tm); +} + + + +void SetDS3231Time (uint32_t epoch_time) { + TIME_T tm; + BreakTime(epoch_time, tm); + I2cWrite8(USE_RTC_ADDR, RTC_SECONDS, dec2bcd(tm.second)); + I2cWrite8(USE_RTC_ADDR, RTC_MINUTES, dec2bcd(tm.minute)); + I2cWrite8(USE_RTC_ADDR, RTC_HOURS, dec2bcd(tm.hour)); + I2cWrite8(USE_RTC_ADDR, RTC_DAY, tm.day_of_week); + I2cWrite8(USE_RTC_ADDR, RTC_DATE, dec2bcd(tm.day_of_month)); + I2cWrite8(USE_RTC_ADDR, RTC_MONTH, dec2bcd(tm.month)); + I2cWrite8(USE_RTC_ADDR, RTC_YEAR, dec2bcd(tm.year)); + I2cWrite8(USE_RTC_ADDR, RTC_STATUS, I2cRead8(USE_RTC_ADDR, RTC_STATUS) & ~_BV(OSF)); +} + +void DS3231EverySecond(void) +{ + TIME_T tmpTime; + if (!ds3231ReadStatus && Rtc.utc_time < START_VALID_TIME ) { + ntp_force_sync = true; + Rtc.utc_time = ReadFromDS3231(); + + + BreakTime(Rtc.utc_time, tmpTime); + if (Rtc.utc_time < START_VALID_TIME ) { + ds3231ReadStatus = true; + } + RtcTime.year = tmpTime.year + 1970; + Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year); + Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year); + AddLog_P2(LOG_LEVEL_INFO, PSTR("Set time from DS3231 to RTC (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), + GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str()); + if (Rtc.local_time < START_VALID_TIME) { + rules_flag.time_init = 1; + } else { + rules_flag.time_set = 1; + } + } + else if (!ds3231WriteStatus && Rtc.utc_time > START_VALID_TIME && abs(Rtc.utc_time - ReadFromDS3231()) > 60) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("Write Time TO DS3231 from NTP (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), + GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str()); + SetDS3231Time (Rtc.utc_time); + ds3231WriteStatus = true; + } +} + + + + + +bool Xsns33(uint8_t function) +{ + if (!I2cEnabled(XI2C_26)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + DS3231Detect(); + } + else if (DS3231chipDetected) { + switch (function) { + case FUNC_EVERY_SECOND: + DS3231EverySecond(); + break; + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_34_hx711.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_34_hx711.ino" +#ifdef USE_HX711 +# 35 "S:/Development/Tasmota/tasmota/xsns_34_hx711.ino" +#define XSNS_34 34 + +#ifndef HX_MAX_WEIGHT +#define HX_MAX_WEIGHT 20000 +#endif +#ifndef HX_REFERENCE +#define HX_REFERENCE 250 +#endif +#ifndef HX_SCALE +#define HX_SCALE 120 +#endif + +#define HX_TIMEOUT 120 +#define HX_SAMPLES 10 +#define HX_CAL_TIMEOUT 15 + +#define HX_GAIN_128 1 +#define HX_GAIN_32 2 +#define HX_GAIN_64 3 + +#define D_JSON_WEIGHT_REF "WeightRef" +#define D_JSON_WEIGHT_CAL "WeightCal" +#define D_JSON_WEIGHT_MAX "WeightMax" +#define D_JSON_WEIGHT_ITEM "WeightItem" +#define D_JSON_WEIGHT_CHANGE "WeightChange" +#define D_JSON_WEIGHT_RAW "WeightRaw" +#define D_JSON_WEIGHT_DELTA "WeightDelta" + +enum HxCalibrationSteps { HX_CAL_END, HX_CAL_LIMBO, HX_CAL_FINISH, HX_CAL_FAIL, HX_CAL_DONE, HX_CAL_FIRST, HX_CAL_RESET, HX_CAL_START }; + +const char kHxCalibrationStates[] PROGMEM = D_HX_CAL_FAIL "|" D_HX_CAL_DONE "|" D_HX_CAL_REFERENCE "|" D_HX_CAL_REMOVE; + +struct HX { + long weight = 0; + long raw = 0; + long last_weight = 0; + long sum_weight = 0; + long sum_raw = 0; + long offset = 0; + long scale = 1; + long weight_diff = 0; + uint8_t type = 1; + uint8_t sample_count = 0; + uint8_t calibrate_step = HX_CAL_END; + uint8_t calibrate_timer = 0; + uint8_t calibrate_msg = 0; + uint8_t pin_sck; + uint8_t pin_dout; + bool tare_flg = false; + bool weight_changed = false; + uint16_t weight_delta = 4; +} Hx; + + + +bool HxIsReady(uint16_t timeout) +{ + + uint32_t start = millis(); + while ((digitalRead(Hx.pin_dout) == HIGH) && (millis() - start < timeout)) { yield(); } + return (digitalRead(Hx.pin_dout) == LOW); +} + +long HxRead(void) +{ + if (!HxIsReady(HX_TIMEOUT)) { return -1; } + + uint8_t data[3] = { 0 }; + uint8_t filler = 0x00; + + + data[2] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST); + data[1] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST); + data[0] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST); + + + for (unsigned int i = 0; i < HX_GAIN_128; i++) { + digitalWrite(Hx.pin_sck, HIGH); + digitalWrite(Hx.pin_sck, LOW); + } + + + if (data[2] & 0x80) { filler = 0xFF; } + + + unsigned long value = ( static_cast(filler) << 24 + | static_cast(data[2]) << 16 + | static_cast(data[1]) << 8 + | static_cast(data[0]) ); + + return static_cast(value); +} + + + +void HxResetPart(void) +{ + Hx.tare_flg = true; + Hx.sum_weight = 0; + Hx.sample_count = 0; + Hx.last_weight = 0; +} + +void HxReset(void) +{ + HxResetPart(); + Settings.energy_frequency_calibration = 0; +} + +void HxCalibrationStateTextJson(uint8_t msg_id) +{ + char cal_text[30]; + + Hx.calibrate_msg = msg_id; + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, GetTextIndexed(cal_text, sizeof(cal_text), Hx.calibrate_msg, kHxCalibrationStates)); + + if (msg_id < 3) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("Sensor34")); } +} + +void SetWeightDelta() +{ + + if (Settings.weight_change == 0) { + Hx.weight_delta = 4; + return; + } + + + if (Settings.weight_change > 100) { + Hx.weight_delta = (Settings.weight_change - 100) * 10 + 100; + return; + } + + + Hx.weight_delta = Settings.weight_change - 1; +} +# 192 "S:/Development/Tasmota/tasmota/xsns_34_hx711.ino" +bool HxCommand(void) +{ + bool serviced = true; + bool show_parms = false; + char sub_string[XdrvMailbox.data_len +1]; + + for (uint32_t ca = 0; ca < XdrvMailbox.data_len; ca++) { + if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; } + } + + switch (XdrvMailbox.payload) { + case 1: + HxReset(); + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, "Reset"); + break; + case 2: + if (strstr(XdrvMailbox.data, ",") != nullptr) { + Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); + } + Hx.scale = 1; + HxReset(); + Hx.calibrate_step = HX_CAL_START; + Hx.calibrate_timer = 1; + HxCalibrationStateTextJson(3); + break; + case 3: + if (strstr(XdrvMailbox.data, ",") != nullptr) { + Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); + } + show_parms = true; + break; + case 4: + if (strstr(XdrvMailbox.data, ",") != nullptr) { + Settings.weight_calibration = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); + Hx.scale = Settings.weight_calibration; + } + show_parms = true; + break; + case 5: + if (strstr(XdrvMailbox.data, ",") != nullptr) { + Settings.weight_max = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10) / 1000; + } + show_parms = true; + break; + case 6: + if (strstr(XdrvMailbox.data, ",") != nullptr) { + Settings.weight_item = (unsigned long)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 2)) * 10); + } + show_parms = true; + break; + case 7: + Settings.energy_frequency_calibration = Hx.weight; + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, D_JSON_DONE); + break; + case 8: + if (strstr(XdrvMailbox.data, ",") != nullptr) { + Settings.SensorBits1.hx711_json_weight_change = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10) & 1; + } + show_parms = true; + break; + case 9: + if (strstr(XdrvMailbox.data, ",") != nullptr) { + Settings.weight_change = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); + SetWeightDelta(); + } + show_parms = true; + break; + default: + show_parms = true; + } + + if (show_parms) { + char item[33]; + dtostrfd((float)Settings.weight_item / 10, 1, item); + Response_P(PSTR("{\"Sensor34\":{\"" D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_MAX "\":%d,\"" + D_JSON_WEIGHT_ITEM "\":%s,\"" D_JSON_WEIGHT_CHANGE "\":%s,\"" D_JSON_WEIGHT_DELTA "\":%d}}"), + Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000, + item, GetStateText(Settings.SensorBits1.hx711_json_weight_change), Settings.weight_change); + } + + return serviced; +} + + + +long HxWeight(void) +{ + return (Hx.calibrate_step < HX_CAL_FAIL) ? Hx.weight : 0; +} + +void HxInit(void) +{ + Hx.type = 0; + if ((pin[GPIO_HX711_DAT] < 99) && (pin[GPIO_HX711_SCK] < 99)) { + Hx.pin_sck = pin[GPIO_HX711_SCK]; + Hx.pin_dout = pin[GPIO_HX711_DAT]; + + pinMode(Hx.pin_sck, OUTPUT); + pinMode(Hx.pin_dout, INPUT); + + digitalWrite(Hx.pin_sck, LOW); + + SetWeightDelta(); + + if (HxIsReady(8 * HX_TIMEOUT)) { + if (!Settings.weight_max) { Settings.weight_max = HX_MAX_WEIGHT / 1000; } + if (!Settings.weight_calibration) { Settings.weight_calibration = HX_SCALE; } + if (!Settings.weight_reference) { Settings.weight_reference = HX_REFERENCE; } + Hx.scale = Settings.weight_calibration; + HxRead(); + HxResetPart(); + Hx.type = 1; + } + } +} + +void HxEvery100mSecond(void) +{ + long raw = HxRead(); + Hx.sum_raw += raw; + Hx.sum_weight += raw; + + Hx.sample_count++; + if (HX_SAMPLES == Hx.sample_count) { + long average = Hx.sum_weight / Hx.sample_count; + long raw_average = Hx.sum_raw / Hx.sample_count; + long value = average - Hx.offset; + Hx.weight = value / Hx.scale; + Hx.raw = raw_average / Hx.scale; + if (Hx.weight < 0) { + if (Settings.energy_frequency_calibration) { + long difference = Settings.energy_frequency_calibration + Hx.weight; + Hx.last_weight = difference; + if (difference < 0) { HxReset(); } + } + Hx.weight = 0; + } else { + Hx.last_weight = Settings.energy_frequency_calibration; + } + + if (Hx.tare_flg) { + Hx.tare_flg = false; + Hx.offset = average; + } + + if (Hx.calibrate_step) { + Hx.calibrate_timer--; + + if (HX_CAL_START == Hx.calibrate_step) { + Hx.calibrate_step--; + Hx.calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES); + } + else if (HX_CAL_RESET == Hx.calibrate_step) { + if (Hx.calibrate_timer) { + if (Hx.weight < (long)Settings.weight_reference) { + Hx.calibrate_step--; + Hx.calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES); + HxCalibrationStateTextJson(2); + } + } else { + Hx.calibrate_step = HX_CAL_FAIL; + } + } + else if (HX_CAL_FIRST == Hx.calibrate_step) { + if (Hx.calibrate_timer) { + if (Hx.weight > (long)Settings.weight_reference) { + Hx.calibrate_step--; + } + } else { + Hx.calibrate_step = HX_CAL_FAIL; + } + } + else if (HX_CAL_DONE == Hx.calibrate_step) { + if (Hx.weight > (long)Settings.weight_reference) { + Hx.calibrate_step = HX_CAL_FINISH; + Settings.weight_calibration = Hx.weight / Settings.weight_reference; + Hx.weight = 0; + HxCalibrationStateTextJson(1); + } else { + Hx.calibrate_step = HX_CAL_FAIL; + } + } + + if (HX_CAL_FAIL == Hx.calibrate_step) { + Hx.calibrate_step--; + Hx.tare_flg = true; + HxCalibrationStateTextJson(0); + } + if (HX_CAL_FINISH == Hx.calibrate_step) { + Hx.calibrate_step--; + Hx.calibrate_timer = 3 * (10 / HX_SAMPLES); + Hx.scale = Settings.weight_calibration; + } + + if (!Hx.calibrate_timer) { + Hx.calibrate_step = HX_CAL_END; + } + } else { + Hx.weight += Hx.last_weight; + + if (Settings.SensorBits1.hx711_json_weight_change) { + if (abs(Hx.weight - Hx.weight_diff) > Hx.weight_delta) { + Hx.weight_diff = Hx.weight; + Hx.weight_changed = true; + } + else if (Hx.weight_changed && (Hx.weight == Hx.weight_diff)) { + mqtt_data[0] = '\0'; + ResponseAppendTime(); + HxShow(true); + ResponseJsonEnd(); + MqttPublishTeleSensor(); + Hx.weight_changed = false; + } + } + } + + Hx.sum_weight = 0; + Hx.sum_raw = 0; + Hx.sample_count = 0; + } +} + +void HxSaveBeforeRestart(void) +{ + Settings.energy_frequency_calibration = Hx.weight; + Hx.sample_count = HX_SAMPLES +1; +} + +#ifdef USE_WEBSERVER +const char HTTP_HX711_WEIGHT[] PROGMEM = + "{s}HX711 " D_WEIGHT "{m}%s " D_UNIT_KILOGRAM "{e}"; +const char HTTP_HX711_COUNT[] PROGMEM = + "{s}HX711 " D_COUNT "{m}%d{e}"; +const char HTTP_HX711_CAL[] PROGMEM = + "{s}HX711 %s{m}{e}"; +#endif + +void HxShow(bool json) +{ + char scount[30] = { 0 }; + + uint16_t count = 0; + float weight = 0; + if (Hx.calibrate_step < HX_CAL_FAIL) { + if (Hx.weight && Settings.weight_item) { + count = (Hx.weight * 10) / Settings.weight_item; + if (count > 1) { + snprintf_P(scount, sizeof(scount), PSTR(",\"" D_JSON_COUNT "\":%d"), count); + } + } + weight = (float)Hx.weight / 1000; + } + char weight_chr[33]; + dtostrfd(weight, Settings.flag2.weight_resolution, weight_chr); + + if (json) { + ResponseAppend_P(PSTR(",\"HX711\":{\"" D_JSON_WEIGHT "\":%s%s, \"" D_JSON_WEIGHT_RAW "\":%d}"), weight_chr, scount, Hx.raw); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_HX711_WEIGHT, weight_chr); + if (count > 1) { + WSContentSend_PD(HTTP_HX711_COUNT, count); + } + if (Hx.calibrate_step) { + char cal_text[30]; + WSContentSend_PD(HTTP_HX711_CAL, GetTextIndexed(cal_text, sizeof(cal_text), Hx.calibrate_msg, kHxCalibrationStates)); + } +#endif + } +} + +#ifdef USE_WEBSERVER +#ifdef USE_HX711_GUI + + + + +#define WEB_HANDLE_HX711 "s34" + +const char S_CONFIGURE_HX711[] PROGMEM = D_CONFIGURE_HX711; + +const char HTTP_BTN_MENU_MAIN_HX711[] PROGMEM = + "

"; + +const char HTTP_BTN_MENU_HX711[] PROGMEM = + "

"; + +const char HTTP_FORM_HX711[] PROGMEM = + "
 " D_CALIBRATION " " + "
" + "

" D_REFERENCE_WEIGHT " (" D_UNIT_KILOGRAM ")

" + "
" + "
" + "


" + + "
 " D_HX711_PARAMETERS " " + "
" + "

" D_ITEM_WEIGHT " (" D_UNIT_KILOGRAM ")

"; + +void HandleHxAction(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_HX711); + + if (WebServer->hasArg("save")) { + HxSaveSettings(); + HandleConfiguration(); + return; + } + + char stemp1[20]; + + if (WebServer->hasArg("reset")) { + snprintf_P(stemp1, sizeof(stemp1), PSTR("Sensor34 1")); + ExecuteWebCommand(stemp1, SRC_WEBGUI); + + HandleRoot(); + return; + } + + if (WebServer->hasArg("calibrate")) { + WebGetArg("p1", stemp1, sizeof(stemp1)); + Settings.weight_reference = (!strlen(stemp1)) ? 0 : (unsigned long)(CharToFloat(stemp1) * 1000); + + HxLogUpdates(); + + snprintf_P(stemp1, sizeof(stemp1), PSTR("Sensor34 2")); + ExecuteWebCommand(stemp1, SRC_WEBGUI); + + HandleRoot(); + return; + } + + WSContentStart_P(S_CONFIGURE_HX711); + WSContentSendStyle(); + dtostrfd((float)Settings.weight_reference / 1000, 3, stemp1); + char stemp2[20]; + dtostrfd((float)Settings.weight_item / 10000, 4, stemp2); + WSContentSend_P(HTTP_FORM_HX711, stemp1, stemp2); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void HxSaveSettings(void) +{ + char tmp[100]; + + WebGetArg("p2", tmp, sizeof(tmp)); + Settings.weight_item = (!strlen(tmp)) ? 0 : (unsigned long)(CharToFloat(tmp) * 10000); + + HxLogUpdates(); +} + +void HxLogUpdates(void) +{ + char weigth_ref_chr[33]; + dtostrfd((float)Settings.weight_reference / 1000, Settings.flag2.weight_resolution, weigth_ref_chr); + char weigth_item_chr[33]; + dtostrfd((float)Settings.weight_item / 10000, 4, weigth_item_chr); + + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_JSON_WEIGHT_REF " %s, " D_JSON_WEIGHT_ITEM " %s"), weigth_ref_chr, weigth_item_chr); +} + +#endif +#endif + + + + + +bool Xsns34(uint8_t function) +{ + bool result = false; + + if (Hx.type) { + switch (function) { + case FUNC_EVERY_100_MSECOND: + HxEvery100mSecond(); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_34 == XdrvMailbox.index) { + result = HxCommand(); + } + break; + case FUNC_JSON_APPEND: + HxShow(1); + break; + case FUNC_SAVE_BEFORE_RESTART: + HxSaveBeforeRestart(); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + HxShow(0); + break; +#ifdef USE_HX711_GUI + case FUNC_WEB_ADD_MAIN_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_MAIN_HX711); + break; + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_HX711); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/" WEB_HANDLE_HX711, HandleHxAction); + break; +#endif +#endif + case FUNC_INIT: + HxInit(); + break; + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_35_tx20.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_35_tx20.ino" +#if defined(USE_TX20_WIND_SENSOR) || defined(USE_TX23_WIND_SENSOR) +#if defined(USE_TX20_WIND_SENSOR) && defined(USE_TX23_WIND_SENSOR) +#undef USE_TX20_WIND_SENSOR +#warning **** use USE_TX20_WIND_SENSOR or USE_TX23_WIND_SENSOR but not both together, TX20 disabled **** +#endif +# 41 "S:/Development/Tasmota/tasmota/xsns_35_tx20.ino" +#define XSNS_35 35 + +#define TX2X_BIT_TIME 1220 +#define TX2X_RESET_VALUES 60 + + + +extern "C" { +#include "gpio.h" +} + +#ifdef USE_TX20_WIND_SENSOR + #define D_TX2x_NAME "TX20" +#else + #define D_TX2x_NAME "TX23" +#endif + +#ifdef USE_WEBSERVER +const char HTTP_SNS_TX2X[] PROGMEM = + "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_AVG "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION "{m}%s (%s°){e}"; +#endif + +const char kTx2xDirections[] PROGMEM = D_TX20_NORTH "|" + D_TX20_NORTH D_TX20_NORTH D_TX20_EAST "|" + D_TX20_NORTH D_TX20_EAST "|" + D_TX20_EAST D_TX20_NORTH D_TX20_EAST "|" + D_TX20_EAST "|" + D_TX20_EAST D_TX20_SOUTH D_TX20_EAST "|" + D_TX20_SOUTH D_TX20_EAST "|" + D_TX20_SOUTH D_TX20_SOUTH D_TX20_EAST "|" + D_TX20_SOUTH "|" + D_TX20_SOUTH D_TX20_SOUTH D_TX20_WEST "|" + D_TX20_SOUTH D_TX20_WEST "|" + D_TX20_WEST D_TX20_SOUTH D_TX20_WEST "|" + D_TX20_WEST "|" + D_TX20_WEST D_TX20_NORTH D_TX20_WEST "|" + D_TX20_NORTH D_TX20_WEST "|" + D_TX20_NORTH D_TX20_NORTH D_TX20_WEST; + +uint8_t tx2x_sa = 0; +uint8_t tx2x_sb = 0; +uint8_t tx2x_sd = 0; +uint8_t tx2x_se = 0; +uint16_t tx2x_sc = 0; +uint16_t tx2x_sf = 0; + +float tx2x_wind_speed_kmh = 0; +float tx2x_wind_speed_max = 0; +float tx2x_wind_speed_avg = 0; +float tx2x_wind_sum = 0; +int tx2x_count = 0; +uint8_t tx2x_wind_direction = 0; + +bool tx2x_available = false; + +#ifdef USE_TX23_WIND_SENSOR +uint8_t tx23_stage = 0; +#endif + +#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +void TX2xStartRead(void) ICACHE_RAM_ATTR; +#endif + +void TX2xStartRead(void) +{ +# 135 "S:/Development/Tasmota/tasmota/xsns_35_tx20.ino" +#ifdef USE_TX23_WIND_SENSOR + if (0!=tx23_stage) + { + if ((2==tx23_stage) || (3==tx23_stage)) + { +#endif + tx2x_available = false; + + tx2x_sa = 0; + tx2x_sb = 0; + tx2x_sd = 0; + tx2x_se = 0; + tx2x_sc = 0; + tx2x_sf = 0; + + delayMicroseconds(TX2X_BIT_TIME / 2); + + for (int32_t bitcount = 41; bitcount > 0; bitcount--) { + uint8_t dpin = (digitalRead(pin[GPIO_TX2X_TXD_BLACK])); +#ifdef USE_TX23_WIND_SENSOR + if (bitcount > 41 - 5) { + + tx2x_sa = (tx2x_sa << 1) | (dpin); + } else if (bitcount > 41 - 5 - 4) { + + tx2x_sb = tx2x_sb >> 1 | ((dpin) << 3); + } else if (bitcount > 41 - 5 - 4 - 12) { + + tx2x_sc = tx2x_sc >> 1 | ((dpin) << 11); + } else if (bitcount > 41 - 5 - 4 - 12 - 4) { + + tx2x_sd = tx2x_sd >> 1 | ((dpin) << 3); + } else if (bitcount > 41 - 5 - 4 - 12 - 4 - 4) { + + tx2x_se = tx2x_se >> 1 | ((dpin ^ 1) << 3); + } else { + + tx2x_sf = tx2x_sf >> 1 | ((dpin ^ 1) << 11); + } +#else + if (bitcount > 41 - 5) { + + tx2x_sa = (tx2x_sa << 1) | (dpin ^ 1); + } else if (bitcount > 41 - 5 - 4) { + + tx2x_sb = tx2x_sb >> 1 | ((dpin ^ 1) << 3); + } else if (bitcount > 41 - 5 - 4 - 12) { + + tx2x_sc = tx2x_sc >> 1 | ((dpin ^ 1) << 11); + } else if (bitcount > 41 - 5 - 4 - 12 - 4) { + + tx2x_sd = tx2x_sd >> 1 | ((dpin ^ 1) << 3); + } else if (bitcount > 41 - 5 - 4 - 12 - 4 - 4) { + + tx2x_se = tx2x_se >> 1 | (dpin << 3); + } else { + + tx2x_sf = tx2x_sf >> 1 | (dpin << 11); + } +#endif + delayMicroseconds(TX2X_BIT_TIME); + } + + uint8_t chk = (tx2x_sb + (tx2x_sc & 0xf) + ((tx2x_sc >> 4) & 0xf) + ((tx2x_sc >> 8) & 0xf)); + chk &= 0xf; + +#ifdef USE_TX23_WIND_SENSOR + + if ((chk == tx2x_sd) && (tx2x_sb==tx2x_se) && (tx2x_sc==tx2x_sf) && (tx2x_sc < 511)) { + tx2x_available = true; + } +#else + if ((chk == tx2x_sd) && (tx2x_sc < 511)) { + tx2x_available = true; + } +#endif +#ifdef USE_TX23_WIND_SENSOR + } + tx23_stage++; + } +#endif + + + + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << pin[GPIO_TX2X_TXD_BLACK]); +} + +void Tx2xReset(void) +{ + tx2x_count = 0; + tx2x_wind_sum = 0; + tx2x_wind_speed_max = 0; +} + +void Tx2xRead(void) +{ +#ifdef USE_TX23_WIND_SENSOR + + + if ((uptime % 3)==0) { + + + tx23_stage = 0; + pinMode(pin[GPIO_TX2X_TXD_BLACK], OUTPUT); + digitalWrite(pin[GPIO_TX2X_TXD_BLACK], LOW); + } else if ((uptime % 3)==1) { + + + tx23_stage = 1; + pinMode(pin[GPIO_TX2X_TXD_BLACK], INPUT_PULLUP); + } +#endif + if (0==Settings.tele_period && !(uptime % TX2X_RESET_VALUES)) { + Tx2xReset(); + } + else if (tx2x_available) { + + tx2x_wind_speed_kmh = float(tx2x_sc) * 0.36; + if (tx2x_wind_speed_kmh > tx2x_wind_speed_max) { + tx2x_wind_speed_max = tx2x_wind_speed_kmh; + } + tx2x_count++; + tx2x_wind_sum += tx2x_wind_speed_kmh; + tx2x_wind_speed_avg = tx2x_wind_sum / tx2x_count; + tx2x_wind_direction = tx2x_sb; + } +} + +void Tx2xInit(void) +{ +#ifdef USE_TX23_WIND_SENSOR + tx23_stage = 0; + pinMode(pin[GPIO_TX2X_TXD_BLACK], OUTPUT); + digitalWrite(pin[GPIO_TX2X_TXD_BLACK], LOW); +#else + pinMode(pin[GPIO_TX2X_TXD_BLACK], INPUT); +#endif + attachInterrupt(pin[GPIO_TX2X_TXD_BLACK], TX2xStartRead, RISING); +} + +void Tx2xShow(bool json) +{ + char wind_speed_string[33]; + dtostrfd(tx2x_wind_speed_kmh, 1, wind_speed_string); + char wind_speed_max_string[33]; + dtostrfd(tx2x_wind_speed_max, 1, wind_speed_max_string); + char wind_speed_avg_string[33]; + dtostrfd(tx2x_wind_speed_avg, 1, wind_speed_avg_string); + char wind_direction_string[4]; + GetTextIndexed(wind_direction_string, sizeof(wind_direction_string), tx2x_wind_direction, kTx2xDirections); + char wind_direction_degree[33]; + dtostrfd(tx2x_wind_direction*22.5, 1, wind_direction_degree); + + if (json) { + ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"Speed\":%s,\"SpeedAvg\":%s,\"SpeedMax\":%s,\"Direction\":\"%s\",\"Degree\":%s}"), + wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string, wind_direction_degree); + Tx2xReset(); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TX2X, wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string, wind_direction_degree); +#endif + } +} + + + + + +bool Xsns35(uint8_t function) +{ + bool result = false; + + if (pin[GPIO_TX2X_TXD_BLACK] < 99) { + switch (function) { + case FUNC_INIT: + Tx2xInit(); + break; + case FUNC_EVERY_SECOND: + Tx2xRead(); + break; + case FUNC_JSON_APPEND: + Tx2xShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Tx2xShow(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_36_mgc3130.ino" +# 22 "S:/Development/Tasmota/tasmota/xsns_36_mgc3130.ino" +#ifdef USE_I2C +#ifdef USE_MGC3130 +# 35 "S:/Development/Tasmota/tasmota/xsns_36_mgc3130.ino" +#define XSNS_36 36 +#define XI2C_27 27 + +#warning **** MGC3130: It is recommended to disable all unneeded I2C-drivers **** + +#define MGC3130_I2C_ADDR 0x42 + +#define MGC3130_xfer pin[GPIO_MGC3130_XFER] +#define MGC3130_reset pin[GPIO_MGC3130_RESET] + + +bool MGC3130_type = false; +char MGC3130stype[] = "MGC3130"; + + +#define MGC3130_SYSTEM_STATUS 0x15 +#define MGC3130_REQUEST_MSG 0x06 +#define MGC3130_FW_VERSION 0x83 +#define MGC3130_SET_RUNTIME 0xA2 +#define MGC3130_SENSOR_DATA 0x91 + + +#define MGC3130_GESTURE_GARBAGE 1 +#define MGC3130_FLICK_WEST_EAST 2 +#define MGC3130_FLICK_EAST_WEST 3 +#define MGC3130_FLICK_SOUTH_NORTH 4 +#define MGC3130_FLICK_NORTH_SOUTH 5 +#define MGC3130_CIRCLE_CLOCKWISE 6 +#define MGC3130_CIRCLE_CCLOCKWISE 7 + +#define MGC3130_MIN_ROTVALUE 0 +#define MGC3130_MAX_ROTVALUE 1023 +#define MGC3130_MIN_ZVALUE 32768 + + +#ifdef USE_WEBSERVER +const char HTTP_MGC_3130_SNS[] PROGMEM = + "{s}" "%s" "{m}%s{e}" + "{s}" "HwRev" "{m}%u.%u{e}" + "{s}" "loaderVer" "{m}%u.%u{e}" + "{s}" "platVer" "{m}%u{e}"; +#endif + + + + + + + +#pragma pack(1) +union MGC3130_Union{ + uint8_t buffer[132]; + struct + { + + uint8_t msgSize; + uint8_t flag; + uint8_t counter; + uint8_t id; + + struct { + uint8_t DSPStatus:1; + uint8_t gestureInfo:1; + uint8_t touchInfo:1; + uint8_t airWheelInfo:1; + uint8_t xyzPosition:1; + uint8_t noisePower:1; + uint8_t reserved:2; + uint8_t electrodeConfiguration:3; + uint8_t CICData:1; + uint8_t SDData:1; + uint16_t reserved2:3; + } outputConfigMask; + uint8_t timestamp; + struct { + uint8_t positionValid:1; + uint8_t airWheelValid:1; + uint8_t rawDataValid:1; + uint8_t noisePowerValid:1; + uint8_t environmentalNoise:1; + uint8_t clipping:1; + uint8_t reserved:1; + uint8_t DSPRunning:1; + } systemInfo; + uint16_t dspInfo; + struct { + uint8_t gestureCode:8; + uint8_t reserved:4; + uint8_t gestureType:4; + uint8_t edgeFlick:1; + uint16_t reserved2:14; + uint8_t gestureInProgress:1; + } gestureInfo; + struct { + uint8_t touchSouth:1; + uint8_t touchWest:1; + uint8_t touchNorth:1; + uint8_t touchEast:1; + uint8_t touchCentre:1; + uint8_t tapSouth:1; + uint8_t tapWest:1; + uint8_t tapNorth:1; + uint8_t tapEast :1; + uint8_t tapCentre:1; + uint8_t doubleTapSouth:1; + uint8_t doubleTapWest:1; + uint8_t doubleTapNorth:1; + uint8_t doubleTapEast:1; + uint8_t doubleTapCentre:1; + uint8_t reserved:1; + uint8_t touchCounter; + uint8_t reserved2; + } touchInfo; + int8_t airWheel; + uint8_t reserved; + uint16_t x; + uint16_t y; + uint16_t z; + float noisePower; + float CICData[4]; + float SDData[4]; + } out; + struct { + uint8_t header[3]; + + uint8_t valid; + uint8_t hwRev[2]; + uint8_t parameterStartAddr; + uint8_t loaderVersion[2]; + uint8_t loaderPlatform; + uint8_t fwStartAddr; + char fwVersion[120]; + } fw; + struct{ + uint8_t id; + uint8_t size; + uint16_t error; + uint32_t reserved; + uint32_t reserved1; + } status; +} MGC_data; +#pragma pack() + +char MGC3130_currentGesture[12]; + +int8_t MGC3130_delta, MGC3130_lastrotation = 0; +int16_t MGC3130_rotValue, MGC3130_lastSentRotValue = 0; + +uint16_t MGC3130_lastSentX, MGC3130_lastSentY, MGC3130_lastSentZ = 0; + +uint8_t hwRev[2], loaderVersion[2], loaderPlatform = 0; +char MGC3130_firmwareInfo[20]; + +uint8_t MGC3130_touchTimeout = 0; +uint16_t MGC3130_touchCounter = 1; +uint32_t MGC3130_touchTimeStamp = millis(); +bool MGC3130_triggeredByTouch = false; + +uint8_t MGC3130_mode = 1; + + + +uint8_t MGC3130autoCal[] = {0x10, 0x00, 0x00, 0xA2, 0x80, 0x00 , 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}; +uint8_t MGC3130disableAirwheel[] = {0x10, 0x00, 0x00, 0xA2, 0x90, 0x00 , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00}; +uint8_t MGC3130enableAirwheel[] = {0x10, 0x00, 0x00, 0xA2, 0x90, 0x00 , 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00}; + +void MGC3130_handleSensorData(){ + if ( MGC_data.out.outputConfigMask.touchInfo && MGC3130_touchTimeout == 0){ + if (MGC3130_handleTouch()){ + MGC3130_triggeredByTouch = true; + MqttPublishSensor(); + } + } + + if(MGC3130_mode == 1){ + if( MGC_data.out.outputConfigMask.gestureInfo && MGC_data.out.gestureInfo.gestureCode > 0){ + MGC3130_handleGesture(); + MqttPublishSensor(); + } + } + if(MGC3130_mode == 2){ + if(MGC_data.out.outputConfigMask.airWheelInfo && MGC_data.out.systemInfo.airWheelValid){ + MGC3130_handleAirWheel(); + MqttPublishSensor(); + } + } + if(MGC3130_mode == 3){ + if(MGC_data.out.systemInfo.positionValid && (MGC_data.out.z > MGC3130_MIN_ZVALUE)){ + MqttPublishSensor(); + } + } +} + +void MGC3130_sendMessage(uint8_t data[], uint8_t length){ + Wire.beginTransmission(MGC3130_I2C_ADDR); + Wire.write(data,length); + Wire.endTransmission(); + delay(2); + MGC3130_receiveMessage(); +} + + +void MGC3130_handleGesture(){ + + char edge[5]; + if (MGC_data.out.gestureInfo.edgeFlick){ + snprintf_P(edge, sizeof(edge), PSTR("ED_")); + } + else{ + snprintf_P(edge, sizeof(edge), PSTR("")); + } + switch(MGC_data.out.gestureInfo.gestureCode){ + case MGC3130_GESTURE_GARBAGE: + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("NONE")); + break; + case MGC3130_FLICK_WEST_EAST: + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("%sFL_WE"), edge); + break; + case MGC3130_FLICK_EAST_WEST: + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("%sFL_EW"), edge); + break; + case MGC3130_FLICK_SOUTH_NORTH: + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("%sFL_SN"), edge); + break; + case MGC3130_FLICK_NORTH_SOUTH: + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("%sFL_NS"), edge); + break; + case MGC3130_CIRCLE_CLOCKWISE: + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("CW")); + break; + case MGC3130_CIRCLE_CCLOCKWISE: + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("CCW")); + break; + } + +} + +bool MGC3130_handleTouch(){ + + bool success = false; + if (MGC_data.out.touchInfo.doubleTapCentre && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_C")); + MGC3130_touchTimeout = 5; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.doubleTapEast && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_E")); + MGC3130_touchTimeout = 5; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.doubleTapNorth && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_N")); + MGC3130_touchTimeout = 5; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.doubleTapWest && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_W")); + MGC3130_touchTimeout = 5; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.doubleTapSouth && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_S")); + MGC3130_touchTimeout = 5; + success = true; + MGC3130_touchCounter = 1; + } + if (MGC_data.out.touchInfo.tapCentre && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_C")); + MGC3130_touchTimeout = 2; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.tapEast && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_E")); + MGC3130_touchTimeout = 2; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.tapNorth && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_N")); + MGC3130_touchTimeout = 2; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.tapWest && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_W")); + MGC3130_touchTimeout = 2; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.tapSouth && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_S")); + MGC3130_touchTimeout = 2; + success = true; + MGC3130_touchCounter = 1; + } + else if (MGC_data.out.touchInfo.touchCentre && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_C")); + success = true; + MGC3130_touchCounter++; + } + else if (MGC_data.out.touchInfo.touchEast && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_E")); + success = true; + MGC3130_touchCounter++; + } + else if (MGC_data.out.touchInfo.touchNorth && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_N")); + success = true; + MGC3130_touchCounter++; + } + else if (MGC_data.out.touchInfo.touchWest && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_W")); + success = true; + MGC3130_touchCounter++; + } + else if (MGC_data.out.touchInfo.touchSouth && !success){ + + snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_S")); + success = true; + MGC3130_touchCounter++; + } + + return success; +} + +void MGC3130_handleAirWheel(){ + MGC3130_delta = MGC_data.out.airWheel - MGC3130_lastrotation; + MGC3130_lastrotation = MGC_data.out.airWheel; + + MGC3130_rotValue = MGC3130_rotValue + MGC3130_delta; + if(MGC3130_rotValue < MGC3130_MIN_ROTVALUE){ + MGC3130_rotValue = MGC3130_MIN_ROTVALUE; + } + if(MGC3130_rotValue > MGC3130_MAX_ROTVALUE){ + MGC3130_rotValue = MGC3130_MAX_ROTVALUE; + } +} + +void MGC3130_handleSystemStatus(){ + +} + +bool MGC3130_receiveMessage(){ + if(MGC3130_readData()){ + switch(MGC_data.out.id){ + case MGC3130_SENSOR_DATA: + MGC3130_handleSensorData(); + break; + case MGC3130_SYSTEM_STATUS: + MGC3130_handleSystemStatus(); + break; + case MGC3130_FW_VERSION: + hwRev[0] = MGC_data.fw.hwRev[1]; + hwRev[1] = MGC_data.fw.hwRev[0]; + loaderVersion[0] = MGC_data.fw.loaderVersion[0]; + loaderVersion[1] = MGC_data.fw.loaderVersion[1]; + loaderPlatform = MGC_data.fw.loaderPlatform; + snprintf_P(MGC3130_firmwareInfo, sizeof(MGC3130_firmwareInfo), PSTR("FW: %s"), MGC_data.fw.fwVersion); + MGC3130_firmwareInfo[20] = '\0'; + + break; + } + return true; + } + return false; +} + +bool MGC3130_readData() +{ + bool success = false; + if (!digitalRead(MGC3130_xfer)){ + pinMode(MGC3130_xfer, OUTPUT); + digitalWrite(MGC3130_xfer, LOW); + Wire.requestFrom(MGC3130_I2C_ADDR, (uint16_t)32); + + MGC_data.buffer[0] = 4; + unsigned char i = 0; + while(Wire.available() && (i < MGC_data.buffer[0])){ + MGC_data.buffer[i] = Wire.read(); + i++; + } + digitalWrite(MGC3130_xfer, HIGH); + pinMode(MGC3130_xfer, INPUT); + success = true; + } + return success; +} + +void MGC3130_nextMode(){ + if (MGC3130_mode < 3){ + MGC3130_mode++; + } + else{ + MGC3130_mode = 1; + } + switch(MGC3130_mode){ + case 1: + MGC3130_sendMessage(MGC3130disableAirwheel,16); + break; + case 2: + MGC3130_sendMessage(MGC3130enableAirwheel,16); + break; + case 3: + MGC3130_sendMessage(MGC3130disableAirwheel,16); + break; + } +} + +void MGC3130_loop() +{ + if(MGC3130_touchTimeout > 0){ + MGC3130_touchTimeout--; + } + MGC3130_receiveMessage(); +} + +void MGC3130_detect(void) +{ + if (MGC3130_type || I2cActive(MGC3130_I2C_ADDR)) { return; } + + pinMode(MGC3130_xfer, INPUT_PULLUP); + pinMode(MGC3130_reset, OUTPUT); + digitalWrite(MGC3130_reset, LOW); + delay(10); + digitalWrite(MGC3130_reset, HIGH); + delay(50); + + if (MGC3130_receiveMessage()) { + I2cSetActiveFound(MGC3130_I2C_ADDR, MGC3130stype); + MGC3130_currentGesture[0] = '\0'; + MGC3130_type = true; + } +} + + + + + +void MGC3130_show(bool json) +{ + if (!MGC3130_type) { return; } + + char status_chr[2]; + if (MGC_data.out.systemInfo.DSPRunning) { + sprintf (status_chr, "1"); + } + else{ + sprintf (status_chr, "0"); + } + + if (json) { + if (MGC3130_mode == 3 && !MGC3130_triggeredByTouch) { + if (MGC_data.out.systemInfo.positionValid && !(MGC_data.out.x == MGC3130_lastSentX && MGC_data.out.y == MGC3130_lastSentY && MGC_data.out.z == MGC3130_lastSentZ)) { + ResponseAppend_P(PSTR(",\"%s\":{\"X\":%u,\"Y\":%u,\"Z\":%u}"), + MGC3130stype, MGC_data.out.x/64, MGC_data.out.y/64, (MGC_data.out.z-(uint16_t)MGC3130_MIN_ZVALUE)/64); + MGC3130_lastSentX = MGC_data.out.x; + MGC3130_lastSentY = MGC_data.out.y; + MGC3130_lastSentZ = MGC_data.out.z; + } + } + MGC3130_triggeredByTouch = false; + + if (MGC3130_mode == 2) { + if (MGC_data.out.systemInfo.airWheelValid && (MGC3130_rotValue != MGC3130_lastSentRotValue)) { + ResponseAppend_P(PSTR(",\"%s\":{\"AW\":%i}"), MGC3130stype, MGC3130_rotValue); + MGC3130_lastSentRotValue = MGC3130_rotValue; + } + } + + if (MGC3130_currentGesture[0] != '\0') { + if (millis() - MGC3130_touchTimeStamp > 220 ) { + MGC3130_touchCounter = 1; + } + ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%u}"), MGC3130stype, MGC3130_currentGesture, MGC3130_touchCounter); + MGC3130_currentGesture[0] = '\0'; + MGC3130_touchTimeStamp = millis(); + } +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_MGC_3130_SNS, MGC3130stype, status_chr, hwRev[0], hwRev[1], loaderVersion[0], loaderVersion[1], loaderPlatform ); +#endif + } +} +# 557 "S:/Development/Tasmota/tasmota/xsns_36_mgc3130.ino" +bool MGC3130CommandSensor() +{ + bool serviced = true; + + switch (XdrvMailbox.payload) { + case 0: + MGC3130_nextMode(); + break; + case 1: + MGC3130_mode = 1; + MGC3130_sendMessage(MGC3130disableAirwheel,16); + break; + case 2: + MGC3130_mode = 2; + MGC3130_sendMessage(MGC3130enableAirwheel,16); + break; + case 3: + MGC3130_mode = 3; + MGC3130_sendMessage(MGC3130disableAirwheel,16); + break; + } + return serviced; +} + + + + + +bool Xsns36(uint8_t function) +{ + if (!I2cEnabled(XI2C_27)) { return false; } + + bool result = false; + + if ((FUNC_INIT == function) && (pin[GPIO_MGC3130_XFER] < 99) && (pin[GPIO_MGC3130_RESET] < 99)) { + MGC3130_detect(); + } + else if (MGC3130_type) { + switch (function) { + case FUNC_EVERY_50_MSECOND: + MGC3130_loop(); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_36 == XdrvMailbox.index) { + result = MGC3130CommandSensor(); + } + break; + case FUNC_JSON_APPEND: + MGC3130_show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MGC3130_show(0); + break; +#endif + } + } + return result; +} +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_37_rfsensor.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_37_rfsensor.ino" +#ifdef USE_RF_SENSOR +# 33 "S:/Development/Tasmota/tasmota/xsns_37_rfsensor.ino" +#define XSNS_37 37 + + + + +#define RFSNS_VALID_WINDOW 1800 + +#define RFSNS_LOOPS_PER_MILLI 1900 +#define RFSNS_RAW_BUFFER_SIZE 180 +#define RFSNS_MIN_RAW_PULSES 112 + +#define RFSNS_MIN_PULSE_LENGTH 300 +#define RFSNS_RAWSIGNAL_SAMPLE 50 +#define RFSNS_SIGNAL_TIMEOUT 10 +#define RFSNS_SIGNAL_REPEAT_TIME 500 + +typedef struct RawSignalStruct +{ + int Number; + uint8_t Repeats; + uint8_t Multiply; + unsigned long Time; + uint8_t Pulses[RFSNS_RAW_BUFFER_SIZE+2]; + +} raw_signal_t; + +raw_signal_t *rfsns_raw_signal = nullptr; +uint8_t rfsns_rf_bit; +uint8_t rfsns_rf_port; +uint8_t rfsns_any_sensor = 0; + + + + + +bool RfSnsFetchSignal(uint8_t DataPin, bool StateSignal) +{ + uint8_t Fbit = digitalPinToBitMask(DataPin); + uint8_t Fport = digitalPinToPort(DataPin); + uint8_t FstateMask = (StateSignal ? Fbit : 0); + + if ((*portInputRegister(Fport) & Fbit) == FstateMask) { + const unsigned long LoopsPerMilli = RFSNS_LOOPS_PER_MILLI; + + + + + + + unsigned long PulseLength = 0; + if (rfsns_raw_signal->Time) { + if (rfsns_raw_signal->Repeats && (rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) { + PulseLength = micros() + RFSNS_SIGNAL_TIMEOUT *1000; + while (((rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) && (PulseLength > micros())) { + if ((*portInputRegister(Fport) & Fbit) == FstateMask) { + PulseLength = micros() + RFSNS_SIGNAL_TIMEOUT *1000; + } + } + while (((rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) && ((*portInputRegister(Fport) & Fbit) != FstateMask)); + } + } + + int RawCodeLength = 1; + bool Ftoggle = false; + unsigned long numloops = 0; + unsigned long maxloops = RFSNS_SIGNAL_TIMEOUT * LoopsPerMilli; + rfsns_raw_signal->Multiply = RFSNS_RAWSIGNAL_SAMPLE; + do { + numloops = 0; + while(((*portInputRegister(Fport) & Fbit) == FstateMask) ^ Ftoggle) { + if (numloops++ == maxloops) { break; } + } + PulseLength = (numloops *1000) / LoopsPerMilli; + if (PulseLength < RFSNS_MIN_PULSE_LENGTH) { break; } + Ftoggle = !Ftoggle; + rfsns_raw_signal->Pulses[RawCodeLength++] = PulseLength / (unsigned long)rfsns_raw_signal->Multiply; + } + while(RawCodeLength < RFSNS_RAW_BUFFER_SIZE && numloops <= maxloops); + + if ((RawCodeLength >= RFSNS_MIN_RAW_PULSES) && (RawCodeLength < RFSNS_RAW_BUFFER_SIZE -1)) { + rfsns_raw_signal->Repeats = 0; + rfsns_raw_signal->Number = RawCodeLength -1; + rfsns_raw_signal->Pulses[rfsns_raw_signal->Number] = 0; + rfsns_raw_signal->Time = millis(); + return true; + } + else + rfsns_raw_signal->Number = 0; + } + + return false; +} + +#ifdef USE_THEO_V2 +# 149 "S:/Development/Tasmota/tasmota/xsns_37_rfsensor.ino" +#define RFSNS_THEOV2_MAX_CHANNEL 2 + +#define RFSNS_THEOV2_PULSECOUNT 114 +#define RFSNS_THEOV2_RF_PULSE_MID 1000 + +typedef struct { + uint32_t time; + int16_t temp; + uint16_t lux; + uint8_t volt; +} theo_v2_t1_t; + +typedef struct { + uint32_t time; + int16_t temp; + uint16_t hum; + uint8_t volt; +} theo_v2_t2_t; + +theo_v2_t1_t *rfsns_theo_v2_t1 = nullptr; +theo_v2_t2_t *rfsns_theo_v2_t2 = nullptr; + +void RfSnsInitTheoV2(void) +{ + rfsns_theo_v2_t1 = (theo_v2_t1_t*)malloc(RFSNS_THEOV2_MAX_CHANNEL * sizeof(theo_v2_t1_t)); + rfsns_theo_v2_t2 = (theo_v2_t2_t*)malloc(RFSNS_THEOV2_MAX_CHANNEL * sizeof(theo_v2_t2_t)); + rfsns_any_sensor++; +} + +void RfSnsAnalyzeTheov2(void) +{ + if (rfsns_raw_signal->Number != RFSNS_THEOV2_PULSECOUNT) { return; } + + uint8_t Checksum; + uint8_t Channel; + uint8_t Type; + uint8_t Voltage; + int Payload1; + int Payload2; + + uint8_t b, bytes, bits, id; + + uint8_t idx = 3; + uint8_t chksum = 0; + for (bytes = 0; bytes < 7; bytes++) { + b = 0; + for (bits = 0; bits <= 7; bits++) + { + if ((rfsns_raw_signal->Pulses[idx] * rfsns_raw_signal->Multiply) > RFSNS_THEOV2_RF_PULSE_MID) { + b |= 1 << bits; + } + idx += 2; + } + if (bytes > 0) { chksum += b; } + + switch (bytes) { + case 0: + Checksum = b; + break; + case 1: + id = b; + Channel = b & 0x7; + Type = (b >> 3) & 0x1f; + break; + case 2: + Voltage = b; + break; + case 3: + Payload1 = b; + break; + case 4: + Payload1 = (b << 8) | Payload1; + break; + case 5: + Payload2 = b; + break; + case 6: + Payload2 = (b << 8) | Payload2; + break; + } + } + + if (Checksum != chksum) { return; } + if ((Channel == 0) || (Channel > RFSNS_THEOV2_MAX_CHANNEL)) { return; } + Channel--; + + rfsns_raw_signal->Repeats = 1; + + int Payload3 = Voltage & 0x3f; + + switch (Type) { + case 1: + rfsns_theo_v2_t1[Channel].time = LocalTime(); + rfsns_theo_v2_t1[Channel].volt = Payload3; + rfsns_theo_v2_t1[Channel].temp = Payload1; + rfsns_theo_v2_t1[Channel].lux = Payload2; + break; + case 2: + rfsns_theo_v2_t2[Channel].time = LocalTime(); + rfsns_theo_v2_t2[Channel].volt = Payload3; + rfsns_theo_v2_t2[Channel].temp = Payload1; + rfsns_theo_v2_t2[Channel].hum = Payload2; + break; + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RFS: TheoV2, ChkCalc %d, Chksum %d, id %d, Type %d, Ch %d, Volt %d, BattLo %d, Pld1 %d, Pld2 %d"), + chksum, Checksum, id, Type, Channel +1, Payload3, (Voltage & 0x80) >> 7, Payload1, Payload2); +} + +void RfSnsTheoV2Show(bool json) +{ + bool sensor_once = false; + + for (uint32_t i = 0; i < RFSNS_THEOV2_MAX_CHANNEL; i++) { + if (rfsns_theo_v2_t1[i].time) { + char sensor[10]; + snprintf_P(sensor, sizeof(sensor), PSTR("TV2T1C%d"), i +1); + char voltage[33]; + dtostrfd((float)rfsns_theo_v2_t1[i].volt / 10, 1, voltage); + + if (rfsns_theo_v2_t1[i].time < LocalTime() - RFSNS_VALID_WINDOW) { + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_RFRECEIVED "\":\"%s\",\"" D_JSON_VOLTAGE "\":%s}"), + sensor, GetDT(rfsns_theo_v2_t1[i].time).c_str(), voltage); + } + } else { + char temperature[33]; + dtostrfd(ConvertTemp((float)rfsns_theo_v2_t1[i].temp / 100), Settings.flag2.temperature_resolution, temperature); + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_VOLTAGE "\":%s}"), + sensor, temperature, rfsns_theo_v2_t1[i].lux, voltage); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && !sensor_once) { + DomoticzSensor(DZ_TEMP, temperature); + DomoticzSensor(DZ_ILLUMINANCE, rfsns_theo_v2_t1[i].lux); + sensor_once = true; + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, sensor, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, sensor, rfsns_theo_v2_t1[i].lux); +#endif + } + } + } + } + + sensor_once = false; + for (uint32_t i = 0; i < RFSNS_THEOV2_MAX_CHANNEL; i++) { + if (rfsns_theo_v2_t2[i].time) { + char sensor[10]; + snprintf_P(sensor, sizeof(sensor), PSTR("TV2T2C%d"), i +1); + char voltage[33]; + dtostrfd((float)rfsns_theo_v2_t2[i].volt / 10, 1, voltage); + + if (rfsns_theo_v2_t2[i].time < LocalTime() - RFSNS_VALID_WINDOW) { + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_RFRECEIVED" \":\"%s\",\"" D_JSON_VOLTAGE "\":%s}"), + sensor, GetDT(rfsns_theo_v2_t2[i].time).c_str(), voltage); + } + } else { + float temp = ConvertTemp((float)rfsns_theo_v2_t2[i].temp / 100); + float humi = ConvertHumidity((float)rfsns_theo_v2_t2[i].hum / 100); + char temperature[33]; + dtostrfd(temp, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(humi, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"" D_JSON_VOLTAGE "\":%s}"), + sensor, temperature, humidity, voltage); + if ((0 == tele_period) && !sensor_once) { +#ifdef USE_DOMOTICZ + DomoticzTempHumSensor(temperature, humidity); +#endif +#ifdef USE_KNX + KnxSensor(KNX_TEMPERATURE, temp); + KnxSensor(KNX_HUMIDITY, humi); +#endif + sensor_once = true; + } +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, sensor, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, sensor, humidity); +#endif + } + } + } + } +} + +#endif + +#ifdef USE_ALECTO_V2 +# 392 "S:/Development/Tasmota/tasmota/xsns_37_rfsensor.ino" +#define RFSNS_DKW2012_PULSECOUNT 176 +#define RFSNS_ACH2010_MIN_PULSECOUNT 160 +#define RFSNS_ACH2010_MAX_PULSECOUNT 160 + +#define D_ALECTOV2 "AlectoV2" + +const char kAlectoV2Directions[] PROGMEM = D_TX20_NORTH "|" + D_TX20_NORTH D_TX20_NORTH D_TX20_EAST "|" + D_TX20_NORTH D_TX20_EAST "|" + D_TX20_EAST D_TX20_NORTH D_TX20_EAST "|" + D_TX20_EAST "|" + D_TX20_EAST D_TX20_SOUTH D_TX20_EAST "|" + D_TX20_SOUTH D_TX20_EAST "|" + D_TX20_SOUTH D_TX20_SOUTH D_TX20_EAST "|" + D_TX20_SOUTH "|" + D_TX20_SOUTH D_TX20_SOUTH D_TX20_WEST "|" + D_TX20_SOUTH D_TX20_WEST "|" + D_TX20_WEST D_TX20_SOUTH D_TX20_WEST "|" + D_TX20_WEST "|" + D_TX20_WEST D_TX20_NORTH D_TX20_WEST "|" + D_TX20_NORTH D_TX20_WEST "|" + D_TX20_NORTH D_TX20_NORTH D_TX20_WEST; + +typedef struct { + uint32_t time; + float temp; + float rain; + float wind; + float gust; + uint8_t type; + uint8_t humi; + uint8_t wdir; +} alecto_v2_t; + +alecto_v2_t *rfsns_alecto_v2 = nullptr; +uint16_t rfsns_alecto_rain_base = 0; + +void RfSnsInitAlectoV2(void) +{ + rfsns_alecto_v2 = (alecto_v2_t*)malloc(sizeof(alecto_v2_t)); + rfsns_any_sensor++; +} + +void RfSnsAnalyzeAlectov2() +{ + if (!(((rfsns_raw_signal->Number >= RFSNS_ACH2010_MIN_PULSECOUNT) && + (rfsns_raw_signal->Number <= RFSNS_ACH2010_MAX_PULSECOUNT)) || (rfsns_raw_signal->Number == RFSNS_DKW2012_PULSECOUNT))) { return; } + + uint8_t c = 0; + uint8_t rfbit; + uint8_t data[9] = { 0 }; + uint8_t msgtype = 0; + uint8_t rc = 0; + int temp; + uint8_t checksum = 0; + uint8_t checksumcalc = 0; + uint8_t maxidx = 8; + unsigned long atime; + float factor; + char buf1[16]; + + if (rfsns_raw_signal->Number > RFSNS_ACH2010_MAX_PULSECOUNT) { maxidx = 9; } + + uint8_t idx = maxidx; + for (uint32_t x = rfsns_raw_signal->Number; x > 0; x = x-2) { + if (rfsns_raw_signal->Pulses[x-1] * rfsns_raw_signal->Multiply < 0x300) { + rfbit = 0x80; + } else { + rfbit = 0; + } + data[idx] = (data[idx] >> 1) | rfbit; + c++; + if (c == 8) { + if (idx == 0) { break; } + c = 0; + idx--; + } + } + + checksum = data[maxidx]; + checksumcalc = RfSnsAlectoCRC8(data, maxidx); + + msgtype = (data[0] >> 4) & 0xf; + rc = (data[0] << 4) | (data[1] >> 4); + + if (checksum != checksumcalc) { return; } + if ((msgtype != 10) && (msgtype != 5)) { return; } + + rfsns_raw_signal->Repeats = 1; + + + + + + factor = 1.22; + + + + + + rfsns_alecto_v2->time = LocalTime(); + rfsns_alecto_v2->type = (RFSNS_DKW2012_PULSECOUNT == rfsns_raw_signal->Number); + rfsns_alecto_v2->temp = (float)(((data[1] & 0x3) * 256 + data[2]) - 400) / 10; + rfsns_alecto_v2->humi = data[3]; + uint16_t rain = (data[6] * 256) + data[7]; + + if (rain < rfsns_alecto_rain_base) { rfsns_alecto_rain_base = rain; } + if (rfsns_alecto_rain_base > 0) { + rfsns_alecto_v2->rain += ((float)rain - rfsns_alecto_rain_base) * 0.30; + } + rfsns_alecto_rain_base = rain; + rfsns_alecto_v2->wind = (float)data[4] * factor; + rfsns_alecto_v2->gust = (float)data[5] * factor; + if (rfsns_alecto_v2->type) { + rfsns_alecto_v2->wdir = data[8] & 0xf; + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RFS: " D_ALECTOV2 ", ChkCalc %d, Chksum %d, rc %d, Temp %d, Hum %d, Rain %d, Wind %d, Gust %d, Dir %d, Factor %s"), + checksumcalc, checksum, rc, ((data[1] & 0x3) * 256 + data[2]) - 400, data[3], (data[6] * 256) + data[7], data[4], data[5], data[8] & 0xf, dtostrfd(factor, 3, buf1)); +} + +void RfSnsAlectoResetRain(void) +{ + if ((RtcTime.hour == 0) && (RtcTime.minute == 0) && (RtcTime.second == 5)) { + rfsns_alecto_v2->rain = 0; + } +} + + + + + + + +uint8_t RfSnsAlectoCRC8(uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + while (len--) { + uint8_t inbyte = *addr++; + for (uint32_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x80; + crc <<= 1; + if (mix) { crc ^= 0x31; } + inbyte <<= 1; + } + } + return crc; +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_ALECTOV2[] PROGMEM = + "{s}" D_ALECTOV2 " " D_RAIN "{m}%s " D_UNIT_MILLIMETER "{e}" + "{s}" D_ALECTOV2 " " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}" + "{s}" D_ALECTOV2 " " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"; +const char HTTP_SNS_ALECTOV2_WDIR[] PROGMEM = + "{s}" D_ALECTOV2 " " D_TX20_WIND_DIRECTION "{m}%s{e}"; +#endif + +void RfSnsAlectoV2Show(bool json) +{ + if (rfsns_alecto_v2->time) { + if (rfsns_alecto_v2->time < LocalTime() - RFSNS_VALID_WINDOW) { + if (json) { + ResponseAppend_P(PSTR(",\"" D_ALECTOV2 "\":{\"" D_JSON_RFRECEIVED "\":\"%s\"}"), GetDT(rfsns_alecto_v2->time).c_str()); + } + } else { + float temp = ConvertTemp(rfsns_alecto_v2->temp); + char temperature[33]; + dtostrfd(temp, Settings.flag2.temperature_resolution, temperature); + float humi = ConvertHumidity((float)rfsns_alecto_v2->humi); + char humidity[33]; + dtostrfd(humi, Settings.flag2.humidity_resolution, humidity); + char rain[33]; + dtostrfd(rfsns_alecto_v2->rain, 2, rain); + char wind[33]; + dtostrfd(rfsns_alecto_v2->wind, 2, wind); + char gust[33]; + dtostrfd(rfsns_alecto_v2->gust, 2, gust); + char wdir[4]; + char direction[20]; + if (rfsns_alecto_v2->type) { + GetTextIndexed(wdir, sizeof(wdir), rfsns_alecto_v2->wdir, kAlectoV2Directions); + snprintf_P(direction, sizeof(direction), PSTR(",\"Direction\":\"%s\""), wdir); + } + + if (json) { + ResponseAppend_P(PSTR(",\"" D_ALECTOV2 "\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"Rain\":%s,\"Wind\":%s,\"Gust\":%s%s}"), + temperature, humidity, rain, wind, gust, (rfsns_alecto_v2->type) ? direction : ""); + if (0 == tele_period) { +#ifdef USE_DOMOTICZ + + + + +#endif + } +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, D_ALECTOV2, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, D_ALECTOV2, humidity); + WSContentSend_PD(HTTP_SNS_ALECTOV2, rain, wind, gust); + if (rfsns_alecto_v2->type) { + WSContentSend_PD(HTTP_SNS_ALECTOV2_WDIR, wdir); + } +#endif + } + } + } +} +#endif + +void RfSnsInit(void) +{ + rfsns_raw_signal = (raw_signal_t*)(malloc(sizeof(raw_signal_t))); + if (rfsns_raw_signal) { + memset(rfsns_raw_signal, 0, sizeof(raw_signal_t)); +#ifdef USE_THEO_V2 + RfSnsInitTheoV2(); +#endif +#ifdef USE_ALECTO_V2 + RfSnsInitAlectoV2(); +#endif + if (rfsns_any_sensor) { + rfsns_rf_bit = digitalPinToBitMask(pin[GPIO_RF_SENSOR]); + rfsns_rf_port = digitalPinToPort(pin[GPIO_RF_SENSOR]); + pinMode(pin[GPIO_RF_SENSOR], INPUT); + } else { + free(rfsns_raw_signal); + rfsns_raw_signal = nullptr; + } + } +} + +void RfSnsAnalyzeRawSignal(void) +{ + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RFS: Pulses %d"), (int)rfsns_raw_signal->Number); + +#ifdef USE_THEO_V2 + RfSnsAnalyzeTheov2(); +#endif +#ifdef USE_ALECTO_V2 + RfSnsAnalyzeAlectov2(); +#endif +} + +void RfSnsEverySecond(void) +{ +#ifdef USE_ALECTO_V2 + RfSnsAlectoResetRain(); +#endif +} + +void RfSnsShow(bool json) +{ +#ifdef USE_THEO_V2 + RfSnsTheoV2Show(json); +#endif +#ifdef USE_ALECTO_V2 + RfSnsAlectoV2Show(json); +#endif +} + + + + + +bool Xsns37(uint8_t function) +{ + bool result = false; + + if ((pin[GPIO_RF_SENSOR] < 99) && (FUNC_INIT == function)) { + RfSnsInit(); + } + else if (rfsns_raw_signal) { + switch (function) { + case FUNC_LOOP: + if ((*portInputRegister(rfsns_rf_port) &rfsns_rf_bit) == rfsns_rf_bit) { + if (RfSnsFetchSignal(pin[GPIO_RF_SENSOR], HIGH)) { + RfSnsAnalyzeRawSignal(); + } + } + sleep = 0; + break; + case FUNC_EVERY_SECOND: + RfSnsEverySecond(); + break; + case FUNC_JSON_APPEND: + RfSnsShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + RfSnsShow(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_38_az7798.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_38_az7798.ino" +#ifdef USE_AZ7798 + +#define XSNS_38 38 +# 112 "S:/Development/Tasmota/tasmota/xsns_38_az7798.ino" +#include + +#ifndef CO2_LOW +#define CO2_LOW 800 +#endif +#ifndef CO2_HIGH +#define CO2_HIGH 1200 +#endif + +#define AZ_READ_TIMEOUT 400 + +#define AZ_CLOCK_UPDATE_INTERVAL (24UL * 60 * 60) +#define AZ_EPOCH (946684800UL) + +TasmotaSerial *AzSerial; + +const char ktype[] = "AZ7798"; +uint8_t az_type = 1; +uint16_t az_co2 = 0; +double az_temperature = 0; +double az_humidity = 0; +uint8_t az_received = 0; +uint8_t az_state = 0; +unsigned long az_clock_update = 10; + + + +void AzEverySecond(void) +{ + unsigned long start = millis(); + + az_state++; + if (5 == az_state) { + az_state = 0; + + AzSerial->flush(); + AzSerial->write(":\r", 2); + az_received = 0; + + uint8_t az_response[32]; + uint8_t counter = 0; + uint8_t i, j; + uint8_t response_substr[16]; + + do { + if (AzSerial->available() > 0) { + az_response[counter] = AzSerial->read(); + if(az_response[counter] == 0x0d) { az_received = 1; } + counter++; + } else { + delay(5); + } + } while(((millis() - start) < AZ_READ_TIMEOUT) && (counter < sizeof(az_response)) && !az_received); + + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, az_response, counter); + + if (!az_received) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 comms timeout")); + return; + } + + i = 0; + while((az_response[i] != 'T') && (i < counter)) {i++;} + if(az_response[i] != 'T') { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 failed to find start of response")); + return; + } + i++; + j = 0; + + while((az_response[i] != 'C') && (az_response[i] != 'F') && (i < counter)) { + response_substr[j++] = az_response[i++]; + } + if((az_response[i] != 'C') && (az_response[i] != 'F')){ + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 failed to find end of temperature")); + return; + } + response_substr[j] = 0; + az_temperature = CharToFloat((char*)response_substr); + if(az_response[i] == 'C') { + az_temperature = ConvertTemp((float)az_temperature); + } else { + az_temperature = ConvertTemp((az_temperature - 32) / 1.8); + } + i++; + if(az_response[i] != ':') { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error first delimiter")); + return; + } + i++; + if(az_response[i] != 'C') { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error start of CO2")); + return; + } + i++; + j = 0; + + while((az_response[i] != 'p') && (i < counter)) { + response_substr[j++] = az_response[i++]; + } + if(az_response[i] != 'p') { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 failed to find end of CO2")); + return; + } + response_substr[j] = 0; + az_co2 = atoi((char*)response_substr); + LightSetSignal(CO2_LOW, CO2_HIGH, az_co2); + i += 3; + if(az_response[i] != ':') { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error second delimiter")); + return; + } + i++; + if(az_response[i] != 'H') { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error start of humidity")); + return; + } + i++; + j = 0; + + while((az_response[i] != '%') && (i < counter)) { + response_substr[j++] = az_response[i++]; + } + if(az_response[i] != '%') { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 failed to find end of humidity")); + return; + } + response_substr[j] = 0; + az_humidity = ConvertHumidity(CharToFloat((char*)response_substr)); + } + + + if ((az_clock_update == 0) && (LocalTime() > AZ_EPOCH)) { + char tmpString[16]; + sprintf(tmpString, "C %d\r", (int)(LocalTime() - AZ_EPOCH)); + AzSerial->write(tmpString); + + do { + if (AzSerial->available() > 0) { + if(AzSerial->read() == 0x0d) { break; } + } else { + delay(5); + } + } while(((millis() - start) < AZ_READ_TIMEOUT)); + az_clock_update = AZ_CLOCK_UPDATE_INTERVAL; + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 clock updated")); + } else { + az_clock_update--; + } +} + + + +void AzInit(void) +{ + az_type = 0; + if ((pin[GPIO_AZ_RXD] < 99) && (pin[GPIO_AZ_TXD] < 99)) { + AzSerial = new TasmotaSerial(pin[GPIO_AZ_RXD], pin[GPIO_AZ_TXD], 1); + if (AzSerial->begin(9600)) { + if (AzSerial->hardwareSerial()) { ClaimSerial(); } + az_type = 1; + } + } +} + +void AzShow(bool json) +{ + char temperature[33]; + dtostrfd(az_temperature, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(az_humidity, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s}"), ktype, az_co2, temperature, humidity); +#ifdef USE_DOMOTICZ + if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, az_co2); +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_CO2, ktype, az_co2); + WSContentSend_PD(HTTP_SNS_TEMP, ktype, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, ktype, humidity); +#endif + } +} + + + + + +bool Xsns38(uint8_t function) +{ + bool result = false; + + if(az_type){ + switch (function) { + case FUNC_INIT: + AzInit(); + break; + case FUNC_EVERY_SECOND: + AzEverySecond(); + break; + case FUNC_JSON_APPEND: + AzShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + AzShow(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_39_max31855.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_39_max31855.ino" +#ifdef USE_MAX31855 + +#define XSNS_39 39 + +bool initialized = false; + +struct MAX31855_ResultStruct{ + uint8_t ErrorCode; + float ProbeTemperature; + float ReferenceTemperature; +} MAX31855_Result; + +void MAX31855_Init(void){ + if(initialized) + return; + + + pinMode(pin[GPIO_MAX31855CS], OUTPUT); + pinMode(pin[GPIO_MAX31855CLK], OUTPUT); + pinMode(pin[GPIO_MAX31855DO], INPUT); + + + digitalWrite(pin[GPIO_MAX31855CS], HIGH); + digitalWrite(pin[GPIO_MAX31855CLK], LOW); + + initialized = true; +} + + + + + +void MAX31855_GetResult(void){ + int32_t RawData = MAX31855_ShiftIn(32); + uint8_t probeerror = RawData & 0x7; + + MAX31855_Result.ErrorCode = probeerror; + MAX31855_Result.ReferenceTemperature = MAX31855_GetReferenceTemperature(RawData); + if(probeerror) + MAX31855_Result.ProbeTemperature = NAN; + else + MAX31855_Result.ProbeTemperature = MAX31855_GetProbeTemperature(RawData); +} + + + + + + +float MAX31855_GetProbeTemperature(int32_t RawData){ + if(RawData & 0x80000000) + RawData = (RawData >> 18) | 0xFFFFC000; + else + RawData >>= 18; + + float result = (RawData * 0.25); + + return ConvertTemp(result); +} + + + + + +float MAX31855_GetReferenceTemperature(int32_t RawData){ + if(RawData & 0x8000) + RawData = (RawData >> 4) | 0xFFFFF000; + else + RawData = (RawData >> 4) & 0x00000FFF; + + float result = (RawData * 0.0625); + + return ConvertTemp(result); +} + + + + + +int32_t MAX31855_ShiftIn(uint8_t Length){ + int32_t dataIn = 0; + + digitalWrite(pin[GPIO_MAX31855CS], LOW); + delayMicroseconds(1); + + for (uint32_t i = 0; i < Length; i++) + { + digitalWrite(pin[GPIO_MAX31855CLK], LOW); + delayMicroseconds(1); + dataIn <<= 1; + if(digitalRead(pin[GPIO_MAX31855DO])) + dataIn |= 1; + digitalWrite(pin[GPIO_MAX31855CLK], HIGH); + delayMicroseconds(1); + } + + digitalWrite(pin[GPIO_MAX31855CS], HIGH); + digitalWrite(pin[GPIO_MAX31855CLK], LOW); + return dataIn; +} + +void MAX31855_Show(bool Json){ + char probetemp[33]; + char referencetemp[33]; + dtostrfd(MAX31855_Result.ProbeTemperature, Settings.flag2.temperature_resolution, probetemp); + dtostrfd(MAX31855_Result.ReferenceTemperature, Settings.flag2.temperature_resolution, referencetemp); + + if(Json){ + ResponseAppend_P(PSTR(",\"MAX31855\":{\"" D_JSON_PROBETEMPERATURE "\":%s,\"" D_JSON_REFERENCETEMPERATURE "\":%s,\"" D_JSON_ERROR "\":%d}"), \ + probetemp, referencetemp, MAX31855_Result.ErrorCode); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_TEMP, probetemp); + } +#endif +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, MAX31855_Result.ProbeTemperature); + } +#endif + } else { +#ifdef USE_WEBSERVER + WSContentSend_PD(HTTP_SNS_TEMP, "MAX31855", probetemp, TempUnit()); +#endif + } +} + + + + + +bool Xsns39(uint8_t function) +{ + bool result = false; + if((pin[GPIO_MAX31855CS] < 99) && (pin[GPIO_MAX31855CLK] < 99) && (pin[GPIO_MAX31855DO] < 99)){ + + switch (function) { + case FUNC_INIT: + MAX31855_Init(); + break; + case FUNC_EVERY_SECOND: + MAX31855_GetResult(); + break; + case FUNC_JSON_APPEND: + MAX31855_Show(true); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MAX31855_Show(false); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_40_pn532.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_40_pn532.ino" +#ifdef USE_PN532_HSU + +#define XSNS_40 40 + +#include + +TasmotaSerial *PN532_Serial; + +#define PN532_INVALID_ACK -1 +#define PN532_TIMEOUT -2 +#define PN532_INVALID_FRAME -3 +#define PN532_NO_SPACE -4 + +#define PN532_PREAMBLE 0x00 +#define PN532_STARTCODE1 0x00 +#define PN532_STARTCODE2 0xFF +#define PN532_POSTAMBLE 0x00 + +#define PN532_HOSTTOPN532 0xD4 +#define PN532_PN532TOHOST 0xD5 + +#define PN532_ACK_WAIT_TIME 0x0A + +#define PN532_COMMAND_GETFIRMWAREVERSION 0x02 +#define PN532_COMMAND_SAMCONFIGURATION 0x14 +#define PN532_COMMAND_RFCONFIGURATION 0x32 +#define PN532_COMMAND_INDATAEXCHANGE 0x40 +#define PN532_COMMAND_INLISTPASSIVETARGET 0x4A + +#define PN532_MIFARE_ISO14443A 0x00 +#define MIFARE_CMD_READ 0x30 +#define MIFARE_CMD_AUTH_A 0x60 +#define MIFARE_CMD_AUTH_B 0x61 +#define MIFARE_CMD_WRITE 0xA0 + +uint8_t pn532_model = 0; +uint8_t pn532_command = 0; +uint8_t pn532_scantimer = 0; + +uint8_t pn532_packetbuffer[64]; + +#ifdef USE_PN532_DATA_FUNCTION +uint8_t pn532_function = 0; +uint8_t pn532_newdata[16]; +uint8_t pn532_newdata_len = 0; +#endif + +void PN532_Init(void) +{ + if ((pin[GPIO_PN532_RXD] < 99) && (pin[GPIO_PN532_TXD] < 99)) { + PN532_Serial = new TasmotaSerial(pin[GPIO_PN532_RXD], pin[GPIO_PN532_TXD], 1); + if (PN532_Serial->begin(115200)) { + if (PN532_Serial->hardwareSerial()) { ClaimSerial(); } + PN532_wakeup(); + uint32_t ver = PN532_getFirmwareVersion(); + if (ver) { + PN532_setPassiveActivationRetries(0xFF); + PN532_SAMConfig(); + pn532_model = 1; + AddLog_P2(LOG_LEVEL_INFO,"NFC: PN532 NFC Reader detected (V%u.%u)",(ver>>16) & 0xFF, (ver>>8) & 0xFF); + } + } + } +} + +int8_t PN532_receive(uint8_t *buf, int len, uint16_t timeout) +{ + int read_bytes = 0; + int ret; + unsigned long start_millis; + while (read_bytes < len) { + start_millis = millis(); + do { + ret = PN532_Serial->read(); + if (ret >= 0) { + break; + } + } while((timeout == 0) || ((millis()- start_millis ) < timeout)); + + if (ret < 0) { + if (read_bytes) { + return read_bytes; + } else { + return PN532_TIMEOUT; + } + } + buf[read_bytes] = (uint8_t)ret; + read_bytes++; + } + return read_bytes; +} + +int8_t PN532_readAckFrame(void) +{ + const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; + uint8_t ackBuf[sizeof(PN532_ACK)]; + + if (PN532_receive(ackBuf, sizeof(PN532_ACK), PN532_ACK_WAIT_TIME) <= 0) { + return PN532_TIMEOUT; + } + + if (memcmp(&ackBuf, &PN532_ACK, sizeof(PN532_ACK))) { + return PN532_INVALID_ACK; + } + return 0; +} + +int8_t PN532_writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0) +{ + + PN532_Serial->flush(); + + pn532_command = header[0]; + PN532_Serial->write((uint8_t)PN532_PREAMBLE); + PN532_Serial->write((uint8_t)PN532_STARTCODE1); + PN532_Serial->write(PN532_STARTCODE2); + + uint8_t length = hlen + blen + 1; + PN532_Serial->write(length); + PN532_Serial->write(~length + 1); + + PN532_Serial->write(PN532_HOSTTOPN532); + uint8_t sum = PN532_HOSTTOPN532; + + PN532_Serial->write(header, hlen); + for (uint32_t i = 0; i < hlen; i++) { + sum += header[i]; + } + + PN532_Serial->write(body, blen); + for (uint32_t i = 0; i < blen; i++) { + sum += body[i]; + } + + uint8_t checksum = ~sum + 1; + PN532_Serial->write(checksum); + PN532_Serial->write((uint8_t)PN532_POSTAMBLE); + + return PN532_readAckFrame(); +} + +int16_t PN532_readResponse(uint8_t buf[], uint8_t len, uint16_t timeout = 50) +{ + uint8_t tmp[3]; + + + if (PN532_receive(tmp, 3, timeout)<=0) { + return PN532_TIMEOUT; + } + if (0 != tmp[0] || 0!= tmp[1] || 0xFF != tmp[2]) { + return PN532_INVALID_FRAME; + } + + + uint8_t length[2]; + if (PN532_receive(length, 2, timeout) <= 0) { + return PN532_TIMEOUT; + } + + if (0 != (uint8_t)(length[0] + length[1])) { + return PN532_INVALID_FRAME; + } + length[0] -= 2; + if (length[0] > len) { + return PN532_NO_SPACE; + } + + + uint8_t cmd = pn532_command + 1; + if (PN532_receive(tmp, 2, timeout) <= 0) { + return PN532_TIMEOUT; + } + if (PN532_PN532TOHOST != tmp[0] || cmd != tmp[1]) { + return PN532_INVALID_FRAME; + } + + if (PN532_receive(buf, length[0], timeout) != length[0]) { + return PN532_TIMEOUT; + } + + uint8_t sum = PN532_PN532TOHOST + cmd; + for (uint32_t i=0; i status) { + return 0; + } + + response = pn532_packetbuffer[0]; + response <<= 8; + response |= pn532_packetbuffer[1]; + response <<= 8; + response |= pn532_packetbuffer[2]; + response <<= 8; + response |= pn532_packetbuffer[3]; + + return response; +} + +void PN532_wakeup(void) +{ + uint8_t wakeup[5] = {0x55, 0x55, 0, 0, 0 }; + PN532_Serial->write(wakeup,sizeof(wakeup)); + + + PN532_Serial->flush(); +} + +bool PN532_readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout = 50) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = cardbaudrate; + if (PN532_writeCommand(pn532_packetbuffer, 3)) { + return 0x0; + } + + if (PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout) < 0) { + return 0x0; + } +# 274 "S:/Development/Tasmota/tasmota/xsns_40_pn532.ino" + if (pn532_packetbuffer[0] != 1) { + return 0; + } + + uint16_t sens_res = pn532_packetbuffer[2]; + sens_res <<= 8; + sens_res |= pn532_packetbuffer[3]; + + + *uidLength = pn532_packetbuffer[5]; + + for (uint32_t i = 0; i < pn532_packetbuffer[5]; i++) { + uid[i] = pn532_packetbuffer[6 + i]; + } + + return 1; +} + +bool PN532_setPassiveActivationRetries(uint8_t maxRetries) +{ + pn532_packetbuffer[0] = PN532_COMMAND_RFCONFIGURATION; + pn532_packetbuffer[1] = 5; + pn532_packetbuffer[2] = 0xFF; + pn532_packetbuffer[3] = 0x01; + pn532_packetbuffer[4] = maxRetries; + if (PN532_writeCommand(pn532_packetbuffer, 5)) { + return 0; + } + return (0 < PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +bool PN532_SAMConfig(void) +{ + pn532_packetbuffer[0] = PN532_COMMAND_SAMCONFIGURATION; + pn532_packetbuffer[1] = 0x01; + pn532_packetbuffer[2] = 0x14; + pn532_packetbuffer[3] = 0x00; + if (PN532_writeCommand(pn532_packetbuffer, 4)) { + return false; + } + return (0 < PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +#ifdef USE_PN532_DATA_FUNCTION + +uint8_t mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData) +{ + uint8_t i; + uint8_t _key[6]; + uint8_t _uid[7]; + uint8_t _uidLen; + + + memcpy(&_key, keyData, 6); + memcpy(&_uid, uid, uidLen); + _uidLen = uidLen; + + + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = (keyNumber) ? MIFARE_CMD_AUTH_B : MIFARE_CMD_AUTH_A; + pn532_packetbuffer[3] = blockNumber; + memcpy(&pn532_packetbuffer[4], &_key, 6); + for (i = 0; i < _uidLen; i++) { + pn532_packetbuffer[10 + i] = _uid[i]; + } + + if (PN532_writeCommand(pn532_packetbuffer, 10 + _uidLen)) { return 0; } + + + PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + + + + if (pn532_packetbuffer[0] != 0x00) { + + return 0; + } + + return 1; +} + +uint8_t mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data) +{ + + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = MIFARE_CMD_READ; + pn532_packetbuffer[3] = blockNumber; + + + if (PN532_writeCommand(pn532_packetbuffer, 4)) { + return 0; + } + + + PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + + if (pn532_packetbuffer[0] != 0x00) { + return 0; + } + + + + memcpy (data, &pn532_packetbuffer[1], 16); + + return 1; +} + +uint8_t mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data) +{ + + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = MIFARE_CMD_WRITE; + pn532_packetbuffer[3] = blockNumber; + memcpy(&pn532_packetbuffer[4], data, 16); + + + if (PN532_writeCommand(pn532_packetbuffer, 20)) { + return 0; + } + + + return (0 < PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +#endif + +void PN532_ScanForTag(void) +{ + if (!pn532_model) { return; } + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; + uint8_t uid_len = 0; + uint8_t card_data[16]; + bool erase_success = false; + bool set_success = false; + if (PN532_readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uid_len)) { + char uids[15]; + +#ifdef USE_PN532_DATA_FUNCTION + char card_datas[34]; +#endif + + ToHex_P((unsigned char*)uid, uid_len, uids, sizeof(uids)); + +#ifdef USE_PN532_DATA_FUNCTION + if (uid_len == 4) { + uint8_t keyuniversal[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + if (mifareclassic_AuthenticateBlock (uid, uid_len, 1, 1, keyuniversal)) { + if (mifareclassic_ReadDataBlock(1, card_data)) { +#ifdef USE_PN532_DATA_RAW + memcpy(&card_datas,&card_data,sizeof(card_data)); +#else + for (uint32_t i = 0;i < sizeof(card_data);i++) { + if ((isalpha(card_data[i])) || ((isdigit(card_data[i])))) { + card_datas[i] = char(card_data[i]); + } else { + card_datas[i] = '\0'; + } + } +#endif + } + if (pn532_function == 1) { + for (uint32_t i = 0;i<16;i++) { + card_data[i] = 0x00; + } + if (mifareclassic_WriteDataBlock(1, card_data)) { + erase_success = true; + AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Erase success")); + memcpy(&card_datas,&card_data,sizeof(card_data)); + } + } + if (pn532_function == 2) { +#ifdef USE_PN532_DATA_RAW + memcpy(&card_data,&pn532_newdata,sizeof(card_data)); + if (mifareclassic_WriteDataBlock(1, card_data)) { + set_success = true; + AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Data write successful")); + memcpy(&card_datas,&card_data,sizeof(card_data)); + } +#else + bool IsAlphaNumeric = true; + for (uint32_t i = 0;i < pn532_newdata_len;i++) { + if ((!isalpha(pn532_newdata[i])) && (!isdigit(pn532_newdata[i]))) { + IsAlphaNumeric = false; + } + } + if (IsAlphaNumeric) { + memcpy(&card_data,&pn532_newdata,pn532_newdata_len); + card_data[pn532_newdata_len] = '\0'; + if (mifareclassic_WriteDataBlock(1, card_data)) { + set_success = true; + AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Data write successful")); + memcpy(&card_datas,&card_data,sizeof(card_data)); + } + } else { + AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Data must be alphanumeric")); + } +#endif + } + } else { + sprintf(card_datas,"AUTHFAIL"); + } + } + switch (pn532_function) { + case 0x01: + if (!erase_success) { + AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Erase fail - exiting erase mode")); + } + break; + case 0x02: + if (!set_success) { + AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Write failed - exiting set mode")); + } + default: + break; + } + pn532_function = 0; +#endif + +#ifdef USE_PN532_DATA_FUNCTION + ResponseTime_P(PSTR(",\"PN532\":{\"UID\":\"%s\", \"DATA\":\"%s\"}}"), uids, card_datas); +#else + ResponseTime_P(PSTR(",\"PN532\":{\"UID\":\"%s\"}}"), uids); +#endif + + MqttPublishTeleSensor(); + +#ifdef USE_PN532_CAUSE_EVENTS + + char command[71]; +#ifdef USE_PN532_DATA_FUNCTION + sprintf(command,"backlog event PN532_UID=%s;event PN532_DATA=%s",uids,card_datas); +#else + sprintf(command,"event PN532_UID=%s",uids); +#endif + ExecuteCommand(command, SRC_RULE); +#endif + + pn532_scantimer = 7; + } +} + +#ifdef USE_PN532_DATA_FUNCTION + +bool PN532_Command(void) +{ + bool serviced = true; + uint8_t paramcount = 0; + if (XdrvMailbox.data_len > 0) { + paramcount=1; + } else { + serviced = false; + return serviced; + } + char sub_string[XdrvMailbox.data_len]; + char sub_string_tmp[XdrvMailbox.data_len]; + for (uint32_t ca=0;ca 1) { + if (XdrvMailbox.data[XdrvMailbox.data_len-1] == ',') { + serviced = false; + return serviced; + } + sprintf(sub_string_tmp,subStr(sub_string, XdrvMailbox.data, ",", 2)); + pn532_newdata_len = strlen(sub_string_tmp); + if (pn532_newdata_len > 15) { pn532_newdata_len = 15; } + memcpy(&pn532_newdata,&sub_string_tmp,pn532_newdata_len); + pn532_newdata[pn532_newdata_len] = 0x00; + pn532_function = 2; + AddLog_P2(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Next scanned tag data block 1 will be set to '%s'"), pn532_newdata); + ResponseTime_P(PSTR(",\"PN532\":{\"COMMAND\":\"S\"}}")); + return serviced; + } + } +} + +#endif + +bool Xsns40(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_INIT: + PN532_Init(); + result = true; + break; + case FUNC_EVERY_50_MSECOND: + break; + case FUNC_EVERY_100_MSECOND: + break; + case FUNC_EVERY_250_MSECOND: + if (pn532_scantimer > 0) { + pn532_scantimer--; + } else { + PN532_ScanForTag(); + } + break; + case FUNC_EVERY_SECOND: + break; +#ifdef USE_PN532_DATA_FUNCTION + case FUNC_COMMAND_SENSOR: + if (XSNS_40 == XdrvMailbox.index) { + result = PN532_Command(); + } + break; +#endif + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_41_max44009.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_41_max44009.ino" +#ifdef USE_I2C +#ifdef USE_MAX44009 + + + + + + +#define XSNS_41 41 +#define XI2C_28 28 + +#define MAX44009_ADDR1 0x4A +#define MAX44009_ADDR2 0x4B +#define MAX44009_NO_REGISTERS 8 +#define REG_CONFIG 0x02 +#define REG_LUMINANCE 0x03 +#define REG_LOWER_THRESHOLD 0x06 +#define REG_THRESHOLD_TIMER 0x07 + +#define MAX44009_CONTINUOUS_AUTO_MODE 0x80 + +uint8_t max44009_address; +uint8_t max44009_addresses[] = { MAX44009_ADDR1, MAX44009_ADDR2, 0 }; +uint8_t max44009_found = 0; +uint8_t max44009_valid = 0; +float max44009_illuminance = 0; +char max44009_types[] = "MAX44009"; + +bool Max4409Read_lum(void) +{ + max44009_valid = 0; + uint8_t regdata[2]; + + + if (I2cValidRead16((uint16_t *)®data, max44009_address, REG_LUMINANCE)) { + int exponent = (regdata[0] & 0xF0) >> 4; + int mantissa = ((regdata[0] & 0x0F) << 4) | (regdata[1] & 0x0F); + max44009_illuminance = (float)(((0x00000001 << exponent) * (float)mantissa) * 0.045); + max44009_valid = 1; + return true; + } else { + return false; + } +} + + + +void Max4409Detect(void) +{ + uint8_t buffer1; + uint8_t buffer2; + for (uint32_t i = 0; 0 != max44009_addresses[i]; i++) { + + max44009_address = max44009_addresses[i]; + if (I2cActive(max44009_address)) { continue; } + + if ((I2cValidRead8(&buffer1, max44009_address, REG_LOWER_THRESHOLD)) && + (I2cValidRead8(&buffer2, max44009_address, REG_THRESHOLD_TIMER))) { + + if ((0x00 == buffer1) && + (0xFF == buffer2)) { + + + + Wire.beginTransmission(max44009_address); + + + Wire.write(REG_CONFIG); + Wire.write(MAX44009_CONTINUOUS_AUTO_MODE); + if (0 == Wire.endTransmission()) { + I2cSetActiveFound(max44009_address, max44009_types); + max44009_found = 1; + break; + } + } + } + } +} + +void Max4409EverySecond(void) +{ + Max4409Read_lum(); +} + +void Max4409Show(bool json) +{ + char illum_str[8]; + + if (max44009_valid) { + + + + uint8_t prec = 0; + if (10 > max44009_illuminance ) { + prec = 3; + } else if (100 > max44009_illuminance) { + prec = 2; + } else if (1000 > max44009_illuminance) { + prec = 1; + } + dtostrf(max44009_illuminance, sizeof(illum_str) -1, prec, illum_str); + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ILLUMINANCE "\":%s}"), max44009_types, illum_str); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_ILLUMINANCE, illum_str); + } +#endif +#ifdef USE_WEBSERVER + } else { + + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, max44009_types, (int)max44009_illuminance); +#endif + } + } +} + + + + + +bool Xsns41(uint8_t function) +{ + if (!I2cEnabled(XI2C_28)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Max4409Detect(); + } + else if (max44009_found) { + switch (function) { + case FUNC_EVERY_SECOND: + Max4409EverySecond(); + break; + case FUNC_JSON_APPEND: + Max4409Show(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Max4409Show(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_42_scd30.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_42_scd30.ino" +#ifdef USE_I2C +#ifdef USE_SCD30 + +#define XSNS_42 42 +#define XI2C_29 29 + + + +#define SCD30_ADDRESS 0x61 + +#define SCD30_MAX_MISSED_READS 3 +#define SCD30_STATE_NO_ERROR 0 +#define SCD30_STATE_ERROR_DATA_CRC 1 +#define SCD30_STATE_ERROR_READ_MEAS 2 +#define SCD30_STATE_ERROR_SOFT_RESET 3 +#define SCD30_STATE_ERROR_I2C_RESET 4 +#define SCD30_STATE_ERROR_UNKNOWN 5 + +#include "Arduino.h" +#include + +#define D_CMND_SCD30 "SCD30" + +const char S_JSON_SCD30_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_SCD30 "%s\":%d}"; +const char S_JSON_SCD30_COMMAND_NFW_VALUE[] PROGMEM = "{\"" D_CMND_SCD30 "%s\":%d.%d}"; +const char S_JSON_SCD30_COMMAND[] PROGMEM = "{\"" D_CMND_SCD30 "%s\"}"; +const char kSCD30_Commands[] PROGMEM = "Alt|Auto|Cal|FW|Int|Pres|TOff"; + + + + + +enum SCD30_Commands { + CMND_SCD30_ALTITUDE, + CMND_SCD30_AUTOMODE, + CMND_SCD30_CALIBRATE, + CMND_SCD30_FW, + CMND_SCD30_INTERVAL, + CMND_SCD30_PRESSURE, + CMND_SCD30_TEMPOFFSET +}; + +FrogmoreScd30 scd30; + +bool scd30Found = false; +bool scd30IsDataValid = false; +int scd30ErrorState = SCD30_STATE_NO_ERROR; +uint16_t scd30Interval_sec; +int scd30Loop_count = 0; +int scd30DataNotAvailable_count = 0; +int scd30GoodMeas_count = 0; +int scd30Reset_count = 0; +int scd30CrcError_count = 0; +int scd30Co2Zero_count = 0; +int i2cReset_count = 0; +uint16_t scd30_CO2 = 0; +uint16_t scd30_CO2EAvg = 0; +float scd30_Humid = 0.0; +float scd30_Temp = 0.0; + +void Scd30Detect(void) +{ + if (I2cActive(SCD30_ADDRESS)) { return; } + + scd30.begin(); + + uint8_t major = 0; + uint8_t minor = 0; + if (scd30.getFirmwareVersion(&major, &minor)) { return; } + uint16_t interval_sec; + if (scd30.getMeasurementInterval(&scd30Interval_sec)) { return; } + if (scd30.beginMeasuring()) { return; } + + I2cSetActiveFound(SCD30_ADDRESS, "SCD30"); + scd30Found = true; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SCD: FW v%d.%d"), major, minor); +} + + +void Scd30Update(void) +{ + scd30Loop_count++; + if (scd30Loop_count > (scd30Interval_sec - 1)) { + int error = 0; + switch (scd30ErrorState) { + case SCD30_STATE_NO_ERROR: { + error = scd30.readMeasurement(&scd30_CO2, &scd30_CO2EAvg, &scd30_Temp, &scd30_Humid); + switch (error) { + case ERROR_SCD30_NO_ERROR: + scd30Loop_count = 0; + scd30IsDataValid = true; + scd30GoodMeas_count++; + break; + + case ERROR_SCD30_NO_DATA: + scd30DataNotAvailable_count++; + break; + + case ERROR_SCD30_CRC_ERROR: + scd30ErrorState = SCD30_STATE_ERROR_DATA_CRC; + scd30CrcError_count++; +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: CRC error, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"), + scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); +#endif + break; + + case ERROR_SCD30_CO2_ZERO: + scd30Co2Zero_count++; +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: CO2 zero, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"), + scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); +#endif + break; + + default: { + scd30ErrorState = SCD30_STATE_ERROR_READ_MEAS; +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld"), error, scd30Loop_count); +#endif + return; + } + break; + } + } + break; + + case SCD30_STATE_ERROR_DATA_CRC: { + +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"), + scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: got CRC error, try again, counter: %ld"), scd30Loop_count); +#endif + scd30ErrorState = ERROR_SCD30_NO_ERROR; + } + break; + + case SCD30_STATE_ERROR_READ_MEAS: { + +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"), + scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: not answering, sending soft reset, counter: %ld"), scd30Loop_count); +#endif + scd30Reset_count++; + error = scd30.softReset(); + if (error) { +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: resetting got error: 0x%lX"), error); +#endif + error >>= 8; + if (error == 4) { + scd30ErrorState = SCD30_STATE_ERROR_SOFT_RESET; + } else { + scd30ErrorState = SCD30_STATE_ERROR_UNKNOWN; + } + } else { + scd30ErrorState = ERROR_SCD30_NO_ERROR; + } + } + break; + + case SCD30_STATE_ERROR_SOFT_RESET: { + +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"), + scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); + AddLog_P(LOG_LEVEL_ERROR, PSTR("SCD30: clearing i2c bus")); +#endif + i2cReset_count++; + error = scd30.clearI2CBus(); + if (error) { + scd30ErrorState = SCD30_STATE_ERROR_I2C_RESET; +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error clearing i2c bus: 0x%lX"), error); +#endif + } else { + scd30ErrorState = ERROR_SCD30_NO_ERROR; + } + } + break; + + default: { + +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: unknown error state: 0x%lX"), scd30ErrorState); +#endif + scd30ErrorState = SCD30_STATE_ERROR_SOFT_RESET; + } + } + + if (scd30Loop_count > (SCD30_MAX_MISSED_READS * scd30Interval_sec)) { + scd30IsDataValid = false; + } + } +} + + +int Scd30GetCommand(int command_code, uint16_t *pvalue) +{ + switch (command_code) + { + case CMND_SCD30_ALTITUDE: + return scd30.getAltitudeCompensation(pvalue); + break; + + case CMND_SCD30_AUTOMODE: + return scd30.getCalibrationType(pvalue); + break; + + case CMND_SCD30_CALIBRATE: + return scd30.getForcedRecalibrationFactor(pvalue); + break; + + case CMND_SCD30_INTERVAL: + return scd30.getMeasurementInterval(pvalue); + break; + + case CMND_SCD30_PRESSURE: + return scd30.getAmbientPressure(pvalue); + break; + + case CMND_SCD30_TEMPOFFSET: + return scd30.getTemperatureOffset(pvalue); + break; + + default: + + break; + } +} + +int Scd30SetCommand(int command_code, uint16_t value) +{ + switch (command_code) + { + case CMND_SCD30_ALTITUDE: + return scd30.setAltitudeCompensation(value); + break; + + case CMND_SCD30_AUTOMODE: + return scd30.setCalibrationType(value); + break; + + case CMND_SCD30_CALIBRATE: + return scd30.setForcedRecalibrationFactor(value); + break; + + case CMND_SCD30_INTERVAL: + { + int error = scd30.setMeasurementInterval(value); + if (!error) + { + scd30Interval_sec = value; + } + + return error; + } + break; + + case CMND_SCD30_PRESSURE: + return scd30.setAmbientPressure(value); + break; + + case CMND_SCD30_TEMPOFFSET: + return scd30.setTemperatureOffset(value); + break; + + default: + + break; + } +} + + + + + +bool Scd30CommandSensor() +{ + char command[CMDSZ]; + bool serviced = true; + uint8_t prefix_len = strlen(D_CMND_SCD30); + + if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_SCD30), prefix_len)) { + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + prefix_len, kSCD30_Commands); + + switch (command_code) { + case CMND_SCD30_ALTITUDE: + case CMND_SCD30_AUTOMODE: + case CMND_SCD30_CALIBRATE: + case CMND_SCD30_INTERVAL: + case CMND_SCD30_PRESSURE: + case CMND_SCD30_TEMPOFFSET: + { + uint16_t value = 0; + if (XdrvMailbox.data_len > 0) + { + value = XdrvMailbox.payload; + Scd30SetCommand(command_code, value); + } + else + { + Scd30GetCommand(command_code, &value); + } + + Response_P(S_JSON_SCD30_COMMAND_NVALUE, command, value); + } + break; + + case CMND_SCD30_FW: + { + uint8_t major = 0; + uint8_t minor = 0; + int error; + error = scd30.getFirmwareVersion(&major, &minor); + if (error) + { +#ifdef SCD30_DEBUG + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error getting FW version: 0x%lX"), error); +#endif + serviced = false; + } + else + { + Response_P(S_JSON_SCD30_COMMAND_NFW_VALUE, command, major, minor); + } + } + break; + + default: + + serviced = false; + break; + } + } + return serviced; +} + +void Scd30Show(bool json) +{ + if (scd30IsDataValid) + { + char humidity[10]; + dtostrfd(ConvertHumidity(scd30_Humid), Settings.flag2.humidity_resolution, humidity); + char temperature[10]; + dtostrfd(ConvertTemp(scd30_Temp), Settings.flag2.temperature_resolution, temperature); + + if (json) { + + ResponseAppend_P(PSTR(",\"SCD30\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s}"), + scd30_CO2, scd30_CO2EAvg, temperature, humidity); +#ifdef USE_DOMOTICZ + if (0 == tele_period) + { + DomoticzSensor(DZ_AIRQUALITY, scd30_CO2); + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_CO2EAVG, "SCD30", scd30_CO2EAvg); + WSContentSend_PD(HTTP_SNS_CO2, "SCD30", scd30_CO2); + WSContentSend_PD(HTTP_SNS_TEMP, "SCD30", temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, "SCD30", humidity); +#endif + } + } +} + + + + + +bool Xsns42(byte function) +{ + if (!I2cEnabled(XI2C_29)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Scd30Detect(); + } + else if (scd30Found) { + switch (function) { + case FUNC_EVERY_SECOND: + Scd30Update(); + break; + case FUNC_COMMAND: + result = Scd30CommandSensor(); + break; + case FUNC_JSON_APPEND: + Scd30Show(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Scd30Show(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_43_hre.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_43_hre.ino" +#ifdef USE_HRE +# 49 "S:/Development/Tasmota/tasmota/xsns_43_hre.ino" +#define XSNS_43 43 + +enum hre_states { + hre_idle, + hre_sync, + hre_syncing, + hre_read, + hre_reading, + hre_sleep, + hre_sleeping +}; + +hre_states hre_state = hre_idle; + +float hre_usage = 0; +float hre_rate = 0; +uint32_t hre_usage_time = 0; + +int hre_read_errors = 0; +bool hre_good = false; + + + +int hreReadBit() +{ + digitalWrite(pin[GPIO_HRE_CLOCK], HIGH); + delay(1); + int bit = digitalRead(pin[GPIO_HRE_DATA]); + digitalWrite(pin[GPIO_HRE_CLOCK], LOW); + delay(1); + return bit; +} + + + +char hreReadChar(int &parity_errors) +{ + + hreReadBit(); + + unsigned ch=0; + int sum=0; + for (uint32_t i=0; i<7; i++) + { + int b = hreReadBit(); + ch |= b << i; + sum += b; + } + + + if ( (sum & 0x1) != hreReadBit()) + parity_errors++; + + + hreReadBit(); + + return ch; +} + +void hreInit(void) +{ + hre_read_errors = 0; + hre_good = false; + + pinMode(pin[GPIO_HRE_CLOCK], OUTPUT); + pinMode(pin[GPIO_HRE_DATA], INPUT); + + + + digitalWrite(pin[GPIO_HRE_CLOCK], LOW); + + hre_state = hre_sync; +} + + +void hreEvery50ms(void) +{ + static int sync_counter = 0; + static int sync_run = 0; + + static uint32_t curr_start = 0; + static int read_counter = 0; + static int parity_errors = 0; + static char buff[46]; + + static char ch; + static size_t i; + + switch (hre_state) + { + case hre_sync: + if (uptime < 10) + break; + sync_run = 0; + sync_counter = 0; + hre_state = hre_syncing; + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "hre_state:hre_syncing")); + break; + + case hre_syncing: + + + for (uint32_t i=0; i<20; i++) + { + if (hreReadBit()) + sync_run++; + else + sync_run = 0; + if (sync_run == 62) + { + hre_state = hre_read; + break; + } + sync_counter++; + } + + if (sync_counter > 1000) + { + hre_state = hre_sleep; + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE D_ERROR)); + } + break; + + + case hre_read: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "sync_run:%d, sync_counter:%d"), sync_run, sync_counter); + read_counter = 0; + parity_errors = 0; + curr_start = uptime; + memset(buff, 0, sizeof(buff)); + hre_state = hre_reading; + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "hre_state:hre_reading")); + + + + + + case hre_reading: + + buff[read_counter++] = hreReadChar(parity_errors); + buff[read_counter++] = hreReadChar(parity_errors); + + if (read_counter == 46) + { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "pe:%d, re:%d, buff:%s"), + parity_errors, hre_read_errors, buff); + if (parity_errors == 0) + { + float curr_usage; + curr_usage = 0.01 * atol(buff+24); + if (hre_usage_time) + { + double dt = 1.666e-2 * (curr_start - hre_usage_time); + hre_rate = (curr_usage - hre_usage)/dt; + } + hre_usage = curr_usage; + hre_usage_time = curr_start; + hre_good = true; + + hre_state = hre_sleep; + } + else + { + hre_read_errors++; + hre_state = hre_sleep; + } + } + break; + + case hre_sleep: + hre_usage_time = curr_start; + hre_state = hre_sleeping; + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "hre_state:hre_sleeping")); + + case hre_sleeping: + + + if (uptime - hre_usage_time >= 27) + hre_state = hre_sync; + } +} + +void hreShow(boolean json) +{ + if (!hre_good) + return; + + const char *id = "HRE"; + + char usage[16]; + char rate[16]; + dtostrfd(hre_usage, 2, usage); + dtostrfd(hre_rate, 3, rate); + + if (json) + { + ResponseAppend_P(JSON_SNS_GNGPM, id, usage, rate); +#ifdef USE_WEBSERVER + } + else + { + WSContentSend_PD(HTTP_SNS_GALLONS, id, usage); + WSContentSend_PD(HTTP_SNS_GPM, id, rate); +#endif + } +} + + + + + +bool Xsns43(byte function) +{ + + if (pin[GPIO_HRE_CLOCK] >= 99 || pin[GPIO_HRE_DATA] >= 99) + return false; + + switch (function) + { + case FUNC_INIT: + hreInit(); + break; + case FUNC_EVERY_50_MSECOND: + hreEvery50ms(); + break; + case FUNC_EVERY_SECOND: + break; + case FUNC_JSON_APPEND: + hreShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + hreShow(0); + break; +#endif + } + return false; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_44_sps30.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_44_sps30.ino" +#ifdef USE_I2C +#ifdef USE_SPS30 + +#define XSNS_44 44 +#define XI2C_30 30 + +#define SPS30_ADDR 0x69 + +#include +#include + +uint8_t sps30_ready = 0; +uint8_t sps30_running; + +struct SPS30 { + float PM1_0; + float PM2_5; + float PM4_0; + float PM10; + float NCPM0_5; + float NCPM1_0; + float NCPM2_5; + float NCPM4_0; + float NCPM10; + float TYPSIZ; +} sps30_result; + +#define SPS_CMD_START_MEASUREMENT 0x0010 +#define SPS_CMD_START_MEASUREMENT_ARG 0x0300 +#define SPS_CMD_STOP_MEASUREMENT 0x0104 +#define SPS_CMD_READ_MEASUREMENT 0x0300 +#define SPS_CMD_GET_DATA_READY 0x0202 +#define SPS_CMD_AUTOCLEAN_INTERVAL 0x8004 +#define SPS_CMD_CLEAN 0x5607 +#define SPS_CMD_GET_ACODE 0xd025 +#define SPS_CMD_GET_SERIAL 0xd033 +#define SPS_CMD_RESET 0xd304 +#define SPS_WRITE_DELAY_US 20000 +#define SPS_MAX_SERIAL_LEN 32 + +uint8_t sps30_calc_CRC(uint8_t *data) { + uint8_t crc = 0xFF; + for (uint32_t i = 0; i < 2; i++) { + crc ^= data[i]; + for (uint32_t bit = 8; bit > 0; --bit) { + if(crc & 0x80) { + crc = (crc << 1) ^ 0x31u; + } else { + crc = (crc << 1); + } + } + } + return crc; +} + +void CmdClean(void); + +unsigned char twi_readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop); + +void sps30_get_data(uint16_t cmd, uint8_t *data, uint8_t dlen) { +unsigned char cmdb[2]; +uint8_t tmp[3]; +uint8_t index=0; +memset(data,0,dlen); +uint8_t twi_buff[64]; + + Wire.beginTransmission(SPS30_ADDR); + cmdb[0]=cmd>>8; + cmdb[1]=cmd; + Wire.write(cmdb,2); + Wire.endTransmission(); + + + dlen/=2; + dlen*=3; + + twi_readFrom(SPS30_ADDR,twi_buff,dlen,1); + + uint8_t bind=0; + while (bind>8; + cmdb[1]=cmd; + + if (cmd==SPS_CMD_START_MEASUREMENT) { + cmdb[2]=SPS_CMD_START_MEASUREMENT_ARG>>8; + cmdb[3]=SPS_CMD_START_MEASUREMENT_ARG&0xff; + cmdb[4]=sps30_calc_CRC(&cmdb[2]); + Wire.write(cmdb,5); + } else { + Wire.write(cmdb,2); + } + Wire.endTransmission(); +} + +void SPS30_Detect(void) +{ + if (!I2cSetDevice(SPS30_ADDR)) { return; } + I2cSetActiveFound(SPS30_ADDR, "SPS30"); + + uint8_t dcode[32]; + sps30_get_data(SPS_CMD_GET_SERIAL,dcode,sizeof(dcode)); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("sps30 found with serial: %s"),dcode); + sps30_cmd(SPS_CMD_START_MEASUREMENT); + sps30_running = 1; + sps30_ready = 1; +} + +#define D_UNIT_PM "ug/m3" +#define D_UNIT_NCPM "#/m3" + +#ifdef USE_WEBSERVER +const char HTTP_SNS_SPS30_a[] PROGMEM ="{s}SPS30 " "%s" "{m}%s " D_UNIT_PM "{e}"; +const char HTTP_SNS_SPS30_b[] PROGMEM ="{s}SPS30 " "%s" "{m}%s " D_UNIT_NCPM "{e}"; +const char HTTP_SNS_SPS30_c[] PROGMEM ="{s}SPS30 " "TYPSIZ" "{m}%s " "um" "{e}"; +#endif + +#define PMDP 2 + +#define SPS30_HOURS Settings.sps30_inuse_hours + + + +void SPS30_Every_Second() { + if (!sps30_running) return; + + if (uptime%10==0) { + uint8_t vars[sizeof(float)*10]; + sps30_get_data(SPS_CMD_READ_MEASUREMENT,vars,sizeof(vars)); + float *fp=&sps30_result.PM1_0; + + typedef union { + uint8_t array[4]; + float value; + } ByteToFloat; + + ByteToFloat conv; + + for (uint32_t count=0; count<10; count++) { + for (uint32_t i = 0; i < 4; i++){ + conv.array[3-i] = vars[count*sizeof(float)+i]; + } + *fp++=conv.value; + } + } + + if (uptime%3600==0 && uptime>60) { + + + SPS30_HOURS++; + if (SPS30_HOURS>(7*24)) { + CmdClean(); + SPS30_HOURS=0; + } + } + +} + +void SPS30_Show(bool json) +{ + if (!sps30_running) { return; } + + char str[64]; + if (json) { + dtostrfd(sps30_result.PM1_0,PMDP,str); + ResponseAppend_P(PSTR(",\"SPS30\":{\"" "PM1_0" "\":%s"), str); + dtostrfd(sps30_result.PM2_5,PMDP,str); + ResponseAppend_P(PSTR(",\"" "PM2_5" "\":%s"), str); + dtostrfd(sps30_result.PM4_0,PMDP,str); + ResponseAppend_P(PSTR(",\"" "PM4_0" "\":%s"), str); + dtostrfd(sps30_result.PM10,PMDP,str); + ResponseAppend_P(PSTR(",\"" "PM10" "\":%s"), str); + dtostrfd(sps30_result.NCPM0_5,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM0_5" "\":%s"), str); + dtostrfd(sps30_result.NCPM1_0,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM1_0" "\":%s"), str); + dtostrfd(sps30_result.NCPM2_5,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM2_5" "\":%s"), str); + dtostrfd(sps30_result.NCPM4_0,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM4_0" "\":%s"), str); + dtostrfd(sps30_result.NCPM10,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM10" "\":%s"), str); + dtostrfd(sps30_result.TYPSIZ,PMDP,str); + ResponseAppend_P(PSTR(",\"" "TYPSIZ" "\":%s}"), str); + +#ifdef USE_WEBSERVER + } else { + dtostrfd(sps30_result.PM1_0,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 1.0",str); + dtostrfd(sps30_result.PM2_5,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 2.5",str); + dtostrfd(sps30_result.PM4_0,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 4.0",str); + dtostrfd(sps30_result.PM10,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 10",str); + dtostrfd(sps30_result.NCPM0_5,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 0.5",str); + dtostrfd(sps30_result.NCPM1_0,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 1.0",str); + dtostrfd(sps30_result.NCPM2_5,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 2.5",str); + dtostrfd(sps30_result.NCPM4_0,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 4.0",str); + dtostrfd(sps30_result.NCPM10,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 10",str); + dtostrfd(sps30_result.TYPSIZ,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_c,str); +#endif + } +} + +void CmdClean(void) +{ + sps30_cmd(SPS_CMD_CLEAN); + ResponseTime_P(PSTR(",\"SPS30\":{\"CFAN\":\"true\"}}")); + MqttPublishTeleSensor(); +} + +bool SPS30_cmd(void) +{ + bool serviced = true; + if (XdrvMailbox.data_len > 0) { + char *cp=XdrvMailbox.data; + if (*cp=='c') { + + CmdClean(); + } else if (*cp=='0' || *cp=='1') { + sps30_running=*cp&1; + sps30_cmd(sps30_running?SPS_CMD_START_MEASUREMENT:SPS_CMD_STOP_MEASUREMENT); + } else { + serviced=false; + } + } + Response_P(PSTR("{\"SPS30\":\"%s\"}"), sps30_running?"running":"stopped"); + + return serviced; +} + + + + + +bool Xsns44(byte function) +{ + if (!I2cEnabled(XI2C_30)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + SPS30_Detect(); + } + else if (sps30_ready) { + switch (function) { + case FUNC_EVERY_SECOND: + SPS30_Every_Second(); + break; + case FUNC_JSON_APPEND: + SPS30_Show(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + SPS30_Show(0); + break; + #endif + case FUNC_COMMAND_SENSOR: + if (XSNS_44 == XdrvMailbox.index) { + result = SPS30_cmd(); + } + break; + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_45_vl53l0x.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_45_vl53l0x.ino" +#ifdef USE_I2C +#ifdef USE_VL53L0X + +#define XSNS_45 45 +#define XI2C_31 31 + +#include +#include "VL53L0X.h" +VL53L0X sensor; + +uint8_t vl53l0x_ready = 0; +uint16_t vl53l0x_distance; +uint16_t Vl53l0_buffer[5]; +uint8_t Vl53l0_index; + + + +void Vl53l0Detect(void) +{ + if (!I2cSetDevice(0x29)) { return; } + + if (!sensor.init()) { return; } + + I2cSetActiveFound(sensor.getAddress(), "VL53L0X"); + + sensor.setTimeout(500); + + + + + + sensor.startContinuous(); + vl53l0x_ready = 1; + + Vl53l0_index=0; +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_VL53L0X[] PROGMEM = + "{s}VL53L0X " D_DISTANCE "{m}%d" D_UNIT_MILLIMETER "{e}"; +#endif + +#define USE_VL_MEDIAN + +void Vl53l0Every_250MSecond(void) +{ + uint16_t tbuff[5],tmp; + uint8_t flag; + + + uint16_t dist = sensor.readRangeContinuousMillimeters(); + if (dist==0 || dist>2000) { + dist=9999; + } + +#ifdef USE_VL_MEDIAN + + Vl53l0_buffer[Vl53l0_index]=dist; + Vl53l0_index++; + if (Vl53l0_index>=5) Vl53l0_index=0; + + + memmove(tbuff,Vl53l0_buffer,sizeof(tbuff)); + for (byte ocnt=0; ocnt<5; ocnt++) { + flag=0; + for (byte count=0; count<4; count++) { + if (tbuff[count]>tbuff[count+1]) { + tmp=tbuff[count]; + tbuff[count]=tbuff[count+1]; + tbuff[count+1]=tmp; + flag=1; + } + } + if (!flag) break; + } + vl53l0x_distance=tbuff[2]; +#else + vl53l0x_distance=dist; +#endif +} + +void Vl53l0Show(boolean json) +{ + if (json) { + ResponseAppend_P(PSTR(",\"VL53L0X\":{\"" D_JSON_DISTANCE "\":%d}"), vl53l0x_distance); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_VL53L0X, vl53l0x_distance); +#endif + } +} + + + + + +bool Xsns45(byte function) +{ + if (!I2cEnabled(XI2C_31)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Vl53l0Detect(); + } + else if (vl53l0x_ready) { + switch (function) { + case FUNC_EVERY_250_MSECOND: + Vl53l0Every_250MSecond(); + break; + case FUNC_JSON_APPEND: + Vl53l0Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Vl53l0Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_46_MLX90614.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_46_MLX90614.ino" +#ifdef USE_I2C +#ifdef USE_MLX90614 + +#define XSNS_46 46 +#define XI2C_32 32 + +#define I2_ADR_IRT 0x5a + +#define MLX90614_RAWIR1 0x04 +#define MLX90614_RAWIR2 0x05 +#define MLX90614_TA 0x06 +#define MLX90614_TOBJ1 0x07 +#define MLX90614_TOBJ2 0x08 + +struct { + union { + uint16_t value; + uint32_t i2c_buf; + }; + float obj_temp; + float amb_temp; + bool ready = false; +} mlx90614; + +void MLX90614_Init(void) +{ + if (!I2cSetDevice(I2_ADR_IRT)) { return; } + I2cSetActiveFound(I2_ADR_IRT, "MLX90614"); + mlx90614.ready = true; +} + +void MLX90614_Every_Second(void) +{ + mlx90614.i2c_buf = I2cRead24(I2_ADR_IRT, MLX90614_TOBJ1); + if (mlx90614.value & 0x8000) { + mlx90614.obj_temp = -999; + } else { + mlx90614.obj_temp = ((float)mlx90614.value * 0.02) - 273.15; + } + mlx90614.i2c_buf = I2cRead24(I2_ADR_IRT,MLX90614_TA); + if (mlx90614.value & 0x8000) { + mlx90614.amb_temp = -999; + } else { + mlx90614.amb_temp = ((float)mlx90614.value * 0.02) - 273.15; + } +} + +#ifdef USE_WEBSERVER + const char HTTP_IRTMP[] PROGMEM = + "{s}MXL90614 " "OBJ-" D_TEMPERATURE "{m}%s C" "{e}" + "{s}MXL90614 " "AMB-" D_TEMPERATURE "{m}%s C" "{e}"; +#endif + +void MLX90614_Show(uint8_t json) +{ + char obj_tstr[16]; + dtostrfd(mlx90614.obj_temp, Settings.flag2.temperature_resolution, obj_tstr); + char amb_tstr[16]; + dtostrfd(mlx90614.amb_temp, Settings.flag2.temperature_resolution, amb_tstr); + + if (json) { + ResponseAppend_P(PSTR(",\"MLX90614\":{\"OBJTMP\":%s,\"AMBTMP\":%s}"), obj_tstr, amb_tstr); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_IRTMP, obj_tstr, amb_tstr); +#endif + } +} + + + + + +bool Xsns46(byte function) +{ + if (!I2cEnabled(XI2C_32)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + MLX90614_Init(); + } + else if (mlx90614.ready) { + switch (function) { + case FUNC_EVERY_SECOND: + MLX90614_Every_Second(); + break; + case FUNC_JSON_APPEND: + MLX90614_Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MLX90614_Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_47_max31865.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_47_max31865.ino" +#ifdef USE_MAX31865 + +#ifndef USE_SPI +#error "MAX31865 requires USE_SPI enabled" +#endif + +#include "Adafruit_MAX31865.h" + +#define XSNS_47 47 + +#if MAX31865_PTD_WIRES == 4 + #define PTD_WIRES MAX31865_4WIRE +#elif MAX31865_PTD_WIRES == 3 + #define PTD_WIRES MAX31865_3WIRE +#else + #define PTD_WIRES MAX31865_2WIRE +#endif + +int8_t init_status = 0; + +Adafruit_MAX31865 max31865; + +struct MAX31865_Result_Struct { + uint8_t ErrorCode; + uint16_t Rtd; + float PtdResistance; + float PtdTemp; +} MAX31865_Result; + +void MAX31865_Init(void){ + if(init_status) + return; + + max31865.setPins( + pin[GPIO_SSPI_CS], + pin[GPIO_SSPI_MOSI], + pin[GPIO_SSPI_MISO], + pin[GPIO_SSPI_SCLK] + ); + + if(max31865.begin(PTD_WIRES)) + init_status = 1; + else + init_status = -1; +} + + + + + +void MAX31865_GetResult(void){ + uint16_t rtd; + + rtd = max31865.readRTD(); + MAX31865_Result.Rtd = rtd; + MAX31865_Result.PtdResistance = max31865.rtd_to_resistance(rtd, MAX31865_REF_RES); + MAX31865_Result.PtdTemp = max31865.rtd_to_temperature(rtd, MAX31865_PTD_RES, MAX31865_REF_RES) + MAX31865_PTD_BIAS; +} + +void MAX31865_Show(bool Json){ + char temperature[33]; + char resistance[33]; + + dtostrfd(MAX31865_Result.PtdResistance, Settings.flag2.temperature_resolution, resistance); + dtostrfd(MAX31865_Result.PtdTemp, Settings.flag2.temperature_resolution, temperature); + + if(Json){ + ResponseAppend_P(PSTR(",\"MAX31865\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_RESISTANCE "\":%s,\"" D_JSON_ERROR "\":%d}"), \ + temperature, resistance, MAX31865_Result.ErrorCode); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_TEMP, temperature); + } +#endif +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, MAX31865_Result.PtdTemp); + } +#endif + } else { +#ifdef USE_WEBSERVER + WSContentSend_PD(HTTP_SNS_TEMP, "MAX31865", temperature, TempUnit()); +#endif + } +} + + + + + +bool Xsns47(uint8_t function) +{ + bool result = false; + if((pin[GPIO_SSPI_MISO] < 99) && (pin[GPIO_SSPI_MOSI] < 99) && + (pin[GPIO_SSPI_SCLK] < 99) && (pin[GPIO_SSPI_CS] < 99)) { + + switch (function) { + case FUNC_INIT: + MAX31865_Init(); + break; + + case FUNC_EVERY_SECOND: + MAX31865_GetResult(); + break; + + case FUNC_JSON_APPEND: + MAX31865_Show(true); + break; + +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MAX31865_Show(false); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_48_chirp.ino" +# 35 "S:/Development/Tasmota/tasmota/xsns_48_chirp.ino" +#ifdef USE_I2C +#ifdef USE_CHIRP +# 47 "S:/Development/Tasmota/tasmota/xsns_48_chirp.ino" +#define XSNS_48 48 +#define XI2C_33 33 + +#define CHIRP_MAX_SENSOR_COUNT 3 + +#define CHIRP_ADDR_STANDARD 0x20 + + + + + +#define D_CMND_CHIRP "CHIRP" + +const char S_JSON_CHIRP_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_CHIRP "%s\":%d}"; +const char S_JSON_CHIRP_COMMAND[] PROGMEM = "{\"" D_CMND_CHIRP "%s\"}"; +const char kCHIRP_Commands[] PROGMEM = "Select|Set|Scan|Reset|Sleep|Wake"; + +const char kChirpTypes[] PROGMEM = "CHIRP"; + + + + + +enum CHIRP_Commands { + CMND_CHIRP_SELECT, + CMND_CHIRP_SET, + CMND_CHIRP_SCAN, + CMND_CHIRP_RESET, + CMND_CHIRP_SLEEP, + CMND_CHIRP_WAKE }; + + + + + + +#define CHIRP_GET_CAPACITANCE 0x00 +#define CHIRP_SET_ADDRESS 0x01 +#define CHIRP_GET_ADDRESS 0x02 +#define CHIRP_MEASURE_LIGHT 0x03 +#define CHIRP_GET_LIGHT 0x04 +#define CHIRP_GET_TEMPERATURE 0x05 +#define CHIRP_RESET 0x06 +#define CHIRP_GET_VERSION 0x07 +#define CHIRP_SLEEP 0x08 +#define CHIRP_GET_BUSY 0x09 + + + + + +void ChirpWriteI2CRegister(uint8_t addr, uint8_t reg) { + Wire.beginTransmission(addr); + Wire.write(reg); + Wire.endTransmission(); +} + +uint16_t ChirpFinishReadI2CRegister16bit(uint8_t addr) { + Wire.requestFrom(addr,(uint8_t)2); + uint16_t t = Wire.read() << 8; + t = t | Wire.read(); + return t; +} + + + + + +uint8_t chirp_current = 0; +uint8_t chirp_found_sensors = 0; + +char chirp_name[7]; +uint8_t chirp_next_job = 0; +uint32_t chirp_timeout_count = 0; + +#pragma pack(1) +struct ChirpSensor_t{ + uint16_t moisture = 0; + uint16_t light = 0; + int16_t temperature = 0; + uint8_t version = 0; + uint8_t address:7; + uint8_t explicitSleep:1; +}; +#pragma pack() + +ChirpSensor_t chirp_sensor[CHIRP_MAX_SENSOR_COUNT]; + + + +void ChirpReset(uint8_t addr) { + ChirpWriteI2CRegister(addr, CHIRP_RESET); +} + + + +void ChirpResetAll(void) { + for (uint32_t i = 0; i < chirp_found_sensors; i++) { + if (chirp_sensor[i].version) { + ChirpReset(chirp_sensor[i].address); + } + } +} + + +void ChirpClockSet() { + Wire.setClockStretchLimit(4000); + Wire.setClock(50000); +} + + + +void ChirpSleep(uint8_t addr) { + ChirpWriteI2CRegister(addr, CHIRP_SLEEP); +} +# 185 "S:/Development/Tasmota/tasmota/xsns_48_chirp.ino" +void ChirpSelect(uint8_t sensor) { + if(sensor < chirp_found_sensors) { + chirp_current = sensor; + DEBUG_SENSOR_LOG(PSTR("CHIRP: Sensor %u now active."), chirp_current); + } + if (sensor == 255) { + DEBUG_SENSOR_LOG(PSTR("CHIRP: Sensor %u active at address 0x%x."), chirp_current, chirp_sensor[chirp_current].address); + } +} + + + +uint8_t ChirpReadVersion(uint8_t addr) { + return (I2cRead8(addr, CHIRP_GET_VERSION)); +} + + + +bool ChirpSet(uint8_t addr) { + if(addr < 128){ + if (I2cWrite8(chirp_sensor[chirp_current].address, CHIRP_SET_ADDRESS, addr)){ + if(chirp_sensor[chirp_current].version>0x25 && chirp_sensor[chirp_current].version != 255){ + delay(5); + I2cWrite8(chirp_sensor[chirp_current].address, CHIRP_SET_ADDRESS, addr); + + } + DEBUG_SENSOR_LOG(PSTR("CHIRP: Wrote adress %u "), addr); + ChirpReset(chirp_sensor[chirp_current].address); + chirp_sensor[chirp_current].address = addr; + chirp_timeout_count = 10; + chirp_next_job = 0; + if(chirp_sensor[chirp_current].version == 255){ + AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: wrote new address %u, please power off device"), addr); + chirp_sensor[chirp_current].version == 0; + } + return true; + } + } + AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: address %u incorrect and not used"), addr); + return false; +} + + + +bool ChirpScan() +{ + ChirpClockSet(); + chirp_found_sensors = 0; + for (uint8_t address = 1; address <= 127; address++) { + chirp_sensor[chirp_found_sensors].version = 0; + chirp_sensor[chirp_found_sensors].version = ChirpReadVersion(address); + delay(2); + chirp_sensor[chirp_found_sensors].version = ChirpReadVersion(address); + if (chirp_sensor[chirp_found_sensors].version > 0) { + I2cSetActiveFound(address, "CHIRP"); + if (chirp_found_sensors 0); +} + + + +void ChirpDetect(void) +{ + if (chirp_next_job > 0) { return; } + + DEBUG_SENSOR_LOG(PSTR("CHIRP: scan will start ...")); + if (ChirpScan()) { + uint8_t chirp_model = 0; + GetTextIndexed(chirp_name, sizeof(chirp_name), chirp_model, kChirpTypes); + } +} + + +void ChirpServiceAllSensors(uint8_t job){ + for (uint32_t i = 0; i < chirp_found_sensors; i++) { + if (chirp_sensor[i].version && !chirp_sensor[i].explicitSleep) { + DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare for sensor at address 0x%x"), chirp_sensor[i].address); + switch(job){ + case 0: + ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_CAPACITANCE); + break; + case 1: + chirp_sensor[i].moisture = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address); + break; + case 2: + ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_TEMPERATURE); + break; + case 3: + chirp_sensor[i].temperature = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address); + break; + case 4: + ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_MEASURE_LIGHT); + break; + case 5: + ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_LIGHT); + break; + case 6: + chirp_sensor[i].light = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address); + break; + default: + break; + } + } + } +} + + + +void ChirpEvery100MSecond(void) +{ + + if(chirp_timeout_count == 0) { + switch(chirp_next_job) { + case 0: + DEBUG_SENSOR_LOG(PSTR("CHIRP: reset all")); + ChirpResetAll(); + chirp_timeout_count = 10; + chirp_next_job++; + break; + case 1: + + + chirp_next_job++; + break; + case 2: + DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare moisture read")); + ChirpServiceAllSensors(0); + chirp_timeout_count = 11; + chirp_next_job++; + break; + case 3: + DEBUG_SENSOR_LOG(PSTR("CHIRP: finish moisture read")); + ChirpServiceAllSensors(1); + chirp_next_job++; + break; + case 4: + DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare moisture read - 2nd")); + ChirpServiceAllSensors(0); + chirp_timeout_count = 11; + chirp_next_job++; + break; + case 5: + DEBUG_SENSOR_LOG(PSTR("CHIRP: finish moisture read - 2nd")); + ChirpServiceAllSensors(1); + chirp_next_job++; + break; + case 6: + DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare temperature read")); + ChirpServiceAllSensors(2); + chirp_timeout_count = 11; + chirp_next_job++; + break; + case 7: + DEBUG_SENSOR_LOG(PSTR("CHIRP: finish temperature read")); + ChirpServiceAllSensors(3); + chirp_next_job++; + break; + case 8: + DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare temperature read - 2nd")); + ChirpServiceAllSensors(2); + chirp_timeout_count = 11; + chirp_next_job++; + break; + case 9: + DEBUG_SENSOR_LOG(PSTR("CHIRP: finish temperature read - 2nd")); + ChirpServiceAllSensors(3); + chirp_next_job++; + break; + case 10: + DEBUG_SENSOR_LOG(PSTR("CHIRP: start light measure process")); + ChirpServiceAllSensors(4); + chirp_timeout_count = 90; + chirp_next_job++; + break; + case 11: + DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare light read")); + ChirpServiceAllSensors(5); + chirp_timeout_count = 11; + chirp_next_job++; + break; + case 12: + DEBUG_SENSOR_LOG(PSTR("CHIRP: finish light read")); + ChirpServiceAllSensors(6); + chirp_next_job++; + break; + case 13: + DEBUG_SENSOR_LOG(PSTR("CHIRP: paused, waiting for TELE")); + break; + case 14: + if (Settings.tele_period > 16){ + chirp_timeout_count = (Settings.tele_period - 17) * 10; + DEBUG_SENSOR_LOG(PSTR("CHIRP: timeout 1/10 sec: %u, tele: %u"), chirp_timeout_count, Settings.tele_period); + } + else{ + AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: TELEPERIOD must be > 16 seconds !")); + + } + chirp_next_job = 1; + break; + } + } + else { + chirp_timeout_count--; + } +} + + + + +#ifdef USE_WEBSERVER + + +const char HTTP_SNS_DARKNESS[] PROGMEM = "{s} " D_JSON_DARKNESS "{m}%s %%{e}"; +const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address{m}0x%x{e}" + "{s} FW-version{m}%s {e}"; ; +const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}"; +#endif + + + +void ChirpShow(bool json) +{ + for (uint32_t i = 0; i < chirp_found_sensors; i++) { + if (chirp_sensor[i].version) { + + char str_temperature[33]; + double t_temperature = ((double) chirp_sensor[i].temperature )/10.0; + dtostrfd(t_temperature, Settings.flag2.temperature_resolution, str_temperature); + char str_light[33]; + dtostrfd(chirp_sensor[i].light, 0, str_light); + char str_version[7]; + if(chirp_sensor[i].version == 0xff){ + strncpy_P(str_version, PSTR("Chirp!"), sizeof(str_version)); + } + else{ + sprintf(str_version, "%x", chirp_sensor[i].version); + } + + if (json) { + if(!chirp_sensor[i].explicitSleep) { + ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%d"), chirp_name, i, chirp_sensor[i].moisture); + if(chirp_sensor[i].temperature!=-1){ + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"),str_temperature); + } + ResponseAppend_P(PSTR(",\"" D_JSON_DARKNESS "\":%s}"),str_light); + } + else { + ResponseAppend_P(PSTR(",\"%s%u\":{\"sleeping\"}"),chirp_name, i); + } + #ifdef USE_DOMOTICZ + if (0 == tele_period) { + char str_moisture[33]; + dtostrfd(chirp_sensor[i].moisture, 0, str_moisture); + DomoticzTempHumSensor(str_temperature, str_moisture); + DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light); + } + #endif + #ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_CHIRPVER, i, chirp_sensor[i].address, str_version); + if (chirp_sensor[i].explicitSleep){ + WSContentSend_PD(HTTP_SNS_CHIRPSLEEP); + } + else { + WSContentSend_PD(HTTP_SNS_MOISTURE, "", chirp_sensor[i].moisture); + WSContentSend_PD(HTTP_SNS_DARKNESS, str_light); + if (chirp_sensor[i].temperature!=-1) { + WSContentSend_PD(HTTP_SNS_TEMP, "", str_temperature, TempUnit()); + } + } + + #endif + } + } + } +} + + + + + +bool ChirpCmd(void) { + char command[CMDSZ]; + bool serviced = true; + uint8_t disp_len = strlen(D_CMND_CHIRP); + + if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_CHIRP), disp_len)) { + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kCHIRP_Commands); + + switch (command_code) { + case CMND_CHIRP_SELECT: + case CMND_CHIRP_SET: + if (XdrvMailbox.data_len > 0) { + if (command_code == CMND_CHIRP_SELECT) { ChirpSelect(XdrvMailbox.payload); } + if (command_code == CMND_CHIRP_SET) { ChirpSet((uint8_t)XdrvMailbox.payload); } + Response_P(S_JSON_CHIRP_COMMAND_NVALUE, command, XdrvMailbox.payload); + } + else { + if (command_code == CMND_CHIRP_SELECT) { ChirpSelect(255); } + Response_P(S_JSON_CHIRP_COMMAND, command, XdrvMailbox.payload); + } + break; + case CMND_CHIRP_SCAN: + case CMND_CHIRP_SLEEP: + case CMND_CHIRP_WAKE: + case CMND_CHIRP_RESET: + if (command_code == CMND_CHIRP_SCAN) { chirp_next_job = 0; + ChirpDetect(); } + if (command_code == CMND_CHIRP_SLEEP) { chirp_sensor[chirp_current].explicitSleep = true; + ChirpSleep(chirp_sensor[chirp_current].address); } + if (command_code == CMND_CHIRP_WAKE) { chirp_sensor[chirp_current].explicitSleep = false; + ChirpReadVersion(chirp_sensor[chirp_current].address); } + if (command_code == CMND_CHIRP_RESET) { ChirpReset(chirp_sensor[chirp_current].address); } + Response_P(S_JSON_CHIRP_COMMAND, command, XdrvMailbox.payload); + break; + default: + + serviced = false; + break; + } + } + return serviced; +} + + + + + +bool Xsns48(uint8_t function) +{ + if (!I2cEnabled(XI2C_33)) { return false; } + + bool result = false; + + switch (function) { + case FUNC_EVERY_100_MSECOND: + if(chirp_found_sensors > 0){ + ChirpEvery100MSecond(); + } + break; + case FUNC_COMMAND: + result = ChirpCmd(); + break; + case FUNC_JSON_APPEND: + ChirpShow(1); + chirp_next_job = 14; + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + ChirpShow(0); + break; +#endif + case FUNC_INIT: + ChirpDetect(); + break; + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_50_paj7620.ino" +# 31 "S:/Development/Tasmota/tasmota/xsns_50_paj7620.ino" +#ifdef USE_I2C +#ifdef USE_PAJ7620 + + + + + + +#define XSNS_50 50 +#define XI2C_34 34 + +#define PAJ7620_ADDR 0x73 + +#define PAJ7620_BANK_SEL 0xEF + + + +#define PAJ7620_GET_GESTURE 0x43 +#define PAJ7620_PROXIMITY_AVG_Y 0x6c + +#define PAJ7620_OBJECT_CENTER_X 0xad +#define PAJ7620_OBJECT_CENTER_Y 0xaf + +#define PAJ7620_DOWN 1 +#define PAJ7620_UP 2 +#define PAJ7620_RIGHT 4 +#define PAJ7620_LEFT 8 +#define PAJ7620_NEAR 16 +#define PAJ7620_FAR 32 +#define PAJ7620_CW 64 +#define PAJ7620_CCW 128 + + + + +const uint8_t PAJ7620initRegisterArray[][2] PROGMEM = { + {0xEF,0x00}, + {0x32,0x29}, {0x33,0x01}, {0x34,0x00}, {0x35,0x01}, {0x36,0x00}, {0x37,0x07}, {0x38,0x17}, {0x39,0x06}, + {0x3A,0x12}, {0x3F,0x00}, {0x40,0x02}, {0x41,0xFF}, {0x42,0x01}, {0x46,0x2D}, {0x47,0x0F}, {0x48,0x3C}, + {0x49,0x00}, {0x4A,0x1E}, {0x4B,0x00}, {0x4C,0x20}, {0x4D,0x00}, {0x4E,0x1A}, {0x4F,0x14}, {0x50,0x00}, + {0x51,0x10}, {0x52,0x00}, {0x5C,0x02}, {0x5D,0x00}, {0x5E,0x10}, {0x5F,0x3F}, {0x60,0x27}, {0x61,0x28}, + {0x62,0x00}, {0x63,0x03}, {0x64,0xF7}, {0x65,0x03}, {0x66,0xD9}, {0x67,0x03}, {0x68,0x01}, {0x69,0xC8}, + {0x6A,0x40}, {0x6D,0x04}, {0x6E,0x00}, {0x6F,0x00}, {0x70,0x80}, {0x71,0x00}, {0x72,0x00}, {0x73,0x00}, + {0x74,0xF0}, {0x75,0x00}, {0x80,0x42}, {0x81,0x44}, {0x82,0x04}, {0x83,0x20}, {0x84,0x20}, {0x85,0x00}, + {0x86,0x10}, {0x87,0x00}, {0x88,0x05}, {0x89,0x18}, {0x8A,0x10}, {0x8B,0x01}, {0x8C,0x37}, {0x8D,0x00}, + {0x8E,0xF0}, {0x8F,0x81}, {0x90,0x06}, {0x91,0x06}, {0x92,0x1E}, {0x93,0x0D}, {0x94,0x0A}, {0x95,0x0A}, + {0x96,0x0C}, {0x97,0x05}, {0x98,0x0A}, {0x99,0x41}, {0x9A,0x14}, {0x9B,0x0A}, {0x9C,0x3F}, {0x9D,0x33}, + {0x9E,0xAE}, {0x9F,0xF9}, {0xA0,0x48}, {0xA1,0x13}, {0xA2,0x10}, {0xA3,0x08}, {0xA4,0x30}, {0xA5,0x19}, + {0xA6,0x10}, {0xA7,0x08}, {0xA8,0x24}, {0xA9,0x04}, {0xAA,0x1E}, {0xAB,0x1E}, {0xCC,0x19}, {0xCD,0x0B}, + {0xCE,0x13}, {0xCF,0x64}, {0xD0,0x21}, {0xD1,0x0F}, {0xD2,0x88}, {0xE0,0x01}, {0xE1,0x04}, {0xE2,0x41}, + {0xE3,0xD6}, {0xE4,0x00}, {0xE5,0x0C}, {0xE6,0x0A}, {0xE7,0x00}, {0xE8,0x00}, {0xE9,0x00}, {0xEE,0x07}, + {0xEF,0x01}, + {0x00,0x1E}, {0x01,0x1E}, {0x02,0x0F}, {0x03,0x10}, {0x04,0x02}, {0x05,0x00}, {0x06,0xB0}, {0x07,0x04}, + {0x08,0x0D}, {0x09,0x0E}, {0x0A,0x9C}, {0x0B,0x04}, {0x0C,0x05}, {0x0D,0x0F}, {0x0E,0x02}, {0x0F,0x12}, + {0x10,0x02}, {0x11,0x02}, {0x12,0x00}, {0x13,0x01}, {0x14,0x05}, {0x15,0x07}, {0x16,0x05}, {0x17,0x07}, + {0x18,0x01}, {0x19,0x04}, {0x1A,0x05}, {0x1B,0x0C}, {0x1C,0x2A}, {0x1D,0x01}, {0x1E,0x00}, {0x21,0x00}, + {0x22,0x00}, {0x23,0x00}, {0x25,0x01}, {0x26,0x00}, {0x27,0x39}, {0x28,0x7F}, {0x29,0x08}, {0x30,0x03}, + {0x31,0x00}, {0x32,0x1A}, {0x33,0x1A}, {0x34,0x07}, {0x35,0x07}, {0x36,0x01}, {0x37,0xFF}, {0x38,0x36}, + {0x39,0x07}, {0x3A,0x00}, {0x3E,0xFF}, {0x3F,0x00}, {0x40,0x77}, {0x41,0x40}, {0x42,0x00}, {0x43,0x30}, + {0x44,0xA0}, {0x45,0x5C}, {0x46,0x00}, {0x47,0x00}, {0x48,0x58}, {0x4A,0x1E}, {0x4B,0x1E}, {0x4C,0x00}, + {0x4D,0x00}, {0x4E,0xA0}, {0x4F,0x80}, {0x50,0x00}, {0x51,0x00}, {0x52,0x00}, {0x53,0x00}, {0x54,0x00}, + {0x57,0x80}, {0x59,0x10}, {0x5A,0x08}, {0x5B,0x94}, {0x5C,0xE8}, {0x5D,0x08}, {0x5E,0x3D}, {0x5F,0x99}, + {0x60,0x45}, {0x61,0x40}, {0x63,0x2D}, {0x64,0x02}, {0x65,0x96}, {0x66,0x00}, {0x67,0x97}, {0x68,0x01}, + {0x69,0xCD}, {0x6A,0x01}, {0x6B,0xB0}, {0x6C,0x04}, {0x6D,0x2C}, {0x6E,0x01}, {0x6F,0x32}, {0x71,0x00}, + {0x72,0x01}, {0x73,0x35}, {0x74,0x00}, {0x75,0x33}, {0x76,0x31}, {0x77,0x01}, {0x7C,0x84}, {0x7D,0x03}, + {0x7E,0x01}, + {0xEF,0x00} +}; + + + + + +const char kPaj7620Directions[] PROGMEM = "Down|Up|Right|Left|Near|Far|CW|CCW"; + +const uint8_t PAJ7620_PIN[]= {1,2,3,4}; + + + + + +char PAJ7620_name[] = "PAJ7620"; + +uint32_t PAJ7620_timeout_counter = 10; +uint32_t PAJ7620_next_job = 0; +uint32_t PAJ7620_mode = 1; + +struct { + uint8_t current; + uint8_t last; + uint8_t same; + uint8_t unfinished; +} PAJ7620_gesture; + +bool PAJ7620_finished_gesture = false; +char PAJ7620_currentGestureName[6]; + +struct{ + uint8_t x; + uint8_t y; + uint8_t last_x; + uint8_t last_y; + uint8_t proximity; + uint8_t last_proximity; + uint8_t corner; + struct { + uint8_t step:3; + uint8_t countdown:3; + uint8_t valid:1; + } PIN; +} PAJ7620_state; + + + + + +void PAJ7620SelectBank(uint8_t bank) +{ + I2cWrite(PAJ7620_ADDR, PAJ7620_BANK_SEL, bank &1, 1); +} + + + +void PAJ7620DecodeGesture(void) +{ + uint32_t index = 0; + switch (PAJ7620_gesture.current) { + case PAJ7620_LEFT: + index++; + case PAJ7620_RIGHT: + index++; + case PAJ7620_UP: + index++; + case PAJ7620_DOWN: + if (PAJ7620_gesture.unfinished) { + PAJ7620_finished_gesture = true; + break; + } + PAJ7620_gesture.unfinished = PAJ7620_gesture.current; + PAJ7620_timeout_counter = 5; + break; + case PAJ7620_NEAR: + index = 4; + PAJ7620_finished_gesture = true; + PAJ7620_timeout_counter = 25; + break; + case PAJ7620_FAR: + index = 5; + PAJ7620_finished_gesture = true; + PAJ7620_timeout_counter = 25; + break; + case PAJ7620_CW: + index = 6; + PAJ7620_finished_gesture = true; + break; + case PAJ7620_CCW: + index = 7; + PAJ7620_finished_gesture = true; + break; + default: + index = 8; + if (PAJ7620_gesture.unfinished) { + PAJ7620_finished_gesture = true; + } + break; + } + if (index < 8) { + GetTextIndexed(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), index, kPaj7620Directions); + } + + if (PAJ7620_finished_gesture) { + if (PAJ7620_gesture.unfinished) { + if ((PAJ7620_gesture.current != PAJ7620_NEAR) && (PAJ7620_gesture.current != PAJ7620_FAR)) { + PAJ7620_gesture.current = PAJ7620_gesture.unfinished; + } + } + if (PAJ7620_gesture.current == PAJ7620_gesture.last) { + PAJ7620_gesture.same++; + } else { + PAJ7620_gesture.same = 1; + } + PAJ7620_gesture.last = PAJ7620_gesture.current; + PAJ7620_finished_gesture = false; + PAJ7620_gesture.unfinished = 0; + PAJ7620_timeout_counter += 3; + MqttPublishSensor(); + } +} + + + +void PAJ7620ReadGesture(void) +{ + switch (PAJ7620_mode) { + case 1: + PAJ7620_gesture.current = I2cRead8(PAJ7620_ADDR,PAJ7620_GET_GESTURE); + if ((PAJ7620_gesture.current > 0) || PAJ7620_gesture.unfinished) { + DEBUG_SENSOR_LOG(PSTR("PAJ: gesture: %u"), PAJ7620_gesture.current); + PAJ7620DecodeGesture(); + } + break; + case 2: + PAJ7620_state.proximity = I2cRead8(PAJ7620_ADDR, PAJ7620_PROXIMITY_AVG_Y); + if ((PAJ7620_state.proximity > 0) || (PAJ7620_state.last_proximity > 0)) { + if (PAJ7620_state.proximity != PAJ7620_state.last_proximity) { + PAJ7620_state.last_proximity = PAJ7620_state.proximity; + DEBUG_SENSOR_LOG(PSTR("PAJ: Proximity: %u"), PAJ7620_state.proximity); + MqttPublishSensor(); + } + } + break; + case 3: + case 4: + case 5: + PAJ7620_state.x = I2cRead8(PAJ7620_ADDR, PAJ7620_OBJECT_CENTER_X); + PAJ7620_state.y = I2cRead8(PAJ7620_ADDR, PAJ7620_OBJECT_CENTER_Y); + if ((PAJ7620_state.y > 0) && (PAJ7620_state.x > 0)) { + if ((PAJ7620_state.y != PAJ7620_state.last_y) || (PAJ7620_state.x != PAJ7620_state.last_x)) { + PAJ7620_state.last_y = PAJ7620_state.y; + PAJ7620_state.last_x = PAJ7620_state.x; + DEBUG_SENSOR_LOG(PSTR("PAJ: x: %u y: %u"), PAJ7620_state.x, PAJ7620_state.y); + + PAJ7620_state.corner = 0; + + + + switch (PAJ7620_state.y) { + case 0: case 1: case 2: case 3: case 4: case 5: + PAJ7620_state.corner = 3; + break; + case 9: case 10: case 11: case 12: case 13: case 14: + PAJ7620_state.corner = 1; + break; + } + if (PAJ7620_state.corner != 0) { + switch (PAJ7620_state.x) { + case 0: case 1: case 2: case 3: case 4: case 5: + break; + case 9: case 10: case 11: case 12: case 13: case 14: + PAJ7620_state.corner++; + break; + default: + PAJ7620_state.corner = 0; + break; + } + } + DEBUG_SENSOR_LOG(PSTR("PAJ: corner: %u"), PAJ7620_state.corner); + + if (PAJ7620_state.PIN.countdown == 0) { + PAJ7620_state.PIN.step = 0; + PAJ7620_state.PIN.valid = 0; + } + if (!PAJ7620_state.PIN.step) { + if (PAJ7620_state.corner == PAJ7620_PIN[PAJ7620_state.PIN.step]) { + PAJ7620_state.PIN.step = 1; + PAJ7620_state.PIN.countdown = 7; + } + } else { + if (PAJ7620_state.corner == PAJ7620_PIN[PAJ7620_state.PIN.step]) { + PAJ7620_state.PIN.step += 1; + PAJ7620_state.PIN.countdown = 7; + } else { + PAJ7620_state.PIN.countdown -= 1; + } + } + if (PAJ7620_state.PIN.step == 4) { + PAJ7620_state.PIN.valid = 1; + DEBUG_SENSOR_LOG(PSTR("PAJ: PIN valid!!")); + PAJ7620_state.PIN.countdown = 0; + } + MqttPublishSensor(); + } + } + break; + } +} + + + +void PAJ7620Detect(void) +{ + if (I2cActive(PAJ7620_ADDR)) { return; } + + PAJ7620SelectBank(0); + PAJ7620SelectBank(0); + uint16_t PAJ7620_id = I2cRead16LE(PAJ7620_ADDR,0); + uint8_t PAJ7620_ver = I2cRead8(PAJ7620_ADDR,2); + if (0x7620 == PAJ7620_id) { + I2cSetActiveFound(PAJ7620_ADDR, PAJ7620_name); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PAJ: ID: 0x%x and VER: %u"), PAJ7620_id, PAJ7620_ver); + PAJ7620_next_job = 1; + } + else { + DEBUG_SENSOR_LOG(PSTR("PAJ: sensor not found, false ID 0x%x"), PAJ7620_id); + } +} + + + +void PAJ7620Init(void) +{ + DEBUG_SENSOR_LOG(PSTR("PAJ: init sensor start %u"),millis()); + union{ + uint32_t raw; + uint8_t reg_val[4]; + } buf; + + for (uint32_t i = 0; i < (sizeof(PAJ7620initRegisterArray) / 2); i += 2) + { + buf.raw = pgm_read_dword(PAJ7620initRegisterArray + i); + DEBUG_SENSOR_LOG("PAJ: %x %x %x %x",buf.reg_val[0],buf.reg_val[1],buf.reg_val[2],buf.reg_val[3]); + I2cWrite(PAJ7620_ADDR, buf.reg_val[0], buf.reg_val[1], 1); + I2cWrite(PAJ7620_ADDR, buf.reg_val[2], buf.reg_val[3], 1); + } + DEBUG_SENSOR_LOG(PSTR("PAJ: init sensor done %u"),millis()); + PAJ7620_next_job = 2; +} + + + +void PAJ7620Loop(void) +{ + if (0 == PAJ7620_timeout_counter) { + switch (PAJ7620_next_job) { + case 1: + PAJ7620Init(); + break; + case 2: + if (PAJ7620_mode != 0) { + PAJ7620ReadGesture(); + } + break; + } + } else { + PAJ7620_timeout_counter--; + } +} + + + +void PAJ7620Show(bool json) +{ + if (json) { + if (PAJ7620_currentGestureName[0] != '\0' ) { + ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%u}"), PAJ7620_name, PAJ7620_currentGestureName, PAJ7620_gesture.same); + PAJ7620_currentGestureName[0] = '\0'; + return; + } + switch (PAJ7620_mode) { + case 2: + ResponseAppend_P(PSTR(",\"%s\":{\"Proximity\":%u}"), PAJ7620_name, PAJ7620_state.proximity); + break; + case 3: + if (PAJ7620_state.corner > 0) { + ResponseAppend_P(PSTR(",\"%s\":{\"Corner\":%u}"), PAJ7620_name, PAJ7620_state.corner); + } + break; + case 4: + if (PAJ7620_state.PIN.valid) { + ResponseAppend_P(PSTR(",\"%s\":{\"PIN\":%u}"), PAJ7620_name, 1); + PAJ7620_state.PIN.valid = 0; + } + break; + case 5: + ResponseAppend_P(PSTR(",\"%s\":{\"x\":%u,\"y\":%u}"), PAJ7620_name, PAJ7620_state.x, PAJ7620_state.y); + break; + } + } +} +# 411 "S:/Development/Tasmota/tasmota/xsns_50_paj7620.ino" +bool PAJ7620CommandSensor(void) +{ + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 5)) { + PAJ7620_mode = XdrvMailbox.payload; + } + Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_50, PAJ7620_mode); + + return true; +} + + + + + +bool Xsns50(uint8_t function) +{ + if (!I2cEnabled(XI2C_34)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + PAJ7620Detect(); + } + else if (PAJ7620_next_job) { + switch (function) { + case FUNC_COMMAND_SENSOR: + if (XSNS_50 == XdrvMailbox.index){ + result = PAJ7620CommandSensor(); + } + break; + case FUNC_EVERY_100_MSECOND: + PAJ7620Loop(); + break; + case FUNC_JSON_APPEND: + PAJ7620Show(1); + break; + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_51_rdm6300.ino" +# 21 "S:/Development/Tasmota/tasmota/xsns_51_rdm6300.ino" +#ifdef USE_RDM6300 + +#define XSNS_51 51 + +#define RDM6300_BAUDRATE 9600 + +#include + +#define RDM_TIMEOUT 100 +char rdm_uid_str[10]; + + +#define RDM6300_BLOCK 2*10 + +uint8_t rdm_blcnt; +TasmotaSerial *RDM6300_Serial = nullptr; + +void RDM6300_Init() { + if (pin[GPIO_RDM6300_RX] < 99) { + RDM6300_Serial = new TasmotaSerial(pin[GPIO_RDM6300_RX],-1,1); + if (RDM6300_Serial->begin(RDM6300_BAUDRATE)) { + if (RDM6300_Serial->hardwareSerial()) { + ClaimSerial(); + } + } + } + rdm_blcnt=0; +} + + +void RDM6300_ScanForTag() { + char rdm_buffer[14]; + uint8_t rdm_index; + uint8_t rdm_array[6]; + + if (!RDM6300_Serial) return; + + if (rdm_blcnt>0) { + rdm_blcnt--; + while (RDM6300_Serial->available()) RDM6300_Serial->read(); + return; + } + + if (RDM6300_Serial->available()) { + + char c=RDM6300_Serial->read(); + if (c!=2) return; + + + rdm_index=0; + uint32_t cmillis=millis(); + while (1) { + if (RDM6300_Serial->available()) { + char c=RDM6300_Serial->read(); + if (c==3) { + + break; + } + rdm_buffer[rdm_index++]=c; + if (rdm_index>13) { + + return; + } + } + if ((millis()-cmillis)>RDM_TIMEOUT) { + + return; + } + } + + + rdm_blcnt=RDM6300_BLOCK; + + + rm6300_hstring_to_array(rdm_array,sizeof(rdm_array),rdm_buffer); + uint8_t accu=0; + for (uint8_t count=0;count<5;count++) { + accu^=rdm_array[count]; + } + if (accu!=rdm_array[5]) { + + return; + } + + + memcpy(rdm_uid_str,&rdm_buffer[2],8); + rdm_uid_str[9]=0; + + ResponseTime_P(PSTR(",\"RDM6300\":{\"UID\":\"%s\"}}"), rdm_uid_str); + MqttPublishTeleSensor(); + + + + + + } + + +} + +uint8_t rm6300_hexnibble(char chr) { + uint8_t rVal = 0; + if (isdigit(chr)) { + rVal = chr - '0'; + } else { + if (chr >= 'A' && chr <= 'F') rVal = chr + 10 - 'A'; + if (chr >= 'a' && chr <= 'f') rVal = chr + 10 - 'a'; + } + return rVal; +} + + +void rm6300_hstring_to_array(uint8_t array[], uint8_t len, char buffer[]) +{ + char *cp=buffer; + for (uint8_t i = 0; i < len; i++) { + uint8_t val = rm6300_hexnibble(*cp++) << 4; + array[i]= val | rm6300_hexnibble(*cp++); + } +} + +#ifdef USE_WEBSERVER +const char HTTP_RDM6300[] PROGMEM = + "{s}RDM6300 " "UID" "{m}%s" "{e}"; + +void RDM6300_Show(void) { + if (!RDM6300_Serial) return; + if (!rdm_uid_str[0]) strcpy(rdm_uid_str,"????"); + WSContentSend_PD(HTTP_RDM6300,rdm_uid_str); +} +#endif + + + + + +bool Xsns51(byte function) +{ + bool result = false; + + switch (function) { + case FUNC_INIT: + RDM6300_Init(); + break; + case FUNC_EVERY_100_MSECOND: + RDM6300_ScanForTag(); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + RDM6300_Show(); + break; +#endif + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_52_ibeacon.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_52_ibeacon.ino" +#ifdef USE_IBEACON + + + +#define XSNS_52 52 + +#include + +#define HM17_BAUDRATE 9600 + +#define IBEACON_DEBUG + + +#define HM17_V110 + + + +#define IB_TIMEOUT_INTERVAL 30 + +#define IB_UPDATE_TIME_INTERVAL 10 + +TasmotaSerial *IBEACON_Serial = nullptr; + + +uint8_t hm17_found,hm17_cmd,hm17_flag; + +#ifdef IBEACON_DEBUG +uint8_t hm17_debug=0; +#endif + + + +#define HM17_BSIZ 128 +char hm17_sbuffer[HM17_BSIZ]; +uint8_t hm17_sindex,hm17_result,hm17_scanning,hm17_connecting; +uint32_t hm17_lastms; +char ib_mac[14]; + + +#if 1 +uint8_t ib_upd_interval,ib_tout_interval; +#define IB_UPDATE_TIME ib_upd_interval +#define IB_TIMEOUT_TIME ib_tout_interval +#else +#undef IB_UPDATE_TIME +#undef IB_TIMEOUT_TIME +#define IB_UPDATE_TIME Settings.ib_upd_interval +#define IB_TIMEOUT_TIME Settings.ib_tout_interval +#endif + +enum {HM17_TEST,HM17_ROLE,HM17_IMME,HM17_DISI,HM17_IBEA,HM17_SCAN,HM17_DISC,HM17_RESET,HM17_RENEW,HM17_CON}; +#define HM17_SUCESS 99 + +struct IBEACON { + char FACID[8]; + char UID[32]; + char MAJOR[4]; + char MINOR[4]; + char PWR[2]; + char MAC[12]; + char RSSI[4]; +}; + +#define MAX_IBEACONS 16 + +struct IBEACON_UID { + char MAC[12]; + char RSSI[4]; + uint8_t FLAGS; + uint8_t TIME; +} ibeacons[MAX_IBEACONS]; + + +void IBEACON_Init() { + + hm17_found=0; + + + if ((pin[GPIO_IBEACON_RX] < 99) && (pin[GPIO_IBEACON_TX] < 99)) { + IBEACON_Serial = new TasmotaSerial(pin[GPIO_IBEACON_RX], pin[GPIO_IBEACON_TX],1); + if (IBEACON_Serial->begin(HM17_BAUDRATE)) { + if (IBEACON_Serial->hardwareSerial()) { + ClaimSerial(); + } + hm17_sendcmd(HM17_TEST); + hm17_lastms=millis(); + + IB_UPDATE_TIME=IB_UPDATE_TIME_INTERVAL; + IB_TIMEOUT_TIME=IB_TIMEOUT_INTERVAL; + } + } +} + +void hm17_every_second(void) { + if (!IBEACON_Serial) return; + + if (hm17_found) { + if (IB_UPDATE_TIME && (uptime%IB_UPDATE_TIME==0)) { + if (hm17_cmd!=99) { + if (hm17_flag&2) { + ib_sendbeep(); + } else { + if (!hm17_connecting) { + hm17_sendcmd(HM17_DISI); + } + } + } + } + for (uint32_t cnt=0;cntIB_TIMEOUT_TIME) { + ibeacons[cnt].FLAGS=0; + ibeacon_mqtt(ibeacons[cnt].MAC,"0000"); + } + } + } + } else { + if (uptime%20==0) { + hm17_sendcmd(HM17_TEST); + } + } +} + +void hm17_sbclr(void) { + memset(hm17_sbuffer,0,HM17_BSIZ); + hm17_sindex=0; + IBEACON_Serial->flush(); +} + +void hm17_sendcmd(uint8_t cmd) { + hm17_sbclr(); + hm17_cmd=cmd; +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("hm17cmd %d"),cmd); +#endif + switch (cmd) { + case HM17_TEST: + IBEACON_Serial->write("AT"); + break; + case HM17_ROLE: + IBEACON_Serial->write("AT+ROLE1"); + break; + case HM17_IMME: + IBEACON_Serial->write("AT+IMME1"); + break; + case HM17_DISI: + IBEACON_Serial->write("AT+DISI?"); + hm17_scanning=1; + break; + case HM17_IBEA: + IBEACON_Serial->write("AT+IBEA1"); + break; + case HM17_RESET: + IBEACON_Serial->write("AT+RESET"); + break; + case HM17_RENEW: + IBEACON_Serial->write("AT+RENEW"); + break; + case HM17_SCAN: + IBEACON_Serial->write("AT+SCAN5"); + break; + case HM17_DISC: + IBEACON_Serial->write("AT+DISC?"); + hm17_scanning=1; + break; + case HM17_CON: + IBEACON_Serial->write((const uint8_t*)"AT+CON",6); + IBEACON_Serial->write((const uint8_t*)ib_mac,12); + hm17_connecting=1; + break; + } +} + +uint32_t ibeacon_add(struct IBEACON *ib) { + + if (!strncmp(ib->MAC,"FFFF",4) || strncmp(ib->FACID,"00000000",8)) { + for (uint32_t cnt=0;cntMAC,12)) { + + memcpy(ibeacons[cnt].RSSI,ib->RSSI,4); + ibeacons[cnt].TIME=0; + return 1; + } + } + } + for (uint32_t cnt=0;cntMAC,12); + memcpy(ibeacons[cnt].RSSI,ib->RSSI,4); + ibeacons[cnt].FLAGS=1; + ibeacons[cnt].TIME=0; + return 1; + } + } + } + return 0; +} + +void hm17_decode(void) { + struct IBEACON ib; + switch (hm17_cmd) { + case HM17_TEST: + if (!strncmp(hm17_sbuffer,"OK",2)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("AT OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + hm17_found=1; + } + break; + case HM17_ROLE: + if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("ROLE OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_IMME: + if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("IMME OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_IBEA: + if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("IBEA OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_SCAN: + if (!strncmp(hm17_sbuffer,"OK+Set:5",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("SCAN OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_RESET: + if (!strncmp(hm17_sbuffer,"OK+RESET",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("RESET OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_RENEW: + if (!strncmp(hm17_sbuffer,"OK+RENEW",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("RENEW OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_CON: + if (!strncmp(hm17_sbuffer,"OK+CONNA",8)) { + hm17_sbclr(); +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNA OK")); +#endif + hm17_connecting=2; + break; + } + if (!strncmp(hm17_sbuffer,"OK+CONNE",8)) { + hm17_sbclr(); +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNE ERROR")); +#endif + break; + } + if (!strncmp(hm17_sbuffer,"OK+CONNF",8)) { + hm17_sbclr(); +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNF ERROR")); +#endif + break; + } + if (hm17_connecting==2 && !strncmp(hm17_sbuffer,"OK+CONN",7)) { + hm17_sbclr(); +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONN OK")); +#endif + hm17_connecting=3; + hm17_sendcmd(HM17_TEST); + hm17_connecting=0; + break; + } + break; + + case HM17_DISI: + case HM17_DISC: + if (!strncmp(hm17_sbuffer,"OK+DISCS",8)) { + hm17_sbclr(); + hm17_result=1; +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISCS OK")); +#endif + break; + } + if (!strncmp(hm17_sbuffer,"OK+DISIS",8)) { + hm17_sbclr(); + hm17_result=1; +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISIS OK")); +#endif + break; + } + if (!strncmp(hm17_sbuffer,"OK+DISCE",8)) { + hm17_sbclr(); + hm17_result=HM17_SUCESS; +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISCE OK")); +#endif + hm17_scanning=0; + break; + } + if (!strncmp(hm17_sbuffer,"OK+NAME:",8)) { + if (hm17_sbuffer[hm17_sindex-1]=='\n') { + hm17_result=HM17_SUCESS; +#ifdef IBEACON_DEBUG + if (hm17_debug) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("NAME OK")); + AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]); + } +#endif + hm17_sbclr(); + } + break; + } + if (!strncmp(hm17_sbuffer,"OK+DIS0:",8)) { + if (hm17_cmd==HM17_DISI) { +#ifdef HM17_V110 + goto hm17_v110; +#endif + } else { + if (hm17_sindex==20) { + hm17_result=HM17_SUCESS; +#ifdef IBEACON_DEBUG + if (hm17_debug) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("DIS0 OK")); + AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]); + } +#endif + hm17_sbclr(); + } + } + break; + } + if (!strncmp(hm17_sbuffer,"OK+DISC:",8)) { +hm17_v110: + if (hm17_cmd==HM17_DISI) { + if (hm17_sindex==78) { +#ifdef IBEACON_DEBUG + if (hm17_debug) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("DISC: OK")); + + AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]); + } +#endif + memcpy(ib.FACID,&hm17_sbuffer[8],8); + memcpy(ib.UID,&hm17_sbuffer[8+8+1],32); + memcpy(ib.MAJOR,&hm17_sbuffer[8+8+1+32+1],4); + memcpy(ib.MINOR,&hm17_sbuffer[8+8+1+32+1+4],4); + memcpy(ib.PWR,&hm17_sbuffer[8+8+1+32+1+4+4],2); + memcpy(ib.MAC,&hm17_sbuffer[8+8+1+32+1+4+4+2+1],12); + memcpy(ib.RSSI,&hm17_sbuffer[8+8+1+32+1+4+4+2+1+12+1],4); + + if (ibeacon_add(&ib)) { + ibeacon_mqtt(ib.MAC,ib.RSSI); + } + hm17_sbclr(); + hm17_result=1; + } + } else { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]); +#endif + } + break; + } + } +} + +void IBEACON_loop() { + + if (!IBEACON_Serial) return; + +uint32_t difftime=millis()-hm17_lastms; + + while (IBEACON_Serial->available()) { + hm17_lastms=millis(); + + if (hm17_sindexread(); + hm17_sindex++; + hm17_decode(); + } else { + hm17_sindex=0; + break; + } + } + + if (hm17_cmd==99) { + if (hm17_sindex>=HM17_BSIZ-2 || (hm17_sindex && (difftime>100))) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),hm17_sbuffer); + hm17_sbclr(); + } + } + +} + +#ifdef USE_WEBSERVER +const char HTTP_IBEACON[] PROGMEM = + "{s}IBEACON-UID : %s" " - RSSI : %s" "{m}{e}"; + +void IBEACON_Show(void) { +char mac[14]; +char rssi[6]; + + for (uint32_t cnt=0;cnt 0) { + char *cp=XdrvMailbox.data; + if (*cp>='0' && *cp<='8') { + hm17_sendcmd(*cp&7); + Response_P(S_JSON_IBEACON, XSNS_52,"hm17cmd",*cp&7); + } else if (*cp=='s') { + cp++; + len--; + while (*cp==' ') { + len--; + cp++; + } + IBEACON_Serial->write((uint8_t*)cp,len); + hm17_cmd=99; + Response_P(S_JSON_IBEACON1, XSNS_52,"hm17cmd",cp); + } else if (*cp=='u') { + cp++; + if (*cp) IB_UPDATE_TIME=atoi(cp); + Response_P(S_JSON_IBEACON, XSNS_52,"uintv",IB_UPDATE_TIME); + } else if (*cp=='t') { + cp++; + if (*cp) IB_TIMEOUT_TIME=atoi(cp); + Response_P(S_JSON_IBEACON, XSNS_52,"lintv",IB_TIMEOUT_TIME); + } else if (*cp=='c') { + for (uint32_t cnt=0;cnt + + +#define SPECIAL_SS + + + + + +#if MY_LANGUAGE==de-DE + +#define D_TPWRIN "Verbrauch" +#define D_TPWROUT "Einspeisung" +#define D_TPWRCURR "Aktueller Verbrauch" +#define D_TPWRCURR1 "Verbrauch P1" +#define D_TPWRCURR2 "Verbrauch P2" +#define D_TPWRCURR3 "Verbrauch P3" +#define D_Strom_L1 "Strom L1" +#define D_Strom_L2 "Strom L2" +#define D_Strom_L3 "Strom L3" +#define D_Spannung_L1 "Spannung L1" +#define D_Spannung_L2 "Spannung L2" +#define D_Spannung_L3 "Spannung L3" +#define D_METERNR "Zähler Nr" +#define D_METERSID "Service ID" +#define D_GasIN "Zählerstand" +#define D_H2oIN "Zählerstand" +#define D_StL1L2L3 "Ströme L1+L2+L3" +#define D_SpL1L2L3 "Spannung L1+L2+L3/3" + +#else + +#undef D_TPWRIN +#undef D_TPWROUT +#undef D_TPWRCURR +#undef D_TPWRCURR1 +#undef D_TPWRCURR2 +#undef D_TPWRCURR3 +#undef D_Strom_L1 +#undef D_Strom_L2 +#undef D_Strom_L3 +#undef D_Spannung_L1 +#undef D_Spannung_L2 +#undef D_Spannung_L3 +#undef D_METERNR +#undef D_METERSID +#undef D_GasIN +#undef D_H2oIN +#undef D_StL1L2L3 +#undef D_SpL1L2L3 + +#define D_TPWRIN "Total-In" +#define D_TPWROUT "Total-Out" +#define D_TPWRCURR "Current-In/Out" +#define D_TPWRCURR1 "Current-In p1" +#define D_TPWRCURR2 "Current-In p2" +#define D_TPWRCURR3 "Current-In p3" +#define D_Strom_L1 "Current L1" +#define D_Strom_L2 "Current L2" +#define D_Strom_L3 "Current L3" +#define D_Spannung_L1 "Voltage L1" +#define D_Spannung_L2 "Voltage L2" +#define D_Spannung_L3 "Voltage L3" +#define D_METERNR "Meter_number" +#define D_METERSID "Service ID" +#define D_GasIN "Counter" +#define D_H2oIN "Counter" +#define D_StL1L2L3 "Current L1+L2+L3" +#define D_SpL1L2L3 "Voltage L1+L2+L3/3" + +#endif + + + +#define DJ_TPWRIN "Total_in" +#define DJ_TPWROUT "Total_out" +#define DJ_TPWRCURR "Power_curr" +#define DJ_TPWRCURR1 "Power_p1" +#define DJ_TPWRCURR2 "Power_p2" +#define DJ_TPWRCURR3 "Power_p3" +#define DJ_CURR1 "Curr_p1" +#define DJ_CURR2 "Curr_p2" +#define DJ_CURR3 "Curr_p3" +#define DJ_VOLT1 "Volt_p1" +#define DJ_VOLT2 "Volt_p2" +#define DJ_VOLT3 "Volt_p3" +#define DJ_METERNR "Meter_number" +#define DJ_METERSID "Meter_id" +#define DJ_CSUM "Curr_summ" +#define DJ_VAVG "Volt_avg" +#define DJ_COUNTER "Count" + +struct METER_DESC { + uint8_t srcpin; + uint8_t type; + uint16_t flag; + int32_t params; + char prefix[8]; + int8_t trxpin; + uint8_t tsecs; + char *txmem; + uint8_t index; + uint8_t max_index; +}; + + + + + + +#define EHZ161_0 1 +#define EHZ161_1 2 +#define EHZ363 3 +#define EHZH 4 +#define EDL300 5 +#define Q3B 6 +#define COMBO3 7 +#define COMBO2 8 +#define COMBO3a 9 +#define Q3B_V1 10 +#define EHZ363_2 11 +#define COMBO3b 12 +#define WGS_COMBO 13 +#define EBZD_G 14 + + +#define METER EHZ161_1 + + +#if METER==EHZ161_0 +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0}}; +const uint8_t meter[]= +"1,1-0:1.8.0*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"1,1-0:2.8.0*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"1,1-0:21.7.0*255(@1," D_TPWRCURR1 ",W," DJ_TPWRCURR1 ",0|" +"1,1-0:41.7.0*255(@1," D_TPWRCURR2 ",W," DJ_TPWRCURR2 ",0|" +"1,1-0:61.7.0*255(@1," D_TPWRCURR3 ",W," DJ_TPWRCURR3 ",0|" +"1,=m 3+4+5 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0"; + +#endif + + + +#if METER==EHZ161_1 +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0}}; +const uint8_t meter[]= +"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0"; +#endif + + + +#if METER==EHZ363 +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}}; + +const uint8_t meter[]= + +"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" + +"1,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" + +"1,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" + +"1,77070100000009ff@#," D_METERNR ",," DJ_METERNR ",0"; +#endif + + + +#if METER==EHZH +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}}; + + +const uint8_t meter[]= + +"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" + +"1,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" + +"1,770701000f0700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0"; +#endif + + + +#if METER==EDL300 +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}}; + + +const uint8_t meter[]= + +"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" + +"1,77070100020801ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" + +"1,770701000f0700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0"; +#endif + +#if METER==EBZD_G +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'s',0,SML_BAUDRATE,"strom",-1,1,0}}; +const uint8_t meter[]= + +"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" + +"1,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" + +"1,77070100010801ff@1000," D_TPWRCURR1 ",KWh," DJ_TPWRCURR1 ",4|" + +"1,77070100010802ff@1000," D_TPWRCURR2 ",KWh," DJ_TPWRCURR2 ",4|" + +"1,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" + +"1,77070100600100ff@#," D_METERNR ",," DJ_METERNR ",0"; +#endif + + + + +#if METER==Q3B +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}}; +const uint8_t meter[]= + +"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" + +"1,77070100020801ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" + +"1,77070100010700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0"; +#endif + +#if METER==COMBO3 + +#undef METERS_USED +#define METERS_USED 3 + +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0}, + [1]={14,'s',0,SML_BAUDRATE,"SML",-1,1,0}, + [2]={4,'o',0,SML_BAUDRATE,"OBIS2",-1,1,0}}; + + +const uint8_t meter[]= +"1,1-0:1.8.0*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"1,1-0:2.8.0*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"1,1-0:21.7.0*255(@1," D_TPWRCURR1 ",W," DJ_TPWRCURR1 ",0|" +"1,1-0:41.7.0*255(@1," D_TPWRCURR2 ",W," DJ_TPWRCURR2 ",0|" +"1,1-0:61.7.0*255(@1," D_TPWRCURR3 ",W," DJ_TPWRCURR3 ",0|" +"1,=m 3+4+5 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|" +"2,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"2,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"2,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"3,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"3,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"3,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"3,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0"; + +#endif + +#if METER==COMBO2 + +#undef METERS_USED +#define METERS_USED 2 + +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'o',0,SML_BAUDRATE,"OBIS1",-1,1,0}, + [1]={14,'o',0,SML_BAUDRATE,"OBIS2",-1,1,0}}; + + +const uint8_t meter[]= +"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|" + +"2,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"2,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"2,=d 6 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"2,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0"; + +#endif + +#if METER==COMBO3a +#undef METERS_USED +#define METERS_USED 3 + +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'o',0,SML_BAUDRATE,"OBIS1",-1,1,0}, + [1]={14,'o',0,SML_BAUDRATE,"OBIS2",-1,1,0}, + [2]={1,'o',0,SML_BAUDRATE,"OBIS3",-1,1,0}}; + + +const uint8_t meter[]= +"1,=h --- Zähler Nr 1 ---|" +"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|" +"2,=h --- Zähler Nr 2 ---|" +"2,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"2,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"2,=d 6 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"2,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|" +"3,=h --- Zähler Nr 3 ---|" +"3,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"3,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"3,=d 10 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"3,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0"; + +#endif + + + +#if METER==Q3B_V1 +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ +[0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0}}; +const uint8_t meter[]= +"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"1,=d 1 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0"; +#endif + + + +#if METER==EHZ363_2 +#undef METERS_USED +#define METERS_USED 1 +struct METER_DESC const meter_desc[METERS_USED]={ +[0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}}; + +const uint8_t meter[]= + +"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" + +"1,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" + +"1,77070100010801ff@1000," D_TPWRCURR1 ",KWh," DJ_TPWRCURR1 ",4|" + +"1,77070100010802ff@1000," D_TPWRCURR2 ",KWh," DJ_TPWRCURR2 ",4|" + +"1,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" + +"1,77070100000009ff@#," D_METERNR ",," DJ_METERNR ",0"; +#endif + + +#if METER==COMBO3b +#undef METERS_USED +#define METERS_USED 3 +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0}, + [1]={14,'c',0,50,"Gas"}, + [2]={1,'c',0,10,"Wasser"}}; + + +const uint8_t meter[]= +"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" +"1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" +"1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" +"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|" + + +"2,1-0:1.8.0*255(@100," D_GasIN ",cbm," DJ_COUNTER ",2|" + +"3,1-0:1.8.0*255(@100," D_H2oIN ",cbm," DJ_COUNTER ",2"; +#endif + + +#if METER==WGS_COMBO +#undef METERS_USED +#define METERS_USED 3 + +struct METER_DESC const meter_desc[METERS_USED]={ + [0]={1,'c',0,10,"H20",-1,1,0}, + [1]={4,'c',0,50,"GAS",-1,1,0}, + [2]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}}; + +const uint8_t meter[]= + + +"1,1-0:1.8.0*255(@10000," D_H2oIN ",cbm," DJ_COUNTER ",4|" + + +"2,=h==================|" +"2,1-0:1.8.0*255(@100," D_GasIN ",cbm," DJ_COUNTER ",3|" + +"3,=h==================|" + +"3,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",3|" +"3,=h==================|" + +"3,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",2|" +"3,=h -------------------------------|" +"3,=m 10+11+12 @100," D_StL1L2L3 ",A," DJ_CSUM ",2|" + +"3,=m 13+14+15/#3 @100," D_SpL1L2L3 ",V," DJ_VAVG ",2|" +"3,=h==================|" + +"3,77070100240700ff@1," D_TPWRCURR1 ",W," DJ_TPWRCURR1 ",2|" + +"3,77070100380700ff@1," D_TPWRCURR2 ",W," DJ_TPWRCURR2 ",2|" + +"3,770701004c0700ff@1," D_TPWRCURR3 ",W," DJ_TPWRCURR3 ",2|" +"3,=h -------------------------------|" + +"3,770701001f0700ff@100," D_Strom_L1 ",A," DJ_CURR1 ",2|" + +"3,77070100330700ff@100," D_Strom_L2 ",A," DJ_CURR2 ",2|" + +"3,77070100470700ff@100," D_Strom_L3 ",A," DJ_CURR3 ",2|" +"3,=h -------------------------------|" + +"3,77070100200700ff@100," D_Spannung_L1 ",V," DJ_VOLT1 ",2|" + +"3,77070100340700ff@100," D_Spannung_L2 ",V," DJ_VOLT2 ",2|" + +"3,77070100480700ff@100," D_Spannung_L3 ",V," DJ_VOLT3 ",2|" +"3,=h==================|" + +"3,77070100000009ff@#," D_METERSID ",," DJ_METERSID ",0|" +"3,=h--------------------------------"; +#endif +# 499 "S:/Development/Tasmota/tasmota/xsns_53_sml.ino" +#define USE_SML_MEDIAN_FILTER + + +#ifndef SML_MAX_VARS +#define SML_MAX_VARS 20 +#endif + + +#define MAX_METERS 5 +double meter_vars[SML_MAX_VARS]; + +#define MAX_DVARS MAX_METERS*2 +double dvalues[MAX_DVARS]; +uint32_t dtimes[MAX_DVARS]; +uint8_t meters_used; + +struct METER_DESC const *meter_desc_p; +const uint8_t *meter_p; +uint8_t meter_spos[MAX_METERS]; + + +TasmotaSerial *meter_ss[MAX_METERS]; + + +#define SML_BSIZ 48 +uint8_t smltbuf[MAX_METERS][SML_BSIZ]; + + +#define METER_ID_SIZE 24 +char meter_id[MAX_METERS][METER_ID_SIZE]; + +#define EBUS_SYNC 0xaa +#define EBUS_ESC 0xa9 + +uint8_t sml_send_blocks; +uint8_t sml_100ms_cnt; +uint8_t sml_desc_cnt; + +#ifdef USE_SML_MEDIAN_FILTER + +#define MEDIAN_SIZE 5 +struct SML_MEDIAN_FILTER { +double buffer[MEDIAN_SIZE]; +int8_t index; +} sml_mf[SML_MAX_VARS]; + +#ifndef FLT_MAX +#define FLT_MAX 99999999 +#endif + +double sml_median_array(double *array,uint8_t len) { + uint8_t ind[len]; + uint8_t mind=0,index=0,flg; + double min=FLT_MAX; + + for (uint8_t hcnt=0; hcntbuffer[mf->index]=in; + mf->index++; + if (mf->index>=MEDIAN_SIZE) mf->index=0; + + return sml_median_array(mf->buffer,MEDIAN_SIZE); +# 603 "S:/Development/Tasmota/tasmota/xsns_53_sml.ino" +} +#endif + +#ifdef ANALOG_OPTO_SENSOR + +uint8_t ads1115_up; + + +#define SAMPLE_BIT (0x8000) + +#define ADS1115_COMP_QUEUE_SHIFT 0 +#define ADS1115_COMP_LATCH_SHIFT 2 +#define ADS1115_COMP_POLARITY_SHIFT 3 +#define ADS1115_COMP_MODE_SHIFT 4 +#define ADS1115_DATA_RATE_SHIFT 5 +#define ADS1115_MODE_SHIFT 8 +#define ADS1115_PGA_SHIFT 9 +#define ADS1115_MUX_SHIFT 12 + +enum ads1115_comp_queue { + ADS1115_COMP_QUEUE_AFTER_ONE = 0, + ADS1115_COMP_QUEUE_AFTER_TWO = 0x1 << ADS1115_COMP_QUEUE_SHIFT, + ADS1115_COMP_QUEUE_AFTER_FOUR = 0x2 << ADS1115_COMP_QUEUE_SHIFT, + ADS1115_COMP_QUEUE_DISABLE = 0x3 << ADS1115_COMP_QUEUE_SHIFT, + ADS1115_COMP_QUEUE_MASK = 0x3 << ADS1115_COMP_QUEUE_SHIFT, +}; + +enum ads1115_comp_latch { + ADS1115_COMP_LATCH_NO = 0, + ADS1115_COMP_LATCH_YES = 1 << ADS1115_COMP_LATCH_SHIFT, + ADS1115_COMP_LATCH_MASK = 1 << ADS1115_COMP_LATCH_SHIFT, +}; + +enum ads1115_comp_polarity { + ADS1115_COMP_POLARITY_ACTIVE_LOW = 0, + ADS1115_COMP_POLARITY_ACTIVE_HIGH = 1 << ADS1115_COMP_POLARITY_SHIFT, + ADS1115_COMP_POLARITY_MASK = 1 << ADS1115_COMP_POLARITY_SHIFT, +}; + +enum ads1115_comp_mode { + ADS1115_COMP_MODE_WINDOW = 0, + ADS1115_COMP_MODE_HYSTERESIS = 1 << ADS1115_COMP_MODE_SHIFT, + ADS1115_COMP_MODE_MASK = 1 << ADS1115_COMP_MODE_SHIFT, +}; + +enum ads1115_data_rate { + ADS1115_DATA_RATE_8_SPS = 0, + ADS1115_DATA_RATE_16_SPS = 0x1 << ADS1115_DATA_RATE_SHIFT, + ADS1115_DATA_RATE_32_SPS = 0x2 << ADS1115_DATA_RATE_SHIFT, + ADS1115_DATA_RATE_64_SPS = 0x3 << ADS1115_DATA_RATE_SHIFT, + ADS1115_DATA_RATE_128_SPS = 0x4 << ADS1115_DATA_RATE_SHIFT, + ADS1115_DATA_RATE_250_SPS = 0x5 << ADS1115_DATA_RATE_SHIFT, + ADS1115_DATA_RATE_475_SPS = 0x6 << ADS1115_DATA_RATE_SHIFT, + ADS1115_DATA_RATE_860_SPS = 0x7 << ADS1115_DATA_RATE_SHIFT, + ADS1115_DATA_RATE_MASK = 0x7 << ADS1115_DATA_RATE_SHIFT, +}; + +enum ads1115_mode { + ADS1115_MODE_CONTINUOUS = 0, + ADS1115_MODE_SINGLE_SHOT = 1 << ADS1115_MODE_SHIFT, + ADS1115_MODE_MASK = 1 << ADS1115_MODE_SHIFT, +}; + +enum ads1115_pga { + ADS1115_PGA_TWO_THIRDS = 0, + ADS1115_PGA_ONE = 0x1 << ADS1115_PGA_SHIFT, + ADS1115_PGA_TWO = 0x2 << ADS1115_PGA_SHIFT, + ADS1115_PGA_FOUR = 0x3 << ADS1115_PGA_SHIFT, + ADS1115_PGA_EIGHT = 0x4 << ADS1115_PGA_SHIFT, + ADS1115_PGA_SIXTEEN = 0x5 << ADS1115_PGA_SHIFT, + ADS1115_PGA_MASK = 0x7 << ADS1115_PGA_SHIFT, +}; + + +enum ads1115_mux { + ADS1115_MUX_DIFF_AIN0_AIN1 = 0, + ADS1115_MUX_DIFF_AIN0_AIN3 = 0x1 << ADS1115_MUX_SHIFT, + ADS1115_MUX_DIFF_AIN1_AIN3 = 0x2 << ADS1115_MUX_SHIFT, + ADS1115_MUX_DIFF_AIN2_AIN3 = 0x3 << ADS1115_MUX_SHIFT, + ADS1115_MUX_GND_AIN0 = 0x4 << ADS1115_MUX_SHIFT, + ADS1115_MUX_GND_AIN1 = 0x5 << ADS1115_MUX_SHIFT, + ADS1115_MUX_GND_AIN2 = 0x6 << ADS1115_MUX_SHIFT, + ADS1115_MUX_GND_AIN3 = 0x7 << ADS1115_MUX_SHIFT, + ADS1115_MUX_MASK = 0x7 << ADS1115_MUX_SHIFT, +}; + +class ADS1115 { +public: + ADS1115(uint8_t address = 0x48); + + void begin(); + uint8_t trigger_sample(); + uint8_t reset(); + bool is_sample_in_progress(); + int16_t read_sample(); + float sample_to_float(int16_t val); + float read_sample_float(); + + void set_comp_queue(enum ads1115_comp_queue val) { set_config(val, ADS1115_COMP_QUEUE_MASK); } + void set_comp_latching(enum ads1115_comp_latch val) { set_config(val, ADS1115_COMP_LATCH_MASK); } + void set_comp_polarity(enum ads1115_comp_polarity val) { set_config(val, ADS1115_COMP_POLARITY_MASK); } + void set_comp_mode(enum ads1115_comp_mode val) { set_config(val, ADS1115_COMP_MODE_MASK); } + void set_data_rate(enum ads1115_data_rate val) { set_config(val, ADS1115_DATA_RATE_MASK); } + void set_mode(enum ads1115_mode val) { set_config(val, ADS1115_MODE_MASK); } + void set_pga(enum ads1115_pga val) { set_config(val, ADS1115_PGA_MASK); m_voltage_range = val >> ADS1115_PGA_SHIFT; } + void set_mux(enum ads1115_mux val) { set_config(val, ADS1115_MUX_MASK); } + +private: + void set_config(uint16_t val, uint16_t mask) { + m_config = (m_config & ~mask) | val; + } + + uint8_t write_register(uint8_t reg, uint16_t val); + uint16_t read_register(uint8_t reg); + + uint8_t m_address; + uint16_t m_config; + int m_voltage_range; +}; + + +enum ads1115_register { + ADS1115_REGISTER_CONVERSION = 0, + ADS1115_REGISTER_CONFIG = 1, + ADS1115_REGISTER_LOW_THRESH = 2, + ADS1115_REGISTER_HIGH_THRESH = 3, +}; + +#define FACTOR 32768.0 +static float ranges[] = { 6.144 / FACTOR, 4.096 / FACTOR, 2.048 / FACTOR, 1.024 / FACTOR, 0.512 / FACTOR, 0.256 / FACTOR}; + +ADS1115::ADS1115(uint8_t address) +{ + m_address = address; + m_config = ADS1115_COMP_QUEUE_AFTER_ONE | + ADS1115_COMP_LATCH_NO | + ADS1115_COMP_POLARITY_ACTIVE_LOW | + ADS1115_COMP_MODE_WINDOW | + ADS1115_DATA_RATE_128_SPS | + ADS1115_MODE_SINGLE_SHOT | + ADS1115_MUX_GND_AIN0; + set_pga(ADS1115_PGA_ONE); +} + +uint8_t ADS1115::write_register(uint8_t reg, uint16_t val) +{ + Wire.beginTransmission(m_address); + Wire.write(reg); + Wire.write(val>>8); + Wire.write(val & 0xFF); + return Wire.endTransmission(); +} + +uint16_t ADS1115::read_register(uint8_t reg) +{ + Wire.beginTransmission(m_address); + Wire.write(reg); + Wire.endTransmission(); + + uint8_t result = Wire.requestFrom((int)m_address, 2, 1); + if (result != 2) { + return 0; + } + + uint16_t val; + + val = Wire.read() << 8; + val |= Wire.read(); + return val; +} + +void ADS1115::begin() +{ + Wire.begin(); +} + +uint8_t ADS1115::trigger_sample() +{ + return write_register(ADS1115_REGISTER_CONFIG, m_config | SAMPLE_BIT); +} + +uint8_t ADS1115::reset() +{ + Wire.beginTransmission(0); + Wire.write(0x6); + return Wire.endTransmission(); +} + +bool ADS1115::is_sample_in_progress() +{ + uint16_t val = read_register(ADS1115_REGISTER_CONFIG); + return (val & SAMPLE_BIT) == 0; +} + +int16_t ADS1115::read_sample() +{ + return read_register(ADS1115_REGISTER_CONVERSION); +} + +float ADS1115::sample_to_float(int16_t val) +{ + return val * ranges[m_voltage_range]; +} + +float ADS1115::read_sample_float() +{ + return sample_to_float(read_sample()); +} + +ADS1115 adc; + +void ADS1115_init(void) { + + ads1115_up=0; + if (!i2c_flg) return; + + adc.begin(); + adc.set_data_rate(ADS1115_DATA_RATE_128_SPS); + adc.set_mode(ADS1115_MODE_CONTINUOUS); + adc.set_mux(ADS1115_MUX_DIFF_AIN0_AIN3); + adc.set_pga(ADS1115_PGA_TWO); + + int16_t val = adc.read_sample(); + ads1115_up=1; +} + +#endif + +char sml_start; +uint8_t dump2log=0; + +#define SML_SAVAILABLE Serial_available() +#define SML_SREAD Serial_read() +#define SML_SPEAK Serial_peek() + +bool Serial_available() { + uint8_t num=dump2log&7; + if (num<1 || num>meters_used) num=1; + return meter_ss[num-1]->available(); +} + +uint8_t Serial_read() { + uint8_t num=dump2log&7; + if (num<1 || num>meters_used) num=1; + return meter_ss[num-1]->read(); +} + +uint8_t Serial_peek() { + uint8_t num=dump2log&7; + if (num<1 || num>meters_used) num=1; + return meter_ss[num-1]->peek(); +} + +uint8_t sml_logindex; + +void Dump2log(void) { + +int16_t index=0,hcnt=0; +uint32_t d_lastms; +uint8_t dchars[16]; + + + + if (dump2log&8) { + + while (SML_SAVAILABLE) { + log_data[index]=':'; + index++; + log_data[index]=' '; + index++; + d_lastms=millis(); + while ((millis()-d_lastms)<40) { + if (SML_SAVAILABLE) { + uint8_t c=SML_SREAD; + sprintf(&log_data[index],"%02x ",c); + dchars[hcnt]=c; + index+=3; + hcnt++; + if (hcnt>15) { + + log_data[index]='='; + index++; + log_data[index]='>'; + index++; + log_data[index]=' '; + index++; + for (uint8_t ccnt=0; ccnt<16; ccnt++) { + if (isprint(dchars[ccnt])) { + log_data[index]=dchars[ccnt]; + } else { + log_data[index]=' '; + } + index++; + } + break; + } + } + } + if (index>0) { + log_data[index]=0; + AddLog(LOG_LEVEL_INFO); + index=0; + hcnt=0; + } + } + } else { + if (meter_desc_p[(dump2log&7)-1].type=='o') { + + while (SML_SAVAILABLE) { + char c=SML_SREAD&0x7f; + if (c=='\n' || c=='\r') { + log_data[sml_logindex]=0; + AddLog(LOG_LEVEL_INFO); + sml_logindex=2; + log_data[0]=':'; + log_data[1]=' '; + break; + } + log_data[sml_logindex]=c; + if (sml_logindex2) { + log_data[index]=0; + AddLog(LOG_LEVEL_INFO); + } + } + } +} + + +uint8_t *skip_sml(uint8_t *cp,int16_t *res) { + uint8_t len,len1,type; + len=*cp&0xf; + type=*cp&0x70; + if (type==0x70) { + + + cp++; + while (len--) { + len1=*cp&0x0f; + cp+=len1; + } + *res=0; + } else { + + *res=(signed char)*(cp+1); + cp+=len; + } + return cp; +} + + + +double sml_getvalue(unsigned char *cp,uint8_t index) { +uint8_t len,unit,type; +int16_t scaler,result; +int64_t value; +double dval; + + + + cp=skip_sml(cp,&result); + + cp=skip_sml(cp,&result); + + cp=skip_sml(cp,&result); + + cp=skip_sml(cp,&result); + scaler=result; + + type=*cp&0x70; + len=*cp&0x0f; + cp++; + if (type==0x50 || type==0x60) { + + uint64_t uvalue=0; + uint8_t nlen=len; + while (--nlen) { + uvalue<<=8; + uvalue|=*cp++; + } + if (type==0x50) { + + switch (len-1) { + case 1: + + value=(signed char)uvalue; + break; + case 2: + +#ifdef DWS74_BUG + if (scaler==-2) { + value=(uint32_t)uvalue; + } else { + value=(int16_t)uvalue; + } +#else + value=(int16_t)uvalue; +#endif + break; + case 3: + case 4: + + value=(int32_t)uvalue; + break; + case 5: + case 6: + case 7: + case 8: + + value=(int64_t)uvalue; + break; + } + } else { + + value=uvalue; + } + + } else { + if (!(type&0xf0)) { + + + + if (len==9) { + + cp++; + uint32_t s1,s2; + s1=*cp<<16|*(cp+1)<<8|*(cp+2); + cp+=4; + s2=*cp<<16|*(cp+1)<<8|*(cp+2); + sprintf(&meter_id[index][0],"%u-%u",s1,s2); + } else { + + char *str=&meter_id[index][0]; + for (type=0; type= 'A' && chr <= 'F') rVal = chr + 10 - 'A'; + } + return rVal; +} + +uint8_t sb_counter; + + +double CharToDouble(const char *str) +{ + + char strbuf[24]; + + strlcpy(strbuf, str, sizeof(strbuf)); + char *pt = strbuf; + while ((*pt != '\0') && isblank(*pt)) { pt++; } + + signed char sign = 1; + if (*pt == '-') { sign = -1; } + if (*pt == '-' || *pt=='+') { pt++; } + + double left = 0; + if (*pt != '.') { + left = atoi(pt); + while (isdigit(*pt)) { pt++; } + } + + double right = 0; + if (*pt == '.') { + pt++; + right = atoi(pt); + while (isdigit(*pt)) { + pt++; + right /= 10.0; + } + } + + double result = left + right; + if (sign < 0) { + return -result; + } + return result; +} + + + +void ebus_esc(uint8_t *ebus_buffer, unsigned char len) { + short count,count1; + for (count=0; countavailable()) { + meter_ss[meters]->read(); + } +} + + +void sml_shift_in(uint32_t meters,uint32_t shard) { + uint32_t count; + if (meter_desc_p[meters].type!='e' && meter_desc_p[meters].type!='m' && meter_desc_p[meters].type!='M' && meter_desc_p[meters].type!='p') { + + for (count=0; countread(); + + if (meter_desc_p[meters].type=='o') { + smltbuf[meters][SML_BSIZ-1]=iob&0x7f; + } else if (meter_desc_p[meters].type=='s') { + smltbuf[meters][SML_BSIZ-1]=iob; + } else if (meter_desc_p[meters].type=='r') { + smltbuf[meters][SML_BSIZ-1]=iob; + } else if (meter_desc_p[meters].type=='m' || meter_desc_p[meters].type=='M') { + smltbuf[meters][meter_spos[meters]] = iob; + meter_spos[meters]++; + if (meter_spos[meters]>=9) { + SML_Decode(meters); + sml_empty_receiver(meters); + meter_spos[meters]=0; + } + } else if (meter_desc_p[meters].type=='p') { + smltbuf[meters][meter_spos[meters]] = iob; + meter_spos[meters]++; + if (meter_spos[meters]>=7) { + SML_Decode(meters); + sml_empty_receiver(meters); + meter_spos[meters]=0; + } + } else { + if (iob==EBUS_SYNC) { + + + if (meter_spos[meters]>4+5) { + + uint8_t tlen=smltbuf[meters][4]+5; + + if (smltbuf[meters][tlen]=ebus_CalculateCRC(smltbuf[meters],tlen)) { + ebus_esc(smltbuf[meters],tlen); + SML_Decode(meters); + } else { + + + } + } + meter_spos[meters]=0; + return; + } + smltbuf[meters][meter_spos[meters]] = iob; + meter_spos[meters]++; + if (meter_spos[meters]>=SML_BSIZ) { + meter_spos[meters]=0; + } + } + sb_counter++; + if (meter_desc_p[meters].type!='e' && meter_desc_p[meters].type!='m' && meter_desc_p[meters].type!='M' && meter_desc_p[meters].type!='p') SML_Decode(meters); +} + + + +void SML_Poll(void) { +uint32_t meters; + + for (meters=0; metersavailable()) { + sml_shift_in(meters,0); + } + } + } +} + + +void SML_Decode(uint8_t index) { + const char *mp=(const char*)meter_p; + int8_t mindex; + uint8_t *cp; + uint8_t dindex=0,vindex=0; + delay(0); + while (mp != NULL) { + + + + mindex=((*mp)&7)-1; + + if (mindex<0 || mindex>=meters_used) mindex=0; + mp+=2; + if (*mp=='=' && *(mp+1)=='h') { + mp = strchr(mp, '|'); + if (mp) mp++; + continue; + } + + if (index!=mindex) goto nextsect; + + + cp=&smltbuf[mindex][0]; + + + if (*mp=='=') { + + mp++; + + if (*mp=='m' && !sb_counter) { + + + mp++; + while (*mp==' ') mp++; + + double dvar; + uint8_t opr; + uint32_t ind; + ind=atoi(mp); + while (*mp>='0' && *mp<='9') mp++; + if (ind<1 || ind>SML_MAX_VARS) ind=1; + dvar=meter_vars[ind-1]; + for (uint8_t p=0;p<5;p++) { + if (*mp=='@') { + + meter_vars[vindex]=dvar; + mp++; + SML_Immediate_MQTT((const char*)mp,vindex,mindex); + break; + } + opr=*mp; + mp++; + uint8_t iflg=0; + if (*mp=='#') { + iflg=1; + mp++; + } + ind=atoi(mp); + while (*mp>='0' && *mp<='9') mp++; + if (ind<1 || ind>SML_MAX_VARS) ind=1; + switch (opr) { + case '+': + if (iflg) dvar+=ind; + else dvar+=meter_vars[ind-1]; + break; + case '-': + if (iflg) dvar-=ind; + else dvar-=meter_vars[ind-1]; + break; + case '*': + if (iflg) dvar*=ind; + else dvar*=meter_vars[ind-1]; + break; + case '/': + if (iflg) dvar/=ind; + else dvar/=meter_vars[ind-1]; + break; + } + while (*mp==' ') mp++; + if (*mp=='@') { + + meter_vars[vindex]=dvar; + mp++; + SML_Immediate_MQTT((const char*)mp,vindex,mindex); + break; + } + } + } else if (*mp=='d') { + + if (dindex='0' && *mp<='9') mp++; + if (ind<1 || ind>SML_MAX_VARS) ind=1; + uint32_t delay=atoi(mp)*1000; + uint32_t dtime=millis()-dtimes[dindex]; + if (dtime>delay) { + + dtimes[dindex]=millis(); + double vdiff = meter_vars[ind-1]-dvalues[dindex]; + dvalues[dindex]=meter_vars[ind-1]; + meter_vars[vindex]=(double)360000.0*vdiff/((double)dtime/10000.0); + + mp=strchr(mp,'@'); + if (mp) { + mp++; + SML_Immediate_MQTT((const char*)mp,vindex,mindex); + } + } + dindex++; + } + } else if (*mp=='h') { + + mp = strchr(mp, '|'); + if (mp) mp++; + continue; + } + } else { + + uint8_t found=1; + uint32_t ebus_dval=99; + float mbus_dval=99; + while (*mp!='@') { + if (meter_desc_p[mindex].type=='o' || meter_desc_p[mindex].type=='c') { + if (*mp++!=*cp++) { + found=0; + } + } else { + if (meter_desc_p[mindex].type=='s') { + + uint8_t val = hexnibble(*mp++) << 4; + val |= hexnibble(*mp++); + if (val!=*cp++) { + found=0; + } + } else { + + + if (*mp=='x' && *(mp+1)=='x') { + + mp+=2; + cp++; + } else if (!strncmp(mp,"UUuuUUuu",8)) { + uint32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0); + ebus_dval=val; + mbus_dval=val; + mp+=8; + cp+=4; + } else if (*mp=='U' && *(mp+1)=='U' && *(mp+2)=='u' && *(mp+3)=='u'){ + uint16_t val = cp[1]|(cp[0]<<8); + mbus_dval=val; + ebus_dval=val; + mp+=4; + cp+=2; + } else if (!strncmp(mp,"SSssSSss",8)) { + int32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0); + ebus_dval=val; + mbus_dval=val; + mp+=8; + cp+=4; + } else if (*mp=='u' && *(mp+1)=='u' && *(mp+2)=='U' && *(mp+3)=='U'){ + uint16_t val = cp[0]|(cp[1]<<8); + mbus_dval=val; + ebus_dval=val; + mp+=4; + cp+=2; + } else if (*mp=='u' && *(mp+1)=='u') { + uint8_t val = *cp++; + mbus_dval=val; + ebus_dval=val; + mp+=2; + } else if (*mp=='s' && *(mp+1)=='s' && *(mp+2)=='S' && *(mp+3)=='S') { + int16_t val = *cp|(*(cp+1)<<8); + mbus_dval=val; + ebus_dval=val; + mp+=4; + cp+=2; + } else if (*mp=='S' && *(mp+1)=='S' && *(mp+2)=='s' && *(mp+3)=='s') { + int16_t val = cp[1]|(cp[0]<<8); + mbus_dval=val; + ebus_dval=val; + mp+=4; + cp+=2; + } + else if (*mp=='s' && *(mp+1)=='s') { + int8_t val = *cp++; + mbus_dval=val; + ebus_dval=val; + mp+=2; + } + else if (!strncmp(mp,"ffffffff",8)) { + uint32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0); + float *fp=(float*)&val; + ebus_dval=*fp; + mbus_dval=*fp; + mp+=8; + cp+=4; + } + else if (!strncmp(mp,"FFffFFff",8)) { + + uint32_t val= (cp[1]<<0)|(cp[0]<<8)|(cp[3]<<16)|(cp[2]<<24); + float *fp=(float*)&val; + ebus_dval=*fp; + mbus_dval=*fp; + mp+=8; + cp+=4; + } + else if (!strncmp(mp,"eeeeee",6)) { + uint32_t val=(cp[0]<<16)|(cp[1]<<8)|(cp[2]<<0); + mbus_dval=val; + mp+=6; + cp+=3; + } + else if (!strncmp(mp,"vvvvvv",6)) { + mbus_dval=(float)((cp[0]<<8)|(cp[1])) + ((float)cp[2]/10.0); + mp+=6; + cp+=3; + } + else if (!strncmp(mp,"cccccc",6)) { + mbus_dval=(float)((cp[0]<<8)|(cp[1])) + ((float)cp[2]/100.0); + mp+=6; + cp+=3; + } + else if (!strncmp(mp,"pppp",4)) { + mbus_dval=(float)((cp[0]<<8)|cp[1]); + mp+=4; + cp+=2; + } + else { + uint8_t val = hexnibble(*mp++) << 4; + val |= hexnibble(*mp++); + if (val!=*cp++) { + found=0; + } + } + } + } + } + if (found) { + + mp++; + if (*mp=='#') { + + mp++; + if (meter_desc_p[mindex].type=='o') { + for (uint8_t p=0;p>=shift; + ebus_dval&=1; + mp+=2; + } + if (*mp=='i') { + + mp++; + uint8_t mb_index=strtol((char*)mp,(char**)&mp,10); + if (mb_index!=meter_desc_p[mindex].index) { + goto nextsect; + } + uint16_t crc = MBUS_calculateCRC(&smltbuf[mindex][0],7); + if (lowByte(crc)!=smltbuf[mindex][7]) goto nextsect; + if (highByte(crc)!=smltbuf[mindex][8]) goto nextsect; + dval=mbus_dval; + + mp++; + } else { + if (meter_desc_p[mindex].type=='p') { + uint8_t crc = SML_PzemCrc(&smltbuf[mindex][0],6); + if (crc!=smltbuf[mindex][6]) goto nextsect; + dval=mbus_dval; + } else { + dval=ebus_dval; + } + } + + } +#ifdef USE_SML_MEDIAN_FILTER + if (meter_desc_p[mindex].flag&16) { + meter_vars[vindex]=sml_median(&sml_mf[vindex],dval); + } else { + meter_vars[vindex]=dval; + } +#else + meter_vars[vindex]=dval; +#endif + + + double fac=CharToDouble((char*)mp); + meter_vars[vindex]/=fac; + SML_Immediate_MQTT((const char*)mp,vindex,mindex); + } + } + } +nextsect: + + if (vindex=meters_used) lastmind=0; + while (mp != NULL) { + + mindex=((*mp)&7)-1; + if (mindex<0 || mindex>=meters_used) mindex=0; + mp+=2; + if (*mp=='=' && *(mp+1)=='h') { + mp+=2; + + if (json) { + mp = strchr(mp, '|'); + if (mp) mp++; + continue; + } + + uint8_t i; + for (i=0;isml_counters[index].sml_debounce) { + RtcSettings.pulse_counter[index]++; + InjektCounterValue(sml_counters[index].sml_cnt_old_state,RtcSettings.pulse_counter[index]); + } + } else { + + sml_counters[index].sml_counter_ltime=millis(); + } +} + +void SML_CounterUpd1(void) { + SML_CounterUpd(0); +} + +void SML_CounterUpd2(void) { + SML_CounterUpd(1); +} + +void SML_CounterUpd3(void) { + SML_CounterUpd(2); +} + +void SML_CounterUpd4(void) { + SML_CounterUpd(3); +} + +#ifdef USE_SCRIPT +struct METER_DESC script_meter_desc[MAX_METERS]; +uint8_t *script_meter; +#endif + +#ifndef METER_DEF_SIZE +#define METER_DEF_SIZE 3000 +#endif + +bool Gpio_used(uint8_t gpiopin) { + for (uint16_t i=0;iM",-2,0); + if (meter_script==99) { + + if (script_meter) free(script_meter); + script_meter=0; + uint8_t *tp=0; + uint16_t index=0; + uint8_t section=0; + uint8_t srcpin=0; + char *lp=glob_script_mem.scriptptr; + sml_send_blocks=0; + while (lp) { + if (!section) { + if (*lp=='>' && *(lp+1)=='M') { + lp+=2; + meters_used=strtol(lp,0,10); + section=1; + uint32_t mlen=0; + for (uint32_t cnt=0;cnt') { + if (*(tp-1)=='|') *(tp-1)=0; + break; + } + if (*lp=='+') { + + + lp++; + index=*lp&7; + lp+=2; + if (index<1 || index>meters_used) goto next_line; + index--; + srcpin=strtol(lp,&lp,10); + if (Gpio_used(srcpin)) { + AddLog_P(LOG_LEVEL_INFO, PSTR("gpio rx double define!")); +dddef_exit: + if (script_meter) free(script_meter); + script_meter=0; + meters_used=METERS_USED; + goto init10; + } + script_meter_desc[index].srcpin=srcpin; + if (*lp!=',') goto next_line; + lp++; + script_meter_desc[index].type=*lp; + lp+=2; + script_meter_desc[index].flag=strtol(lp,&lp,10); + if (*lp!=',') goto next_line; + lp++; + script_meter_desc[index].params=strtol(lp,&lp,10); + if (*lp!=',') goto next_line; + lp++; + script_meter_desc[index].prefix[7]=0; + for (uint32_t cnt=0; cnt<8; cnt++) { + if (*lp==SCRIPT_EOL || *lp==',') { + script_meter_desc[index].prefix[cnt]=0; + break; + } + script_meter_desc[index].prefix[cnt]=*lp++; + } + if (*lp==',') { + lp++; + script_meter_desc[index].trxpin=strtol(lp,&lp,10); + if (Gpio_used(script_meter_desc[index].trxpin)) { + AddLog_P(LOG_LEVEL_INFO, PSTR("gpio tx double define!")); + goto dddef_exit; + } + if (*lp!=',') goto next_line; + lp++; + script_meter_desc[index].tsecs=strtol(lp,&lp,10); + if (*lp==',') { + lp++; + char txbuff[256]; + uint32_t txlen=0,tx_entries=1; + for (uint32_t cnt=0; cntmeters_used) goto next_line; + while (1) { + if (*lp==SCRIPT_EOL) { + if (*(tp-1)!='|') *tp++='|'; + goto next_line; + } + *tp++=*lp++; + index++; + if (index>=METER_DEF_SIZE) break; + } + } + + } + +next_line: + if (*lp==SCRIPT_EOL) { + lp++; + } else { + lp = strchr(lp, SCRIPT_EOL); + if (!lp) break; + lp++; + } + } + *tp=0; + meter_desc_p=script_meter_desc; + meter_p=script_meter; + } +#endif + +init10: + typedef void (*function)(); + function counter_callbacks[] = {SML_CounterUpd1,SML_CounterUpd2,SML_CounterUpd3,SML_CounterUpd4}; + uint8_t cindex=0; + + for (byte i = 0; i < MAX_COUNTERS; i++) { + RtcSettings.pulse_counter[i]=Settings.pulse_counter[i]; + sml_counters[i].sml_cnt_last_ts=millis(); + } + for (uint8_t meters=0; metersbegin(meter_desc_p[meters].params)) { + meter_ss[meters]->flush(); + } + if (meter_ss[meters]->hardwareSerial()) { + if (meter_desc_p[meters].type=='M') { + Serial.begin(meter_desc_p[meters].params, SERIAL_8E1); + } + ClaimSerial(); + } + + } + } + +} + + +#ifdef USE_SML_SCRIPT_CMD +uint32_t SML_SetBaud(uint32_t meter, uint32_t br) { + if (meter<1 || meter>meters_used) return 0; + meter--; + if (!meter_ss[meter]) return 0; + if (meter_ss[meter]->begin(br)) { + meter_ss[meter]->flush(); + } + if (meter_ss[meter]->hardwareSerial()) { + if (meter_desc_p[meter].type=='M') { + Serial.begin(br, SERIAL_8E1); + } + } + return 1; +} + +uint32_t SML_Write(uint32_t meter,char *hstr) { + if (meter<1 || meter>meters_used) return 0; + meter--; + if (!meter_ss[meter]) return 0; + SML_Send_Seq(meter,hstr); + return 1; +} +#endif + + +void SetDBGLed(uint8_t srcpin, uint8_t ledpin) { + pinMode(ledpin, OUTPUT); + if (digitalRead(srcpin)) { + digitalWrite(ledpin,LOW); + } else { + digitalWrite(ledpin,HIGH); + } +} + + +void SML_Counter_Poll(void) { +uint16_t meters,cindex=0; +uint32_t ctime=millis(); + + for (meters=0; meters0) { + if (ctime-sml_counters[cindex].sml_cnt_last_ts>meter_desc_p[meters].params) { + sml_counters[cindex].sml_cnt_last_ts=ctime; + + if (meter_desc_p[meters].flag&2) { + +#ifdef ANALOG_OPTO_SENSOR + if (ads1115_up) { + int16_t val = adc.read_sample(); + if (val>sml_counters[cindex].ana_max) sml_counters[cindex].ana_max=val; + if (val10) { + sml_counters[cindex].sml_cnt_last_ts=ctime; +#ifdef DEBUG_CNT_LED1 + if (cindex==0) SetDBGLed(meter_desc_p[meters].srcpin,DEBUG_CNT_LED1); +#endif +#ifdef DEBUG_CNT_LED2 + if (cindex==1) SetDBGLed(meter_desc_p[meters].srcpin,DEBUG_CNT_LED2); +#endif + } + } + cindex++; + } + } +} + +#ifdef USE_SCRIPT +char *SML_Get_Sequence(char *cp,uint32_t index) { + if (!index) return cp; + uint32_t cindex=0; + while (cp) { + cp=strchr(cp,','); + if (cp) { + cp++; + cindex++; + if (cindex==index) { + return cp; + } + } + } +} + +void SML_Check_Send(void) { + sml_100ms_cnt++; + char *cp; + for (uint32_t cnt=sml_desc_cnt; cnt=0 && script_meter_desc[cnt].txmem) { + if ((sml_100ms_cnt%script_meter_desc[cnt].tsecs)==0) { + if (script_meter_desc[cnt].max_index>1) { + script_meter_desc[cnt].index++; + if (script_meter_desc[cnt].index>=script_meter_desc[cnt].max_index) { + script_meter_desc[cnt].index=0; + sml_desc_cnt++; + } + cp=SML_Get_Sequence(script_meter_desc[cnt].txmem,script_meter_desc[cnt].index); + + } else { + cp=script_meter_desc[cnt].txmem; + + sml_desc_cnt++; + } + + SML_Send_Seq(cnt,cp); + if (sml_desc_cnt>=meters_used) { + sml_desc_cnt=0; + } + break; + } + } else { + sml_desc_cnt++; + } + + if (sml_desc_cnt>=meters_used) { + sml_desc_cnt=0; + } + } +} + +uint8_t sml_hexnibble(char chr) { + uint8_t rVal = 0; + if (isdigit(chr)) { + rVal = chr - '0'; + } else { + if (chr >= 'A' && chr <= 'F') rVal = chr + 10 - 'A'; + if (chr >= 'a' && chr <= 'f') rVal = chr + 10 - 'a'; + } + return rVal; +} + + +void SML_Send_Seq(uint32_t meter,char *seq) { + uint8_t sbuff[32]; + uint8_t *ucp=sbuff,slen=0; + char *cp=seq; + while (*cp) { + if (!*cp || !*(cp+1)) break; + if (*cp==',') break; + uint8_t iob=(sml_hexnibble(*cp) << 4) | sml_hexnibble(*(cp+1)); + cp+=2; + *ucp++=iob; + slen++; + if (slen>=sizeof(sbuff)) break; + } + if (script_meter_desc[meter].type=='m' || script_meter_desc[meter].type=='M') { + *ucp++=0; + *ucp++=2; + + uint16_t crc = MBUS_calculateCRC(sbuff,6); + *ucp++=lowByte(crc); + *ucp++=highByte(crc); + slen+=4; + } + if (script_meter_desc[meter].type=='o') { + for (uint32_t cnt=0;cntwrite(sbuff,slen); +} +#endif + +uint16_t MBUS_calculateCRC(uint8_t *frame, uint8_t num) { + uint16_t crc, flag; + crc = 0xFFFF; + for (uint32_t i = 0; i < num; i++) { + crc ^= frame[i]; + for (uint32_t j = 8; j; j--) { + if ((crc & 0x0001) != 0) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + return crc; +} + +uint8_t SML_PzemCrc(uint8_t *data, uint8_t len) { + uint16_t crc = 0; + for (uint32_t i = 0; i < len; i++) crc += *data++; + return (uint8_t)(crc & 0xFF); +} + + +uint8_t CalcEvenParity(uint8_t data) { +uint8_t parity=0; + + while(data) { + parity^=(data &1); + data>>=1; + } + return parity; +} +# 2361 "S:/Development/Tasmota/tasmota/xsns_53_sml.ino" +bool XSNS_53_cmd(void) { + bool serviced = true; + if (XdrvMailbox.data_len > 0) { + char *cp=XdrvMailbox.data; + if (*cp=='d') { + + cp++; + uint8_t index=atoi(cp); + if ((index&7)>meters_used) index=1; + if (index>0 && meter_desc_p[(index&7)-1].type=='c') { + index=0; + } + dump2log=index; + ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"dump: %d\"}}"),dump2log); + } else if (*cp=='c') { + + cp++; + uint8_t index=*cp&7; + if (index<1 || index>MAX_COUNTERS) index=1; + cp++; + while (*cp==' ') cp++; + if (isdigit(*cp)) { + uint32_t cval=atoi(cp); + while (isdigit(*cp)) cp++; + RtcSettings.pulse_counter[index-1]=cval; + uint8_t cindex=0; + for (uint8_t meters=0; metersaddress, INA226_REG_CALIBRATION, si->calibrationValue); + +} + + + + + + +bool Ina226TestPresence(uint8_t device) +{ + + + + uint16_t config = I2cRead16( slaveInfo[device].address, INA226_REG_CONFIG ); + + + if (config != slaveInfo[device].config) + return false; + + return true; + +} + +void Ina226ResetActive(void) +{ + Ina226SlaveInfo_t *p = slaveInfo; + + for (uint32_t i = 0; i < INA226_MAX_ADDRESSES; i++) { + p = &slaveInfo[i]; + + uint8_t addr = p->address; + if (addr) { + I2cResetActive(addr); + } + } +} + + + + + +void Ina226Init() +{ + uint32_t i; + + slavesFound = 0; + + Ina226SlaveInfo_t *p = slaveInfo; +# 215 "S:/Development/Tasmota/tasmota/xsns_54_ina226.ino" + for (i = 0; i < 4; i++){ + *p = {0}; + } + + + + + + for (i = 0; i < INA226_MAX_ADDRESSES; i++){ + uint8_t addr = pgm_read_byte(probeAddresses + i); + + if (I2cActive(addr)) { continue; } + + + + + if (!Settings.ina226_i_fs[i]) + continue; + + + + + + + if (!I2cWrite16( addr, INA226_REG_CONFIG, INA226_CONFIG_RESET)){ + + AddLog_P2( LOG_LEVEL_DEBUG, "No INA226 at address: %02X", addr); + continue; + } + + + + uint16_t config = I2cRead16( addr, INA226_REG_CONFIG ); + + + if (INA226_RES_CONFIG != config) + continue; + + + config = INA226_DEF_CONFIG; + + + if (!I2cWrite16( addr, INA226_REG_CONFIG, config)) + continue; + + + p = &slaveInfo[i]; + + p->address = addr; + + p->config = config; + + + p->i_lsb = (((float) Settings.ina226_i_fs[i])/10.0f)/32768.0f; + + + + uint32_t r_shunt_uohms = _expand_r_shunt(Settings.ina226_r_shunt[i]); + + + + p->calibrationValue = ((uint16_t) (0.00512/(p->i_lsb * r_shunt_uohms/1000000.0f))); + + p->present = true; + + + Ina226SetCalibration(i); + + I2cSetActiveFound(addr, Ina226Str); + + slavesFound++; + } +} + + + + + +float Ina226ReadBus_v(uint8_t device) +{ + uint8_t addr = slaveInfo[device].address; + int16_t reg_bus_v = I2cReadS16( addr, INA226_REG_BUSVOLTAGE); + + float result = ((float) reg_bus_v) * 0.00125f; + + return result; + +} + + + + + +float Ina226ReadShunt_i(uint8_t device) +{ + uint8_t addr = slaveInfo[device].address; + int16_t reg_shunt_i = I2cReadS16( addr, INA226_REG_CURRENT); + + float result = ((float) reg_shunt_i) * slaveInfo[device].i_lsb; + + return result; +} + + + + + +float Ina226ReadPower_w(uint8_t device) +{ + uint8_t addr = slaveInfo[device].address; + int16_t reg_shunt_i = I2cReadS16( addr, INA226_REG_POWER); + + float result = ((float) reg_shunt_i) * (slaveInfo[device].i_lsb * 25.0); + + return result; +} + + + + + + +void Ina226Read(uint8_t device) +{ + + voltages[device] = Ina226ReadBus_v(device); + currents[device] = Ina226ReadShunt_i(device); + powers[device] = Ina226ReadPower_w(device); + + + + +} + + + + + +void Ina226EverySecond() +{ + + for (uint8_t device = 0; device < INA226_MAX_ADDRESSES; device++){ + + if (slavesFound && slaveInfo[device].present && Ina226TestPresence(device)){ + Ina226Read(device); + } + else { + powers[device] = currents[device] = voltages[device] = 0.0f; + + + + + + + slaveInfo[device].present = false; + } + } +} + + + + + +bool Ina226CommandSensor() +{ + bool serviced = true; + bool show_config = false; + char param_str[64]; + char *cp, *params[4]; + uint8_t i, param_count, device, p1 = XdrvMailbox.payload; + uint32_t r_shunt_uohms; + uint16_t compact_r_shunt_uohms; + + + + + + if (XdrvMailbox.data_len > 62){ + return false; + } + + strncpy(param_str, XdrvMailbox.data, XdrvMailbox.data_len + 1); + param_str[XdrvMailbox.data_len] = 0; + + + for (cp = param_str, i = 0, param_count = 0; *cp && (i < XdrvMailbox.data_len + 1) && (param_count <= 3); i++) + if (param_str[i] == ' ' || param_str[i] == ',' || param_str[i] == 0){ + param_str[i] = 0; + params[param_count] = cp; + + param_count++; + cp = param_str + i + 1; + } + + + if (p1 < 10 || p1 >= 50){ + + switch (p1){ + case 1: + Ina226ResetActive(); + Ina226Init(); + Response_P(PSTR("{\"Sensor54-Command-Result\":{\"SlavesFound\":%d}}"),slavesFound); + break; + + case 2: + restart_flag = 2; + Response_P(PSTR("{\"Sensor54-Command-Result\":{\"Restart_flag\":%d}}"),restart_flag); + break; + + default: + serviced = false; + } + } + else if (p1 < 50){ + + device = (p1 / 10) - 1; + switch (p1 % 10){ + case 0: + show_config = true; + break; + + case 1: + r_shunt_uohms = (uint32_t) ((CharToFloat(params[1])) * 1000000.0f); + + + + if (r_shunt_uohms > 32767){ + uint32_t r_shunt_mohms = r_shunt_uohms/1000UL; + Settings.ina226_r_shunt[device] = (uint16_t) (r_shunt_mohms | 0x8000); + } + else + Settings.ina226_r_shunt[device] = (uint16_t) r_shunt_uohms; + + + show_config = true; + break; + + case 2: + Settings.ina226_i_fs[device] = (uint16_t) ((CharToFloat(params[1])) * 10.0f); + + show_config = true; + break; + + + default: + serviced = false; + break; + } + } + else + serviced = false; + + if (show_config) { + char shunt_r_str[16]; + char fs_i_str[16]; + + + r_shunt_uohms = _expand_r_shunt(Settings.ina226_r_shunt[device]); + dtostrfd(((float)r_shunt_uohms)/1000000.0f, 6, shunt_r_str); + + dtostrfd(((float)Settings.ina226_i_fs[device])/10.0f, 1, fs_i_str); + + Response_P(PSTR("{\"Sensor54-device-settings-%d\":{\"SHUNT_R\":%s,\"FS_I\":%s}}"), + device + 1, shunt_r_str, fs_i_str); + } + + return serviced; +} + + + + + +#ifdef USE_WEBSERVER +const char HTTP_SNS_INA226_DATA[] PROGMEM = + "{s}%s " D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" + "{s}%s " D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" + "{s}%s " D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"; +#endif + +void Ina226Show(bool json) +{ + int i, num_found; + for (num_found = 0, i = 0; i < INA226_MAX_ADDRESSES; i++) { + + if (!slaveInfo[i].present) + continue; + + num_found++; + + char voltage[16]; + dtostrfd(voltages[i], Settings.flag2.voltage_resolution, voltage); + char current[16]; + dtostrfd(currents[i], Settings.flag2.current_resolution, current); + char power[16]; + dtostrfd(powers[i], Settings.flag2.wattage_resolution, power); + char name[16]; + snprintf_P(name, sizeof(name), PSTR("INA226%c%d"),IndexSeparator(), i + 1); + + + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"Id\":%d,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_POWERUSAGE "\":%s}"), + name, i, voltage, current, power); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_VOLTAGE, voltage); + DomoticzSensor(DZ_CURRENT, current); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_INA226_DATA, name, voltage, name, current, name, power); +#endif + } + + } + +} +# 546 "S:/Development/Tasmota/tasmota/xsns_54_ina226.ino" +bool Xsns54(byte callback_id) +{ + if (!I2cEnabled(XI2C_35)) { return false; } + + + bool result = false; + + + switch (callback_id) { + case FUNC_EVERY_SECOND: + Ina226EverySecond(); + break; + case FUNC_JSON_APPEND: + Ina226Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Ina226Show(0); + break; +#endif + case FUNC_COMMAND_SENSOR: + if (XSNS_54 == XdrvMailbox.index) { + result = Ina226CommandSensor(); + } + break; + case FUNC_INIT: + Ina226Init(); + break; + } + + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_55_hih_series.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_55_hih_series.ino" +#ifdef USE_I2C +#ifdef USE_HIH6 +# 33 "S:/Development/Tasmota/tasmota/xsns_55_hih_series.ino" +#define XSNS_55 55 +#define XI2C_36 36 + +#define HIH6_ADDR 0x27 + +struct HIH6 { + float temperature = 0; + float humidity = 0; + uint8_t valid = 0; + uint8_t type = 0; + char types[4] = "HIH"; +} Hih6; + +bool Hih6Read(void) +{ + Wire.beginTransmission(HIH6_ADDR); + if (Wire.endTransmission() != 0) { return false; } + + delay(40); + + uint8_t data[4]; + Wire.requestFrom(HIH6_ADDR, 4); + if (4 == Wire.available()) { + data[0] = Wire.read(); + data[1] = Wire.read(); + data[2] = Wire.read(); + data[3] = Wire.read(); + } else { return false; } + + + + Hih6.humidity = ConvertHumidity(((float)(((data[0] & 0x3F) << 8) | data[1]) * 100.0) / 16383.0); + + int temp = ((data[2] << 8) | (data[3] & 0xFC)) / 4; + Hih6.temperature = ConvertTemp(((float)temp / 16384.0) * 165.0 - 40.0); + + Hih6.valid = SENSOR_MAX_MISS; + return true; +} + + + +void Hih6Detect(void) +{ + if (I2cActive(HIH6_ADDR)) { return; } + + if (uptime < 2) { delay(20); } + Hih6.type = Hih6Read(); + if (Hih6.type) { + I2cSetActiveFound(HIH6_ADDR, Hih6.types); + } +} + +void Hih6EverySecond(void) +{ + if (uptime &1) { + + if (!Hih6Read()) { + AddLogMissed(Hih6.types, Hih6.valid); + } + } +} + +void Hih6Show(bool json) +{ + if (Hih6.valid) { + char temperature[33]; + dtostrfd(Hih6.temperature, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(Hih6.humidity, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, Hih6.types, temperature, humidity); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, Hih6.temperature); + KnxSensor(KNX_HUMIDITY, Hih6.humidity); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, Hih6.types, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, Hih6.types, humidity); +#endif + } + } +} + + + + + +bool Xsns55(uint8_t function) +{ + if (!I2cEnabled(XI2C_36)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Hih6Detect(); + } + else if (Hih6.type) { + switch (function) { + case FUNC_EVERY_SECOND: + Hih6EverySecond(); + break; + case FUNC_JSON_APPEND: + Hih6Show(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Hih6Show(0); + break; + #endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_56_hpma.ino" +# 21 "S:/Development/Tasmota/tasmota/xsns_56_hpma.ino" +#ifdef USE_HPMA +# 31 "S:/Development/Tasmota/tasmota/xsns_56_hpma.ino" +#define XSNS_56 56 + +#include +#include + +TasmotaSerial *HpmaSerial; +HPMA115S0 *hpma115S0; + +uint8_t hpma_type = 1; +uint8_t hpma_valid = 0; + +struct hpmadata { + unsigned int pm10; + unsigned int pm2_5; +} hpma_data; + + + +void HpmaSecond(void) +{ + unsigned int pm2_5, pm10; + + + + + if (hpma115S0->ReadParticleMeasurement(&pm2_5, &pm10)) { + hpma_data.pm2_5 = pm2_5; + hpma_data.pm10 = pm10; + hpma_valid = 1; + } + +} + +void HpmaInit(void) +{ + hpma_type = 0; + if (pin[GPIO_HPMA_RX] < 99 && pin[GPIO_HPMA_TX] < 99) { + HpmaSerial = new TasmotaSerial(pin[GPIO_HPMA_RX], pin[GPIO_HPMA_TX], 1); + hpma115S0 = new HPMA115S0(*HpmaSerial); + + if (HpmaSerial->begin(9600)) { + if (HpmaSerial->hardwareSerial()) { + ClaimSerial(); + } + hpma_type = 1; + hpma115S0->Init(); + hpma115S0->StartParticleMeasurement(); + } + } +} + +#ifdef USE_WEBSERVER +const char HTTP_HPMA_SNS[] PROGMEM = + "{s}HPMA " D_ENVIRONMENTAL_CONCENTRATION "2.5 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}HPMA " D_ENVIRONMENTAL_CONCENTRATION "10 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"; +#endif + +void HpmaShow(bool json) +{ + if (hpma_valid) { + char pm10[33]; + snprintf_P(pm10, 33, PSTR("%d"), hpma_data.pm10); + char pm2_5[33]; + snprintf_P(pm2_5, 33, PSTR("%d"), hpma_data.pm2_5); + + if (json) { + ResponseAppend_P(PSTR(",\"HPMA\":{\"PM2.5\":%d,\"PM10\":%d}"), hpma_data.pm2_5, hpma_data.pm10); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { + DomoticzSensor(DZ_VOLTAGE, pm2_5); + DomoticzSensor(DZ_CURRENT, pm10); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_HPMA_SNS, pm2_5, pm10); +#endif + } + } +} + + + + + +bool Xsns56(uint8_t function) +{ + bool result = false; + + if (hpma_type) { + switch (function) { + case FUNC_INIT: + HpmaInit(); + break; + case FUNC_EVERY_SECOND: + HpmaSecond(); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_56 == XdrvMailbox.index) { + return true; + } + break; + case FUNC_JSON_APPEND: + HpmaShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + HpmaShow(0); + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_57_tsl2591.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_57_tsl2591.ino" +#ifdef USE_I2C +#ifdef USE_TSL2591 + + + + + + + +#define XSNS_57 57 +#define XI2C_40 40 + +#define TSL2591_ADDRESS 0x29 + +#include + +Adafruit_TSL2591 tsl = Adafruit_TSL2591(); + +uint8_t tsl2591_type = 0; +uint8_t tsl2591_valid = 0; +float tsl2591_lux = 0; + +void Tsl2591Init(void) +{ + + if (I2cSetDevice(0x29)) { + if (tsl.begin()) { + tsl.setGain(TSL2591_GAIN_MED); + tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS); + tsl2591_type = 1; + I2cSetActiveFound(TSL2591_ADDRESS, "TSL2591"); + } + } +} + +bool Tsl2591Read(void) +{ + uint32_t lum = tsl.getFullLuminosity(); + uint16_t ir, full; + ir = lum >> 16; + full = lum & 0xFFFF; + tsl2591_lux = tsl.calculateLux(full, ir); + tsl2591_valid = 1; +} + +void Tsl2591EverySecond(void) +{ + Tsl2591Read(); +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_TSL2591[] PROGMEM = + "{s}TSL2591 " D_ILLUMINANCE "{m}%s " D_UNIT_LUX "{e}"; +#endif + +void Tsl2591Show(bool json) +{ + if (tsl2591_valid) { + char lux_str[10]; + dtostrf(tsl2591_lux, sizeof(lux_str)-1, 3, lux_str); + if (json) { + ResponseAppend_P(PSTR(",\"TSL2591\":{\"" D_JSON_ILLUMINANCE "\":%s}"), lux_str); +#ifdef USE_DOMOTICZ + if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, tsl2591_lux); } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TSL2591, lux_str); +#endif + } + } +} + + + + + +bool Xsns57(uint8_t function) +{ + if (!I2cEnabled(XI2C_40)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Tsl2591Init(); + } + else if (tsl2591_type) { + switch (function) { + case FUNC_EVERY_SECOND: + Tsl2591EverySecond(); + break; + case FUNC_JSON_APPEND: + Tsl2591Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Tsl2591Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_58_dht12.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_58_dht12.ino" +#ifdef USE_I2C +#ifdef USE_DHT12 + + + + + + +#define XSNS_58 58 +#define XI2C_41 41 + +#define DHT12_ADDR 0x5C + +struct DHT12 { + float temperature = NAN; + float humidity = NAN; + uint8_t valid = 0; + uint8_t count = 0; + char name[6] = "DHT12"; +} Dht12; + +bool Dht12Read(void) +{ + if (Dht12.valid) { Dht12.valid--; } + + Wire.beginTransmission(DHT12_ADDR); + Wire.write(0); + if (Wire.endTransmission() != 0) { return false; } + + delay(50); + + Wire.requestFrom(DHT12_ADDR, 5); + delay(5); + uint8_t humidity = Wire.read(); + uint8_t humidityTenth = Wire.read(); + uint8_t temp = Wire.read(); + uint8_t tempTenth = Wire.read(); + uint8_t checksum = Wire.read(); + + Dht12.humidity = ConvertHumidity( (float) humidity + (float) humidityTenth/(float) 10.0 ); + Dht12.temperature = ConvertTemp( (float) temp + (float) tempTenth/(float) 10.0 ); + + if (isnan(Dht12.temperature) || isnan(Dht12.humidity)) { return false; } + + Dht12.valid = SENSOR_MAX_MISS; + return true; +} + + + +void Dht12Detect(void) +{ + if (I2cActive(DHT12_ADDR)) { return; } + + if (Dht12Read()) { + I2cSetActiveFound(DHT12_ADDR, Dht12.name); + Dht12.count = 1; + } +} + +void Dht12EverySecond(void) +{ + if (uptime &1) { + + if (!Dht12Read()) { + AddLogMissed(Dht12.name, Dht12.valid); + } + } +} + +void Dht12Show(bool json) +{ + if (Dht12.valid) { + char temperature[33]; + dtostrfd(Dht12.temperature, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(Dht12.humidity, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, Dht12.name, temperature, humidity); +#ifdef USE_DOMOTICZ + if ((0 == tele_period)) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, Dht12.temperature); + KnxSensor(KNX_HUMIDITY, Dht12.humidity); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, Dht12.name, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, Dht12.name, humidity); +#endif + } + } +} + + + + + +bool Xsns58(uint8_t function) +{ + if (!I2cEnabled(XI2C_41)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Dht12Detect(); + } + else if (Dht12.count) { + switch (function) { + case FUNC_EVERY_SECOND: + Dht12EverySecond(); + break; + case FUNC_JSON_APPEND: + Dht12Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Dht12Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_59_ds1624.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_59_ds1624.ino" +#ifdef USE_I2C +#ifdef USE_DS1624 + + + + + + + +#define XSNS_59 59 +#define XI2C_42 42 + +#define DS1624_MEM_REGISTER 0x17 +#define DS1624_CONF_REGISTER 0xAC +#define DS1624_TEMP_REGISTER 0xAA +#define DS1624_START_REGISTER 0xEE +#define DS1624_STOP_REGISTER 0x22 + +#define DS1621_COUNTER_REGISTER 0xA8 +#define DS1621_SLOPE_REGISTER 0xA9 + +#define DS1621_CFG_1SHOT (1<<0) +#define DS1621_CFG_DONE (1<<7) + +enum { + DS1624_TYPE_DS1624, + DS1624_TYPE_DS1621 +}; + +#define DS1624_MAX_SENSORS 8 + +bool ds1624_init = false; + +struct { + float value; + uint8_t type; + int errcnt; + int misscnt; + bool valid; + char name[9]; +} ds1624_sns[DS1624_MAX_SENSORS]; + +uint32_t DS1624_Idx2Addr(uint32_t idx) { + return 0x48 + idx; +} + +int DS1624_Restart(uint8_t config, uint32_t idx) { + uint32_t addr = DS1624_Idx2Addr(idx); + if ((config & 1) == 1) { + config &= ~(DS1621_CFG_DONE|DS1621_CFG_1SHOT); + I2cWrite8(addr, DS1624_CONF_REGISTER, config); + delay(10); + AddLog_P2(LOG_LEVEL_ERROR, "%s addr %x is reset, reconfig: %x", ds1624_sns[idx].name, addr, config); + } + I2cValidRead(addr, DS1624_START_REGISTER, 1); +} + +void DS1624_HotPlugUp(uint32_t idx) +{ + uint32_t addr = DS1624_Idx2Addr(idx); + + if (I2cActive(addr)) { return; } + if (!I2cSetDevice(addr)) { return; } + + uint8_t config; + if (I2cValidRead8(&config, addr, DS1624_CONF_REGISTER)) { + uint8_t tmp; + ds1624_sns[idx].type = (I2cValidRead8(&tmp, addr, DS1624_MEM_REGISTER)) ? DS1624_TYPE_DS1624 : DS1624_TYPE_DS1621; + + snprintf_P(ds1624_sns[idx].name, sizeof(ds1624_sns[idx].name), PSTR("DS162%c%c%d"), + (ds1624_sns[idx].type == DS1624_TYPE_DS1621) ? '1' : '4', IndexSeparator(), idx); + I2cSetActiveFound(addr, ds1624_sns[idx].name); + + ds1624_sns[idx].valid = true; + ds1624_sns[idx].errcnt = 0; + ds1624_sns[idx].misscnt = 0; + DS1624_Restart(config,idx); + AddLog_P2(LOG_LEVEL_INFO, "Hot Plug %s addr %x config: %x", ds1624_sns[idx].name, addr, config); + } +} + +void DS1624_HotPlugDown(int idx) +{ + uint32_t addr = DS1624_Idx2Addr(idx); + if (!I2cActive(addr)) { return; } + I2cResetActive(addr); + ds1624_sns[idx].valid = false; + AddLog_P2(LOG_LEVEL_INFO, "Hot UnPlug %s", ds1624_sns[idx].name); +} + +bool DS1624GetTemp(float *value, int idx) +{ + uint32_t addr = DS1624_Idx2Addr(idx); + + uint8_t config; + if (!I2cValidRead8(&config, addr, DS1624_CONF_REGISTER)) { + ds1624_sns[idx].misscnt++; + AddLog_P2(LOG_LEVEL_INFO, "%s device missing (errors: %i)", ds1624_sns[idx].name, ds1624_sns[idx].misscnt); + return false; + } + ds1624_sns[idx].misscnt=0; + if (config & (DS1621_CFG_1SHOT|DS1621_CFG_DONE)) { + ds1624_sns[idx].errcnt++; + AddLog_P2(LOG_LEVEL_INFO, "%s config error, restart... (errors: %i)", ds1624_sns[idx].name, ds1624_sns[idx].errcnt); + DS1624_Restart(config, idx); + return false; + } + + uint16_t t; + if (!I2cValidRead16(&t, DS1624_Idx2Addr(idx), DS1624_TEMP_REGISTER)) { return false; } + if (ds1624_sns[idx].type == DS1624_TYPE_DS1624) { + *value = ((float)(int8_t)(t>>8)) + ((t>>4)&0xf)*0.0625; + } else { + + *value = ((float)(int8_t)(t>>8)); + uint8_t remain; + if (!I2cValidRead8(&remain, addr, DS1621_COUNTER_REGISTER)) { return true; } + uint8_t perc; + if (!I2cValidRead8(&perc, addr, DS1621_SLOPE_REGISTER)) { return true; } + float fix=(float)(perc - remain)/(float)perc; + *value+=fix; + } + ds1624_sns[idx].errcnt=0; + config &= ~(DS1621_CFG_DONE); + I2cWrite8(addr, DS1624_CONF_REGISTER, config); + return true; +} + +void DS1624HotPlugScan(void) +{ + uint16_t t; + + for (uint32_t idx = 0; idx < DS1624_MAX_SENSORS; idx++) { + uint32_t addr = DS1624_Idx2Addr(idx); + if (I2cActive(addr) && !ds1624_sns[idx].valid) { + continue; + } + if (ds1624_sns[idx].valid) { + if ((ds1624_sns[idx].misscnt>2)||(ds1624_sns[idx].errcnt>2)) { + DS1624_HotPlugDown(idx); + continue; + } + } + DS1624_HotPlugUp(idx); + } +} + +void DS1624EverySecond(void) +{ + float t; + for (uint32_t i = 0; i < DS1624_MAX_SENSORS; i++) { + if (!ds1624_sns[i].valid) { continue; } + if (!DS1624GetTemp(&t, i)) { continue; } + ds1624_sns[i].value = ConvertTemp(t); + } +} + +void DS1624Show(bool json) +{ + char temperature[33]; + bool once = true; + + for (uint32_t i = 0; i < DS1624_MAX_SENSORS; i++) { + if (!ds1624_sns[i].valid) { continue; } + + dtostrfd(ds1624_sns[i].value, Settings.flag2.temperature_resolution, temperature); + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}"), ds1624_sns[i].name, temperature); + if ((0 == tele_period) && once) { +#ifdef USE_DOMOTICZ + DomoticzSensor(DZ_TEMP, temperature); +#endif +#ifdef USE_KNX + KnxSensor(KNX_TEMPERATURE, temperature); +#endif + once = false; + } +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, ds1624_sns[i].name, temperature, TempUnit()); +#endif + } + } +} + + + + + +bool Xsns59(uint8_t function) +{ + if (!I2cEnabled(XI2C_42)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + if (!ds1624_init) { + memset(ds1624_sns, 0, sizeof(ds1624_sns)); + ds1624_init = true; + DS1624HotPlugScan(); + } + } + switch (function) { + case FUNC_HOTPLUG_SCAN: + DS1624HotPlugScan(); + break; + case FUNC_EVERY_SECOND: + DS1624EverySecond(); + break; + case FUNC_JSON_APPEND: + DS1624Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + DS1624Show(0); + break; +#endif + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_60_GPS.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_60_GPS.ino" +#ifdef USE_GPS +# 113 "S:/Development/Tasmota/tasmota/xsns_60_GPS.ino" +#define XSNS_60 60 + +#include "NTPServer.h" +#include "NTPPacket.h" + + + + + +#define D_CMND_UBX "UBX" + +const char S_JSON_UBX_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_UBX "%s\":%d}"; + +const char kUBXTypes[] PROGMEM = "UBX"; + +#define UBX_LAT_LON_THRESHOLD 1000 + +#define UBX_SERIAL_BUFFER_SIZE 256 +#define UBX_TCP_PORT 1234 + + + + + +const char UBLOX_INIT[] PROGMEM = { + + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x24, + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x2B, + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x02,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x32, + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x39, + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x04,0x00,0x00,0x00,0x00,0x00,0x01,0x04,0x40, + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x05,0x00,0x00,0x00,0x00,0x00,0x01,0x05,0x47, + + + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0xDC, + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0xB9, + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xC0, + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0x92, + + + + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x13,0xBE, + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x14,0xC5, + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x01,0x00,0x00,0x00,0x00,0x32,0x97, + + + + + + +}; + +char UBX_name[4]; + +struct UBX_t { + const char UBX_HEADER[2] = { 0xB5, 0x62 }; + const char NAV_POSLLH_HEADER[2] = { 0x01, 0x02 }; + const char NAV_STATUS_HEADER[2] = { 0x01, 0x03 }; + const char NAV_TIME_HEADER[2] = { 0x01, 0x21 }; + + struct entry_t { + int32_t lat; + int32_t lon; + uint32_t time; + }; + + union { + entry_t values; + uint8_t bytes[sizeof(entry_t)]; + } rec_buffer; + + struct POLL_MSG { + uint8_t cls; + uint8_t id; + uint16_t zero; + }; + + struct NAV_POSLLH { + uint8_t cls; + uint8_t id; + uint16_t len; + uint32_t iTOW; + int32_t lon; + int32_t lat; + int32_t alt; + int32_t hMSL; + uint32_t hAcc; + uint32_t vAcc; + }; + + struct NAV_STATUS { + uint8_t cls; + uint8_t id; + uint16_t len; + uint32_t iTOW; + uint8_t gpsFix; + uint8_t flags; + uint8_t fixStat; + uint8_t flags2; + uint32_t ttff; + uint32_t msss; + }; + + struct NAV_TIME_UTC { + uint8_t cls; + uint8_t id; + uint16_t len; + uint32_t iTOW; + uint32_t tAcc; + int32_t nano; + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + struct { + uint8_t UTC:1; + uint8_t WKN:1; + uint8_t TOW:1; + uint8_t padding:5; + } valid; + }; + + struct CFG_RATE { + uint8_t cls; + uint8_t id; + uint16_t len; + uint16_t measRate; + uint16_t navRate; + uint16_t timeRef; + char CK[2]; + }; + + struct { + uint32_t last_iTOW; + int32_t last_alt; + uint32_t last_hAcc; + uint32_t last_vAcc; + uint8_t gpsFix; + uint8_t non_empty_loops; + uint16_t log_interval; + } state; + + struct { + uint32_t init:1; + uint32_t filter_noise:1; + uint32_t send_when_new:1; + uint32_t send_UI_only:1; + uint32_t runningNTP:1; + uint32_t forceUTCupdate:1; + uint32_t runningVPort:1; + + } mode; + + union { + NAV_POSLLH navPosllh; + NAV_STATUS navStatus; + NAV_TIME_UTC navTime; + POLL_MSG pollMsg; + CFG_RATE cfgRate; + } Message; + + uint8_t TCPbuf[UBX_SERIAL_BUFFER_SIZE]; + size_t TCPbufSize; +} UBX; + +enum UBXMsgType { + MT_NONE, + MT_NAV_POSLLH, + MT_NAV_STATUS, + MT_NAV_TIME, + MT_POLL +}; + +#ifdef USE_FLOG +FLOG *Flog = nullptr; +#endif +TasmotaSerial *UBXSerial; + +NtpServer timeServer(PortUdp); + +WiFiServer vPortServer(UBX_TCP_PORT); +WiFiClient vPortClient; + + + + + +void UBXcalcChecksum(char* CK, size_t msgSize) +{ + memset(CK, 0, 2); + for (int i = 0; i < msgSize; i++) { + CK[0] += ((char*)(&UBX.Message))[i]; + CK[1] += CK[0]; + } +} + +bool UBXcompareMsgHeader(const char* msgHeader) +{ + char* ptr = (char*)(&UBX.Message); + return ptr[0] == msgHeader[0] && ptr[1] == msgHeader[1]; +} + +void UBXinitCFG(void) +{ + for (uint32_t i = 0; i < sizeof(UBLOX_INIT); i++) { + UBXSerial->write( pgm_read_byte(UBLOX_INIT+i) ); + } + DEBUG_SENSOR_LOG(PSTR("UBX: turn off NMEA")); +} + +void UBXTriggerTele(void) +{ + mqtt_data[0] = '\0'; + if (MqttShowSensor()) { + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); +#ifdef USE_RULES + RulesTeleperiod(); +#endif + } +} + + + +void UBXDetect(void) +{ + UBX.mode.init = 0; + if ((pin[GPIO_GPS_RX] < 99) && (pin[GPIO_GPS_TX] < 99)) { + UBXSerial = new TasmotaSerial(pin[GPIO_GPS_RX], pin[GPIO_GPS_TX], 1, 0, UBX_SERIAL_BUFFER_SIZE); + if (UBXSerial->begin(9600)) { + DEBUG_SENSOR_LOG(PSTR("UBX: started serial")); + if (UBXSerial->hardwareSerial()) { + ClaimSerial(); + DEBUG_SENSOR_LOG(PSTR("UBX: claim HW")); + } + } + } + else { + return; + } + + UBXinitCFG(); + UBX.mode.init = 1; + +#ifdef USE_FLOG + if (!Flog) { + Flog = new FLOG; + Flog->init(); + } +#endif + + UBX.state.log_interval = 10; + UBX.mode.send_UI_only = true; + UBXTriggerTele(); +} + +uint32_t UBXprocessGPS() +{ + static uint32_t fpos = 0; + static char checksum[2]; + static uint8_t currentMsgType = MT_NONE; + static size_t payloadSize = sizeof(UBX.Message); + + + uint32_t data_bytes = 0; + while ( UBXSerial->available() ) { + data_bytes++; + byte c = UBXSerial->read(); + if (UBX.mode.runningVPort){ + UBX.TCPbuf[data_bytes-1] = c; + UBX.TCPbufSize = data_bytes; + } + if ( fpos < 2 ) { + + if ( c == UBX.UBX_HEADER[fpos] ) { + fpos++; + } else { + fpos = 0; + } + } else { + + + + + + if ( (fpos-2) < payloadSize ) { + ((char*)(&UBX.Message))[fpos-2] = c; + } + fpos++; + + if ( fpos == 4 ) { + + + if ( UBXcompareMsgHeader(UBX.NAV_POSLLH_HEADER) ) { + currentMsgType = MT_NAV_POSLLH; + payloadSize = sizeof(UBX_t::NAV_POSLLH); + DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_POSLLH")); + } + else if ( UBXcompareMsgHeader(UBX.NAV_STATUS_HEADER) ) { + currentMsgType = MT_NAV_STATUS; + payloadSize = sizeof(UBX_t::NAV_STATUS); + DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_STATUS")); + } + else if ( UBXcompareMsgHeader(UBX.NAV_TIME_HEADER) ) { + currentMsgType = MT_NAV_TIME; + payloadSize = sizeof(UBX_t::NAV_TIME_UTC); + DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_TIME_UTC")); + } + else { + + fpos = 0; + continue; + } + } + + if ( fpos == (payloadSize+2) ) { + + + UBXcalcChecksum(checksum, payloadSize); + } + else if ( fpos == (payloadSize+3) ) { + + + if ( c != checksum[0] ) { + + fpos = 0; + } + } + else if ( fpos == (payloadSize+4) ) { + + + fpos = 0; + if ( c == checksum[1] ) { + + return currentMsgType; + } + } + else if ( fpos > (payloadSize+4) ) { + + + fpos = 0; + } + } + } + + if (data_bytes!=0) { + UBX.state.non_empty_loops++; + DEBUG_SENSOR_LOG(PSTR("UBX: got %u bytes, non-empty-loop: %u"), data_bytes, UBX.state.non_empty_loops); + } else { + UBX.state.non_empty_loops = 0; + } + return MT_NONE; +} + + + + + +#ifdef USE_FLOG +void UBXsendHeader(void) +{ + WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN); + WebServer->sendHeader(F("Content-Disposition"), F("attachment; filename=TASMOTA.gpx")); + WSSend(200, CT_STREAM, F( + "\r\n" + "\r\n" + "\r\n\r\n")); +} + +void UBXsendRecord(uint8_t *buf) +{ + char record[100]; + char stime[32]; + UBX_t::entry_t *entry = (UBX_t::entry_t*)buf; + snprintf_P(stime, sizeof(stime), GetDT(entry->time).c_str()); + char lat[12]; + char lon[12]; + dtostrfd((double)entry->lat/10000000.0f,7,lat); + dtostrfd((double)entry->lon/10000000.0f,7,lon); + snprintf_P(record, sizeof(record),PSTR("\n\t\n\n"),lat ,lon, stime); + + WebServer->sendContent_P(record); +} + +void UBXsendFooter(void) +{ + WebServer->sendContent(F("\n\n")); + WebServer->sendContent(""); + Rtc.user_time_entry = false; +} + + + +void UBXsendFile(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + Flog->startDownload(sizeof(UBX.rec_buffer),UBXsendHeader,UBXsendRecord,UBXsendFooter); +} +#endif + + + +void UBXSetRate(uint16_t interval) +{ + UBX.Message.cfgRate.cls = 0x06; + UBX.Message.cfgRate.id = 0x08; + UBX.Message.cfgRate.len = 6; + uint32_t measRate = (1000*(uint32_t)interval); + if (measRate > 0xffff) { + measRate = 0xffff; + } + UBX.Message.cfgRate.measRate = (uint16_t)measRate; + UBX.Message.cfgRate.navRate = 1; + UBX.Message.cfgRate.timeRef = 1; + UBXcalcChecksum(UBX.Message.cfgRate.CK, sizeof(UBX.Message.cfgRate)-sizeof(UBX.Message.cfgRate.CK)); + DEBUG_SENSOR_LOG(PSTR("UBX: requested interval: %u seconds measRate: %u ms"), interval, UBX.Message.cfgRate.measRate); + UBXSerial->write(UBX.UBX_HEADER[0]); + UBXSerial->write(UBX.UBX_HEADER[1]); + for (uint32_t i =0; iwrite(((uint8_t*)(&UBX.Message.cfgRate))[i]); + DEBUG_SENSOR_LOG(PSTR("UBX: cfgRate byte %u: %x"), i, ((uint8_t*)(&UBX.Message.cfgRate))[i]); + } + UBX.state.log_interval = 10*interval; +} + +void UBXSelectMode(uint16_t mode) +{ + DEBUG_SENSOR_LOG(PSTR("UBX: set mode to %u"),mode); + switch(mode){ +#ifdef USE_FLOG + case 0: + Flog->mode = 0; + break; + case 1: + Flog->mode = 1; + break; + case 2: + UBX.mode.filter_noise = true; + break; + case 3: + UBX.mode.filter_noise = false; + break; + case 4: + Flog->startRecording(true); + AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - appending")); + break; + case 5: + Flog->startRecording(false); + AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - new log")); + break; + case 6: + if(Flog->recording == true){ + Flog->stopRecording(); + } + AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: stop recording")); + break; +#endif + case 7: + UBX.mode.send_when_new = 1; + break; + case 8: + UBX.mode.send_when_new = 0; + break; + case 9: + if (timeServer.beginListening()) { + UBX.mode.runningNTP = true; + } + break; + case 10: + UBX.mode.runningNTP = false; + break; + case 11: + UBX.mode.forceUTCupdate = true; + break; + case 12: + UBX.mode.forceUTCupdate = false; + break; + case 13: + Settings.latitude = UBX.rec_buffer.values.lat/10; + Settings.longitude = UBX.rec_buffer.values.lon/10; + break; + case 14: + vPortServer.begin(); + UBX.mode.runningVPort = 1; + break; + case 15: + + UBX.mode.runningVPort = 0; + break; + default: + if (mode>1000 && mode <1066) { + + UBXSetRate(mode-1000); + } + break; + } + UBX.mode.send_UI_only = true; + UBXTriggerTele(); +} + + + +bool UBXHandlePOSLLH() +{ + DEBUG_SENSOR_LOG(PSTR("UBX: iTOW: %u"),UBX.Message.navPosllh.iTOW); + if (UBX.state.gpsFix>1) { + if (UBX.mode.filter_noise) { + if ((UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat6) { + if(UBX.mode.runningVPort) return; + UBXinitCFG(); + AddLog_P(LOG_LEVEL_ERROR, PSTR("UBX: possible device-reset, will re-init")); + UBXSerial->flush(); + UBX.state.non_empty_loops = 0; + } +} + + + +void UBXLoop50msec(void) +{ + + if (UBX.mode.runningVPort){ + if(!vPortClient.connected()) { + vPortClient = vPortServer.available(); + } + while(vPortClient.available()) { + byte _newByte = vPortClient.read(); + UBXSerial->write(_newByte); + } + + if (UBX.TCPbufSize!=0){ + vPortClient.write((char*)UBX.TCPbuf, UBX.TCPbufSize); + UBX.TCPbufSize = 0; + } + } + + if(UBX.mode.runningNTP){ + timeServer.processOneRequest(Rtc.utc_time, UBX.state.last_iTOW%1000); + } +} + +void UBXLoop(void) +{ + static uint16_t counter; + static bool new_position; + + uint32_t msgType = UBXprocessGPS(); + + switch(msgType){ + case MT_NAV_POSLLH: + new_position = UBXHandlePOSLLH(); + break; + case MT_NAV_STATUS: + UBXHandleSTATUS(); + break; + case MT_NAV_TIME: + UBXHandleTIME(); + break; + default: + UBXHandleOther(); + break; + } + +#ifdef USE_FLOG + if (counter>UBX.state.log_interval) { + if (Flog->recording && new_position) { + UBX.rec_buffer.values.time = Rtc.local_time; + Flog->addToBuffer(UBX.rec_buffer.bytes, sizeof(UBX.rec_buffer.bytes)); + counter = 0; + } + } +#endif + + counter++; +} + + + + +#ifdef USE_WEBSERVER + + +#ifdef USE_FLOG +#ifdef DEBUG_TASMOTA_SENSOR + const char HTTP_SNS_FLOGVER[] PROGMEM = "{s}
{m}
{e}{s} FLOG with %u sectors: {m}%u bytes{e}" + "{s} FLOG next sector for REC: {m} %u {e}" + "{s} %u sector(s) with data at sector: {m} %u {e}"; + const char HTTP_SNS_FLOGREC[] PROGMEM = "{s} RECORDING (bytes in buffer) {m}%u{e}"; +#endif + + const char HTTP_SNS_FLOG[] PROGMEM = "{s}
{m}
{e}{s} Flash-Log {m} %s{e}"; + const char kFLOG_STATE0[] PROGMEM = "ready"; + const char kFLOG_STATE1[] PROGMEM = "recording"; + const char * kFLOG_STATE[] ={kFLOG_STATE0, kFLOG_STATE1}; + + const char HTTP_BTN_FLOG_DL[] PROGMEM = ""; + +#endif + const char HTTP_SNS_NTPSERVER[] PROGMEM = "{s} NTP server {m}active{e}"; + + const char HTTP_SNS_GPS[] PROGMEM = "{s} GPS latitude {m}%s{e}" + "{s} GPS longitude {m}%s{e}" + "{s} GPS altitude {m}%s m{e}" + "{s} GPS hor. Accuracy {m}%s m{e}" + "{s} GPS vert. Accuracy {m}%s m{e}" + "{s} GPS sat-fix status {m}%s{e}"; + + const char kGPSFix0[] PROGMEM = "no fix"; + const char kGPSFix1[] PROGMEM = "dead reckoning only"; + const char kGPSFix2[] PROGMEM = "2D-fix"; + const char kGPSFix3[] PROGMEM = "3D-fix"; + const char kGPSFix4[] PROGMEM = "GPS + dead reckoning combined"; + const char kGPSFix5[] PROGMEM = "Time only fix"; + const char * kGPSFix[] PROGMEM ={kGPSFix0, kGPSFix1, kGPSFix2, kGPSFix3, kGPSFix4, kGPSFix5}; + + + + +#endif + + + +void UBXShow(bool json) +{ + char lat[12]; + char lon[12]; + char alt[12]; + char hAcc[12]; + char vAcc[12]; + dtostrfd((double)UBX.rec_buffer.values.lat/10000000.0f,7,lat); + dtostrfd((double)UBX.rec_buffer.values.lon/10000000.0f,7,lon); + dtostrfd((double)UBX.state.last_alt/1000.0f,3,alt); + dtostrfd((double)UBX.state.last_vAcc/1000.0f,3,hAcc); + dtostrfd((double)UBX.state.last_hAcc/1000.0f,3,vAcc); + + if (json) { + ResponseAppend_P(PSTR(",\"GPS\":{")); + if (UBX.mode.send_UI_only) { + uint32_t i = UBX.state.log_interval / 10; + ResponseAppend_P(PSTR("\"fil\":%u,\"int\":%u}"), UBX.mode.filter_noise, i); + } else { + ResponseAppend_P(PSTR("\"lat\":%s,\"lon\":%s,\"alt\":%s,\"hAcc\":%s,\"vAcc\":%s}"), lat, lon, alt, hAcc, vAcc); + } +#ifdef USE_FLOG + ResponseAppend_P(PSTR(",\"FLOG\":{\"rec\":%u,\"mode\":%u,\"sec\":%u}"), Flog->recording, Flog->mode, Flog->sectors_left); +#endif + UBX.mode.send_UI_only = false; +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_GPS, lat, lon, alt, hAcc, vAcc, kGPSFix[UBX.state.gpsFix]); + +#ifdef DEBUG_TASMOTA_SENSOR +#ifdef USE_FLOG + WSContentSend_PD(HTTP_SNS_FLOGVER, Flog->num_sectors, Flog->size, Flog->current_sector, Flog->sectors_left, Flog->sector.header.physical_start_sector); + if (Flog->recording) { + WSContentSend_PD(HTTP_SNS_FLOGREC, Flog->sector.header.buf_pointer - 8); + } +#endif +#endif +#ifdef USE_FLOG + if (Flog->ready) { + WSContentSend_P(HTTP_SNS_FLOG,kFLOG_STATE[Flog->recording]); + } + if (!Flog->recording && Flog->found_saved_data) { + WSContentSend_P(HTTP_BTN_FLOG_DL); + } +#endif + if (UBX.mode.runningNTP) { + WSContentSend_P(HTTP_SNS_NTPSERVER); + } +#endif + } +} + + + + + +bool UBXCmd(void) +{ + bool serviced = true; + if (XdrvMailbox.data_len > 0) { + UBXSelectMode(XdrvMailbox.payload); + Response_P(S_JSON_UBX_COMMAND_NVALUE, XdrvMailbox.command, XdrvMailbox.payload); + } + return serviced; +} + + + + + +bool Xsns60(uint8_t function) +{ + bool result = false; + + if (FUNC_INIT == function) { + UBXDetect(); + } + + if (UBX.mode.init) { + switch (function) { + case FUNC_COMMAND_SENSOR: + if (XSNS_60 == XdrvMailbox.index) { + result = UBXCmd(); + } + break; + case FUNC_EVERY_50_MSECOND: + UBXLoop50msec(); + break; + case FUNC_EVERY_100_MSECOND: +#ifdef USE_FLOG + if (!Flog->running_download) +#endif + { + UBXLoop(); + } + break; +#ifdef USE_FLOG + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/UBX", UBXsendFile); + break; +#endif + case FUNC_JSON_APPEND: + UBXShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: +#ifdef USE_FLOG + if (!Flog->running_download) +#endif + { + UBXShow(0); + } + break; +#endif + } + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_61_MI_NRF24.ino" +# 38 "S:/Development/Tasmota/tasmota/xsns_61_MI_NRF24.ino" +#ifdef USE_SPI +#ifdef USE_NRF24 +#ifdef USE_MIBLE + +#ifdef DEBUG_TASMOTA_SENSOR + #define MINRF_LOG_BUFFER(x) MINRFshowBuffer(x); +#else + #define MINRF_LOG_BUFFER(x) +#endif +# 56 "S:/Development/Tasmota/tasmota/xsns_61_MI_NRF24.ino" +#define XSNS_61 61 + +#include + +#define FLORA 1 +#define MJ_HT_V1 2 +#define LYWSD02 3 +#define LYWSD03 4 +#define CGG1 5 + +const uint16_t kMINRFSlaveID[5]={ 0x0098, + 0x01aa, + 0x045b, + 0x055b, + 0x0347 + }; + +const char kMINRFSlaveType1[] PROGMEM = "Flora"; +const char kMINRFSlaveType2[] PROGMEM = "MJ_HT_V1"; +const char kMINRFSlaveType3[] PROGMEM = "LYWSD02"; +const char kMINRFSlaveType4[] PROGMEM = "LYWSD03"; +const char kMINRFSlaveType5[] PROGMEM = "CGG1"; +const char * kMINRFSlaveType[] PROGMEM = {kMINRFSlaveType1,kMINRFSlaveType2,kMINRFSlaveType3,kMINRFSlaveType4,kMINRFSlaveType5}; + + +const uint32_t kMINRFFloPDU[3] = {0x3eaa857d,0xef3b8730,0x71da7b46}; +const uint32_t kMINRFMJPDU[3] = {0x4760cd66,0xdbcc0cd3,0x33048df5}; +const uint32_t kMINRFL2PDU[3] = {0x3eaa057d,0xef3b0730,0x71da7646}; + +const uint32_t kMINRFL3PDU[3] = {0x4760cb78,0xdbcc0acd,0x33048beb}; +const uint32_t kMINRFCGPDU[3] = {0x4760cd6e,0xdbcc0cdb,0x33048dfd}; + + +const uint8_t kMINRFlsfrList_A[3] = {0x4b,0x17,0x23}; +const uint8_t kMINRFlsfrList_B[3] = {0x21,0x72,0x43}; + + +#pragma pack(1) +struct MJ_HT_V1Header_t { + uint8_t padding[3]; + uint8_t mesSize; + uint8_t padding2; + uint16_t uuid; + uint16_t type; + uint8_t padding3[2]; + uint8_t counter; + uint8_t serial[6]; + uint8_t mode; + uint8_t padding5; + uint8_t effectiveDataLength; + }; + +struct FlowerHeader_t { + uint8_t padding[4]; + uint8_t padding2; + uint16_t uuid; + uint8_t mesSize; + uint8_t padding22; + uint16_t uuid2; + uint16_t type; + uint8_t padding3[2]; + uint8_t counter; + uint8_t serial[6]; + uint8_t padding4; + uint8_t mode; + }; + +union floraPacket_t { + struct { + uint16_t idWord; + uint8_t padding; + uint8_t serial[6]; + uint8_t padding4; + uint8_t mode; + uint8_t valueTen; + uint8_t effectiveDataLength; + uint16_t data; + } T; + struct { + uint16_t idWord; + uint8_t padding; + uint8_t serial[6]; + uint8_t padding4; + uint8_t mode; + uint8_t valueTen; + uint8_t effectiveDataLength; + uint32_t data:24; + } L; + struct { + uint8_t padding[3]; + uint8_t serial[6]; + uint8_t padding4; + uint8_t mode; + uint8_t valueTen; + uint8_t effectiveDataLength; + uint8_t data; + } M; + struct { + uint8_t padding[3]; + uint8_t serial[6]; + uint8_t padding4; + uint8_t mode; + uint8_t valueTen; + uint8_t effectiveDataLength; + uint16_t data; + } F; +}; + +union MJ_HT_V1Packet_t { + struct { + uint16_t idWord; + uint8_t padding; + uint8_t serial[6]; + uint8_t mode; + uint8_t valueTen; + uint8_t effectiveDataLength; + uint16_t temp; + uint16_t hum; + } TH; + struct { + uint8_t padding[3]; + uint8_t serial[6]; + uint8_t mode; + uint8_t valueTen; + uint8_t effectiveDataLength; + uint8_t battery; + } B; + +}; + +union LYWSD02Packet_t { + struct { + uint16_t idWord; + uint8_t padding; + uint8_t serial[6]; + uint8_t padding4; + uint8_t mode; + uint8_t valueTen; + uint8_t effectiveDataLength; + uint16_t data; + } TH; +}; + +struct bleAdvPacket_t { + uint8_t pduType; + uint8_t payloadSize; + uint8_t mac[6]; + union { + uint8_t payload[24]; + MJ_HT_V1Header_t header; + FlowerHeader_t flowerHeader; + struct { + uint8_t padding[21]; + uint16_t temp; + uint8_t hum_lb; + } TH; + struct { + uint8_t padding[21]; + uint16_t temp; + } T; + struct { + uint8_t padding[21]; + uint16_t hum; + } H; + struct { + uint8_t padding[21]; + uint8_t battery; + } B; + struct { + uint8_t padding[2]; + uint8_t mode; + uint16_t size; + uint16_t data; + } F_T; + struct { + uint8_t padding[2]; + uint8_t mode; + uint16_t size; + uint16_t data; + uint8_t data2; + } F_L; + struct { + uint8_t padding[2]; + uint8_t mode; + uint16_t size; + uint8_t data; + } F_M; + struct { + uint8_t padding[2]; + uint8_t mode; + uint16_t size; + uint16_t data; + } F_F; + }; +}; + +union FIFO_t{ + bleAdvPacket_t bleAdv; + floraPacket_t floraPacket; + MJ_HT_V1Packet_t MJ_HT_V1Packet; + LYWSD02Packet_t LYWSD02Packet; + uint8_t raw[32]; +}; + +#pragma pack(0) + +struct { + const uint8_t channel[3] = {37,38,39}; + const uint8_t frequency[3] = { 2,26,80}; + + uint16_t timer; + uint8_t currentChan=0; + FIFO_t buffer; + uint8_t packetMode; + +#ifdef DEBUG_TASMOTA_SENSOR + uint8_t streamBuffer[sizeof(buffer)]; + uint8_t lsfrBuffer[sizeof(buffer)]; +#endif + +} MINRF; + +struct mi_sensor_t{ + uint8_t type; + uint8_t serial[6]; + uint8_t showedUp; + float temp; + union { + struct { + float moisture; + float fertility; + uint32_t lux; + }; + struct { + float hum; + uint8_t bat; + }; + }; +}; + +std::vector MIBLEsensors; + + + + +bool MINRFinitBLE(uint8_t _mode) +{ + if (MINRF.timer%1000 == 0){ + NRF24radio.begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_DC]); + NRF24radio.setAutoAck(false); + NRF24radio.setDataRate(RF24_1MBPS); + NRF24radio.disableCRC(); + NRF24radio.setChannel( MINRF.frequency[MINRF.currentChan] ); + NRF24radio.setRetries(0,0); + NRF24radio.setPALevel(RF24_PA_MIN); + NRF24radio.setAddressWidth(4); + + + NRF24radio.powerUp(); + } + if(NRF24radio.isChipConnected()){ + + MINRFchangePacketModeTo(_mode); + return true; + } + + return false; +} + +void MINRFhopChannel() +{ + MINRF.currentChan++; + if(MINRF.currentChan >= sizeof(MINRF.channel)) { + MINRF.currentChan = 0; + } + NRF24radio.setChannel( MINRF.frequency[MINRF.currentChan] ); +} + + + + + + + +bool MINRFreceivePacket(void) +{ + if(!NRF24radio.available()) { + return false; + } + while(NRF24radio.available()) { + + + NRF24radio.read( &MINRF.buffer, sizeof(MINRF.buffer) ); +#ifdef DEBUG_TASMOTA_SENSOR + memcpy(&MINRF.streamBuffer, &MINRF.buffer, sizeof(MINRF.buffer)); +#endif + MINRFswapbuf( sizeof(MINRF.buffer) ); + + + + switch (MINRF.packetMode) { + case 0: + MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), MINRF.channel[MINRF.currentChan] | 0x40); + break; + case 1: + MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_A[MINRF.currentChan]); + break; + case 2: + MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); + break; + case 3: + MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_A[MINRF.currentChan]); + break; + case 4: + MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); + break; + case 5: + MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); + break; + } + + + } + + return true; +} + +#ifdef DEBUG_TASMOTA_SENSOR +void MINRFshowBuffer(uint8_t (&buf)[32]){ + + + + + DEBUG_SENSOR_LOG(PSTR("MINRF: Buffer: %02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x ") + ,buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9],buf[10],buf[11], + buf[12],buf[13],buf[14],buf[15],buf[16],buf[17],buf[18],buf[19],buf[20],buf[21],buf[22],buf[23], + buf[24],buf[25],buf[26],buf[27],buf[28],buf[29],buf[30],buf[31] + ); +} +#endif + + + + + + +void MINRFswapbuf(uint8_t len) +{ + uint8_t* buf = (uint8_t*)&MINRF.buffer; + while(len--) { + uint8_t a = *buf; + uint8_t v = 0; + if (a & 0x80) v |= 0x01; + if (a & 0x40) v |= 0x02; + if (a & 0x20) v |= 0x04; + if (a & 0x10) v |= 0x08; + if (a & 0x08) v |= 0x10; + if (a & 0x04) v |= 0x20; + if (a & 0x02) v |= 0x40; + if (a & 0x01) v |= 0x80; + *(buf++) = v; + } +} +# 430 "S:/Development/Tasmota/tasmota/xsns_61_MI_NRF24.ino" +void MINRFwhiten(uint8_t *buf, uint8_t len, uint8_t lfsr) +{ + while(len--) { + uint8_t res = 0; + + for (uint8_t i = 1; i; i <<= 1) { + if (lfsr & 0x01) { + lfsr ^= 0x88; + res |= i; + } + lfsr >>= 1; + } + *(buf++) ^= res; +#ifdef DEBUG_TASMOTA_SENSOR + MINRF.lsfrBuffer[31-len] = lfsr; +#endif + } +} + +void MINRFreverseMAC(uint8_t _mac[]){ + uint8_t _reversedMAC[6]; + for (uint8_t i=0; i<6; i++){ + _reversedMAC[5-i] = _mac[i]; + } + memcpy(_mac,_reversedMAC, sizeof(_reversedMAC)); +} + + + + + + +void MINRFchangePacketModeTo(uint8_t _mode) { + uint32_t (_nextchannel) = MINRF.currentChan+1; + if (_nextchannel>2) _nextchannel=0; + + switch(_mode){ + case 0: + NRF24radio.openReadingPipe(0,0x6B7D9171); + break; + case 1: + NRF24radio.openReadingPipe(0,kMINRFFloPDU[_nextchannel]); + break; + case 2: + NRF24radio.openReadingPipe(0,kMINRFMJPDU[_nextchannel]); + break; + case 3: + NRF24radio.openReadingPipe(0,kMINRFL2PDU[_nextchannel]); + break; + case 4: + if(kMINRFL3PDU[_nextchannel]==0xffffffff) break; + NRF24radio.openReadingPipe(0,kMINRFL3PDU[_nextchannel]); + break; + case 5: + NRF24radio.openReadingPipe(0,kMINRFCGPDU[_nextchannel]); + break; + } + + MINRF.packetMode = _mode; +} +# 498 "S:/Development/Tasmota/tasmota/xsns_61_MI_NRF24.ino" +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(!_success) return 0xff; + + DEBUG_SENSOR_LOG(PSTR("MINRF: vector size %u"), MIBLEsensors.size()); + for(uint32_t i=0; i6000){ + DEBUG_SENSOR_LOG(PSTR("MINRF: check for FAKE sensors")); + MINRFpurgeFakeSensors(); + MINRF.timer=0; + } + MINRF.timer++; + + if (!MINRFreceivePacket()){ + + } + + else if(MINRF.buffer.bleAdv.header.uuid==0xfe95){ + MINRF_LOG_BUFFER(MINRF.streamBuffer); + MINRF_LOG_BUFFER(MINRF.lsfrBuffer); + MINRF_LOG_BUFFER(MINRF.buffer.raw); + DEBUG_SENSOR_LOG(PSTR("MINRF: Type: %x"), MINRF.buffer.bleAdv.header.type); + switch(MINRF.buffer.bleAdv.header.type){ + case 0x2050: + DEBUG_SENSOR_LOG(PSTR("MINRF: MJ_HT_V1 Packet")); + break; + case 0x1613:case 0x1614:case 0x1615: + DEBUG_SENSOR_LOG(PSTR("MINRF: Flora Packet")); + break; + default: + DEBUG_SENSOR_LOG(PSTR("MINRF: unknown Packet")); + break; + } + } + else if (MINRF.packetMode == FLORA){ + MINRFhandleFloraPacket(); + } + else if (MINRF.packetMode == MJ_HT_V1){ + MINRFhandleMJ_HT_V1Packet(); + } + else if (MINRF.packetMode == LYWSD02){ + MINRFhandleLYWSD02Packet(); + } + else if (MINRF.packetMode == LYWSD03){ + MINRFhandleLYWSD03Packet(); + } + else if (MINRF.packetMode == CGG1){ + MINRFhandleCGG1Packet(); + } + if (MINRF.packetMode == CGG1){ + MINRFinitBLE(1); + } + else { + MINRFinitBLE(++MINRF.packetMode); + } + + MINRFhopChannel(); + NRF24radio.startListening(); +} + + + + +const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u%%{e}"; +const char HTTP_MINRF_MAC[] PROGMEM = "{s}%s %s{m}%02x:%02x:%02x:%02x:%02x:%02x%{e}"; +const char HTTP_MINRF_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%sus/cm{e}"; +const char HTTP_MINRF_HL[] PROGMEM = "{s}
{m}
{e}"; + +void MINRFShow(bool json) +{ + if (json) { + for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { + if(MIBLEsensors.at(i).showedUp < 3){ + DEBUG_SENSOR_LOG(PSTR("MINRF: sensor not fully registered yet")); + break; + } + char slave[33]; + sprintf_P(slave,"%s-%02x%02x%02x",kMINRFSlaveType[MIBLEsensors.at(i).type-1],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]); + char temperature[33]; + dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature); + + ResponseAppend_P(PSTR(",\"%s\":{"),slave); + if(MIBLEsensors.at(i).temp!=-1000.0f){ + ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s"), temperature); + } + if (MIBLEsensors.at(i).type==FLORA){ + char lux[33]; + char moisture[33]; + char fertility[33]; + dtostrfd((float)MIBLEsensors.at(i).lux, 0, lux); + dtostrfd(MIBLEsensors.at(i).moisture, 0, moisture); + dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility); + if(MIBLEsensors.at(i).lux!=0xffff){ + ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":%s"), lux); + } + if(MIBLEsensors.at(i).moisture!=-1000.0f){ + ResponseAppend_P(PSTR(",\"" D_JSON_MOISTURE "\":%s"), moisture); + } + if(MIBLEsensors.at(i).fertility!=-1000.0f){ + ResponseAppend_P(PSTR(",\"Fertility\":%s"), fertility); + } + } + if (MIBLEsensors.at(i).type>FLORA){ + char humidity[33]; + dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity); + if(MIBLEsensors.at(i).hum!=-1.0f){ + ResponseAppend_P(PSTR(",\"" D_JSON_HUMIDITY "\":%s"), humidity); + } + if(MIBLEsensors.at(i).bat!=0xff){ + ResponseAppend_P(PSTR(",\"Battery\":%u"), MIBLEsensors.at(i).bat); + } + } + ResponseAppend_P(PSTR("}")); + } +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_NRF24, NRF24type, NRF24.chipType); + for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { + if(MIBLEsensors.at(i).showedUp < 3){ + DEBUG_SENSOR_LOG(PSTR("MINRF: sensor not fully registered yet")); + break; + } + WSContentSend_PD(HTTP_MINRF_HL); + WSContentSend_PD(HTTP_MINRF_MAC, kMINRFSlaveType[MIBLEsensors.at(i).type-1], D_MAC_ADDRESS, MIBLEsensors.at(i).serial[0], MIBLEsensors.at(i).serial[1],MIBLEsensors.at(i).serial[2],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]); + if(MIBLEsensors.at(i).temp!=-1000.0f){ + char temperature[33]; + dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature); + WSContentSend_PD(HTTP_SNS_TEMP, kMINRFSlaveType[MIBLEsensors.at(i).type-1], temperature, TempUnit()); + } + if (MIBLEsensors.at(i).type==FLORA){ + if(MIBLEsensors.at(i).lux!=0x00ffffff){ + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kMINRFSlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).lux); + } + if(MIBLEsensors.at(i).moisture!=-1000.0f){ + WSContentSend_PD(HTTP_SNS_MOISTURE, kMINRFSlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).moisture); + } + if(MIBLEsensors.at(i).fertility!=-1000.0f){ + char fertility[33]; + dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility); + WSContentSend_PD(HTTP_MINRF_FLORA_DATA, kMINRFSlaveType[MIBLEsensors.at(i).type-1], fertility); + } + } + if (MIBLEsensors.at(i).type>FLORA){ + if(MIBLEsensors.at(i).hum!=-1.0f){ + char humidity[33]; + dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity); + WSContentSend_PD(HTTP_SNS_HUM, kMINRFSlaveType[MIBLEsensors.at(i).type-1], humidity); + } + if(MIBLEsensors.at(i).bat!=0xff){ + WSContentSend_PD(HTTP_BATTERY, kMINRFSlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).bat); + } + } + } +#endif + } +} + + + + + +bool Xsns61(uint8_t function) +{ + bool result = false; + + if (NRF24.chipType) { + switch (function) { + case FUNC_INIT: + MINRFinitBLE(1); + AddLog_P2(LOG_LEVEL_INFO,PSTR("MINRF: started")); + break; + case FUNC_EVERY_50_MSECOND: + MINRF_EVERY_50_MSECOND(); + break; + case FUNC_JSON_APPEND: + MINRFShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MINRFShow(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_62_MI_HM10.ino" +# 30 "S:/Development/Tasmota/tasmota/xsns_62_MI_HM10.ino" +#ifdef USE_HM10 + +#define XSNS_62 62 + +#include +#include + +TasmotaSerial *HM10Serial; +#define HM10_BAUDRATE 115200 + +#define HM10_MAX_TASK_NUMBER 12 +uint8_t HM10_TASK_LIST[HM10_MAX_TASK_NUMBER+1][2]; + +#define HM10_MAX_RX_BUF 512 +char HM10_RX_STRING[HM10_MAX_RX_BUF] = {0}; + +struct { + uint8_t current_task_delay; + uint8_t last_command; + uint16_t firmware; + uint32_t period; + uint32_t serialSpeed; + union { + uint32_t time; + uint8_t timebuf[4]; + }; + struct { + uint32_t init:1; + uint32_t pending_task:1; + uint32_t connected:1; + uint32_t subscribed:1; + uint32_t awaitingHT:1; + uint32_t awaitingB:1; + + } mode; + struct { + uint8_t sensor; + + } state; +} HM10; + +#pragma pack(1) +struct { + uint16_t temp; + uint8_t hum; +} LYWSD0x_HT; +#pragma pack(0) + +struct mi_sensor_t{ + uint8_t type; + uint8_t serial[6]; + uint8_t showedUp; + float temp; + union { + struct { + float moisture; + float fertility; + uint16_t lux; + }; + struct { + float hum; + uint8_t bat; + }; + }; +}; + +std::vector MIBLEsensors; + + + + + +#define D_CMND_HM10 "HM10" + +const char S_JSON_HM10_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_HM10 "%s\":%d}"; +const char S_JSON_HM10_COMMAND[] PROGMEM = "{\"" D_CMND_HM10 "%s%s\"}"; +const char kHM10_Commands[] PROGMEM = "Scan|AT|Period|Baud|Time"; + +#define FLORA 1 +#define MJ_HT_V1 2 +#define LYWSD02 3 +#define LYWSD03MMC 4 + +uint8_t kHM10SlaveID[4][3] = { 0xC4,0x7C,0x8D, + 0x58,0x2D,0x34, + 0xE7,0x2E,0x00, + 0xA4,0xC1,0x38, + }; + +const char kHM10SlaveType1[] PROGMEM = "Flora"; +const char kHM10SlaveType2[] PROGMEM = "MJ_HT_V1"; +const char kHM10SlaveType3[] PROGMEM = "LYWSD02"; +const char kHM10SlaveType4[] PROGMEM = "LYWSD03"; +const char * kHM10SlaveType[] PROGMEM = {kHM10SlaveType1,kHM10SlaveType2,kHM10SlaveType3,kHM10SlaveType4}; + + + + + +enum HM10_Commands { + CMND_HM10_DISC_SCAN, + CMND_HM10_AT, + CMND_HM10_PERIOD, + CMND_HM10_BAUD, + CMND_HM10_TIME + }; + + + + + +#define TASK_HM10_NOTASK 0 +#define TASK_HM10_ROLE1 1 +#define TASK_HM10_IMME1 2 +#define TASK_HM10_RENEW 3 +#define TASK_HM10_RESET 4 +#define TASK_HM10_DISC 5 +#define TASK_HM10_CONN 6 +#define TASK_HM10_VERSION 7 +#define TASK_HM10_NAME 8 +#define TASK_HM10_FEEDBACK 9 +#define TASK_HM10_DISCONN 10 +#define TASK_HM10_SUB_L3 11 +#define TASK_HM10_READ_HT 12 +#define TASK_HM10_FINDALLCHARS 13 +#define TASK_HM10_UN_L3 14 +#define TASK_HM10_DELAY_SUB 15 +#define TASK_HM10_READ_BT_L3 16 +#define TASK_HM10_SUB_L2 17 +#define TASK_HM10_UN_L2 18 +#define TASK_HM10_READ_BT_L2 19 +#define TASK_HM10_TIME_L2 20 + +#define TASK_HM10_DONE 99 + + + + + +void HM10_Launchtask(uint8_t task, uint8_t slot, uint8_t delay){ + HM10_TASK_LIST[slot][0] = task; + HM10_TASK_LIST[slot][1] = delay; + HM10_TASK_LIST[slot+1][0] = TASK_HM10_NOTASK; + HM10.current_task_delay = HM10_TASK_LIST[0][1]; +} + +void HM10_TaskReplaceInSlot(uint8_t task, uint8_t slot){ + HM10.last_command = HM10_TASK_LIST[slot][0]; + HM10_TASK_LIST[slot][0] = task; +} + + + + + +void HM10_Reset(void) { HM10_Launchtask(TASK_HM10_DISCONN,0,1); + HM10_Launchtask(TASK_HM10_ROLE1,1,1); + HM10_Launchtask(TASK_HM10_IMME1,2,1); + HM10_Launchtask(TASK_HM10_RESET,3,1); + HM10_Launchtask(TASK_HM10_VERSION,4,10); + HM10_Launchtask(TASK_HM10_DISC,5,50); + } + +void HM10_Discovery_Scan(void) { + HM10_Launchtask(TASK_HM10_DISCONN,0,1); + HM10_Launchtask(TASK_HM10_DISC,1,1); + } + +void HM10_Read_LYWSD03(void) { + HM10_Launchtask(TASK_HM10_CONN,0,1); + HM10_Launchtask(TASK_HM10_FEEDBACK,1,35); + HM10_Launchtask(TASK_HM10_SUB_L3,2,20); + HM10_Launchtask(TASK_HM10_UN_L3,3,80); + HM10_Launchtask(TASK_HM10_READ_BT_L3,4,5); + HM10_Launchtask(TASK_HM10_DISCONN,5,5); + } + +void HM10_Read_LYWSD02(void) { + HM10_Launchtask(TASK_HM10_CONN,0,1); + HM10_Launchtask(TASK_HM10_FEEDBACK,1,35); + HM10_Launchtask(TASK_HM10_SUB_L2,2,20); + HM10_Launchtask(TASK_HM10_UN_L2,3,80); + HM10_Launchtask(TASK_HM10_READ_BT_L2,4,5); + HM10_Launchtask(TASK_HM10_DISCONN,5,5); + } + +void HM10_Time_LYWSD02(void) { + HM10_Launchtask(TASK_HM10_DISCONN,0,0); + HM10_Launchtask(TASK_HM10_CONN,1,5); + HM10_Launchtask(TASK_HM10_FEEDBACK,2,35); + HM10_Launchtask(TASK_HM10_TIME_L2,3,20); + HM10_Launchtask(TASK_HM10_DISCONN,4,5); + } +# 231 "S:/Development/Tasmota/tasmota/xsns_62_MI_HM10.ino" +uint32_t MIBLEgetSensorSlot(uint8_t (&_serial)[6], uint8_t _type){ + if(_type==0xff){ + DEBUG_SENSOR_LOG(PSTR("MIBLE: will test MAC-type")); + for (uint32_t i=0;i<4;i++){ + if(memcmp(_serial,kHM10SlaveID+i,3)==0){ + DEBUG_SENSOR_LOG(PSTR("MIBLE: MAC is type %u"), i); + _type = i+1; + } + else { + DEBUG_SENSOR_LOG(PSTR("MIBLE: MAC-type is unknown")); + } + } + } + if(_type==0xff) return _type; + + DEBUG_SENSOR_LOG(PSTR("MIBLE: vector size %u"), MIBLEsensors.size()); + for(uint32_t i=0; ibegin(HM10.serialSpeed)) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s start serial communication fixed to 115200 baud"),D_CMND_HM10); + if (HM10Serial->hardwareSerial()) { + ClaimSerial(); + DEBUG_SENSOR_LOG(PSTR("HM10: claim HW")); + } + HM10_Reset(); + HM10.mode.pending_task = 1; + HM10.mode.init = 1; + HM10.period = Settings.tele_period; + DEBUG_SENSOR_LOG(PSTR("%s_TASK_LIST initialized, now return to main loop"),D_CMND_HM10); + } + return; +} +# 315 "S:/Development/Tasmota/tasmota/xsns_62_MI_HM10.ino" +void HM10MACStringToBytes(const char* string, uint8_t _mac[]) { + uint32_t index = 0; + while (index < 12) { + char c = string[index]; + uint32_t value = 0; + if(c >= '0' && c <= '9') + value = (c - '0'); + else if (c >= 'A' && c <= 'F') + value = (10 + (c - 'A')); + _mac[(index/2)] += value << (((index + 1) % 2) * 4); + + index++; + } + DEBUG_SENSOR_LOG(PSTR("HM10: MAC-array: %x%x%x%x%x%x"),_mac[0],_mac[1],_mac[2],_mac[3],_mac[4],_mac[5]); +} + + + + + + +void HM10ParseResponse(char *buf) { + if (!strncmp(buf,"OK",2)) { + DEBUG_SENSOR_LOG(PSTR("HM10: got OK")); + } + if (!strncmp(buf,"HMSoft",6)) { + const char* _fw = "000"; + memcpy((void *)_fw,(void *)(buf+8),3); + HM10.firmware = atoi(_fw); + DEBUG_SENSOR_LOG(PSTR("HM10: Firmware: %d"), HM10.firmware); + return; + } + char * _pos = strstr(buf, "IS0:"); + if(_pos) { + const char* _mac = "000000000000"; + memcpy((void *)_mac,(void *)(_pos+4),12); + DEBUG_SENSOR_LOG(PSTR("HM10: found Mac: %s"), _mac); + uint8_t _newMacArray[6] = {0}; + HM10MACStringToBytes(_mac, _newMacArray); + DEBUG_SENSOR_LOG(PSTR("HM10: MAC-array: %x%x%x%x%x%x"),_newMacArray[0],_newMacArray[1],_newMacArray[2],_newMacArray[3],_newMacArray[4],_newMacArray[5]); + MIBLEgetSensorSlot(_newMacArray, 0xff); + } + if (strstr(buf, "LOST")){ + HM10.mode.connected = false; + } + else { + DEBUG_SENSOR_LOG(PSTR("HM10: empty response")); + } +} + +void HM10readTempHum(char *_buf){ + DEBUG_SENSOR_LOG(PSTR("HM10: raw data: %x%x%x%x%x%x%x"),_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + if(_buf[0] != 0 && _buf[1] != 0){ + memcpy(&LYWSD0x_HT,(void *)_buf,3); + DEBUG_SENSOR_LOG(PSTR("HM10: Temperature * 100: %u, Humidity: %u"),LYWSD0x_HT.temp,LYWSD0x_HT.hum); + uint32_t _slot = HM10.state.sensor; + + DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); + static float _tempFloat; + _tempFloat=(float)(LYWSD0x_HT.temp)/100.0f; + if(_tempFloat<60){ + MIBLEsensors.at(_slot).temp=_tempFloat; + HM10.mode.awaitingHT = false; + HM10.current_task_delay = 0; + } + _tempFloat=(float)LYWSD0x_HT.hum; + if(_tempFloat<100){ + MIBLEsensors.at(_slot).hum = _tempFloat; + DEBUG_SENSOR_LOG(PSTR("LYWSD03: hum updated")); + } + } +} + +bool HM10readBat(char *_buf){ + DEBUG_SENSOR_LOG(PSTR("HM10: raw data: %x%x%x%x%x%x%x"),_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + if(_buf[0] != 0){ + DEBUG_SENSOR_LOG(PSTR("HM10: Battery: %u"),_buf[0]); + uint32_t _slot = HM10.state.sensor; + DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); + if(_buf[0]<101){ + MIBLEsensors.at(_slot).bat=_buf[0]; + return true; + } + } + return false; +} + + + + + +bool HM10SerialHandleFeedback(){ + bool success = false; + uint32_t i = 0; + char ret[HM10_MAX_RX_BUF] = {0}; + + + while(HM10Serial->available()) { + + if(iread(); + } + i++; + success = true; + } + if(HM10.mode.awaitingHT) { + if (HM10.mode.connected) HM10readTempHum(ret); + } + else if(HM10.mode.awaitingB) { + if (HM10.mode.connected) { + if (HM10readBat(ret)){ + HM10.mode.awaitingB = false; + HM10.current_task_delay = 0; + } + } + } + else if(success) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s response: %s"),D_CMND_HM10, (char *)ret); + HM10ParseResponse(ret); + } + else { + + } + return success; +} + + + + + +void HM10_TaskEvery100ms(){ + if (HM10.current_task_delay == 0) { + uint8_t i = 0; + bool runningTaskLoop = true; + while (runningTaskLoop) { + switch(HM10_TASK_LIST[i][0]) { + case TASK_HM10_ROLE1: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s set role to 1"),D_CMND_HM10); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+ROLE1"); + break; + case TASK_HM10_IMME1: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s set imme to 1"),D_CMND_HM10); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+IMME1"); + break; + case TASK_HM10_DISC: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s start discovery"),D_CMND_HM10); + HM10.current_task_delay = 35; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+DISC?"); + break; + case TASK_HM10_VERSION: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read version"),D_CMND_HM10); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+VERR?"); + break; + case TASK_HM10_NAME: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read name"),D_CMND_HM10); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+NAME?"); + break; + case TASK_HM10_CONN: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s connect"),D_CMND_HM10); + HM10.current_task_delay = 2; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + char _con[20]; + sprintf_P(_con,"AT+CON%02x%02x%02x%02x%02x%02x",MIBLEsensors.at(HM10.state.sensor).serial[0],MIBLEsensors.at(HM10.state.sensor).serial[1],MIBLEsensors.at(HM10.state.sensor).serial[2],MIBLEsensors.at(HM10.state.sensor).serial[3],MIBLEsensors.at(HM10.state.sensor).serial[4],MIBLEsensors.at(HM10.state.sensor).serial[5]); + HM10Serial->write(_con); + HM10.mode.awaitingB = false; + HM10.mode.connected = true; + break; + case TASK_HM10_DISCONN: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s disconnect"),D_CMND_HM10); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT"); + break; + case TASK_HM10_RESET: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s Reset Device"),D_CMND_HM10); + HM10Serial->write("AT+RESET"); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + break; + case TASK_HM10_SUB_L3: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s subscribe"),D_CMND_HM10); + HM10.current_task_delay = 25; + HM10_TaskReplaceInSlot(TASK_HM10_DELAY_SUB,i); + runningTaskLoop = false; + HM10Serial->write("AT+NOTIFY_ON0037"); + break; + case TASK_HM10_UN_L3: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s un-subscribe"),D_CMND_HM10); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10.mode.awaitingHT = false; + HM10Serial->write("AT+NOTIFYOFF0037"); + break; + case TASK_HM10_SUB_L2: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s subscribe"),D_CMND_HM10); + HM10.current_task_delay = 25; + HM10_TaskReplaceInSlot(TASK_HM10_DELAY_SUB,i); + runningTaskLoop = false; + HM10Serial->write("AT+NOTIFY_ON003C"); + break; + case TASK_HM10_UN_L2: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s un-subscribe"),D_CMND_HM10); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10.mode.awaitingHT = false; + HM10Serial->write("AT+NOTIFYOFF003C"); + break; + case TASK_HM10_TIME_L2: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s set time"),D_CMND_HM10); + HM10.current_task_delay = 5; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10.time = Rtc.utc_time; + HM10Serial->write("AT+SEND_DATAWR002F"); + HM10Serial->write(HM10.timebuf,4); + HM10Serial->write(Rtc.time_timezone / 60); + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s Time-string: %x%x%x%x%x"),D_CMND_HM10, HM10.timebuf[0],HM10.timebuf[1],HM10.timebuf[2],HM10.timebuf[3],(Rtc.time_timezone /60)); + break; + case TASK_HM10_READ_HT: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read handle 0036"),D_CMND_HM10); + HM10.current_task_delay = 0; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+READDATA0036?"); + HM10.mode.awaitingHT = true; + break; + case TASK_HM10_READ_BT_L3: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read handle 003A"),D_CMND_HM10); + HM10.current_task_delay = 2; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+READDATA003A?"); + HM10.mode.awaitingB = true; + break; + case TASK_HM10_READ_BT_L2: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read handle 0043"),D_CMND_HM10); + HM10.current_task_delay = 2; + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+READDATA0043?"); + HM10.mode.awaitingB = true; + break; + + + + + + + + case TASK_HM10_FEEDBACK: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s get response"),D_CMND_HM10); + HM10SerialHandleFeedback(); + HM10.current_task_delay = HM10_TASK_LIST[i+1][1];; + HM10_TASK_LIST[i][0] = TASK_HM10_DONE; + runningTaskLoop = false; + break; + case TASK_HM10_DELAY_SUB: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s start reading"),D_CMND_HM10); + HM10SerialHandleFeedback(); + HM10.current_task_delay = HM10_TASK_LIST[i+1][1];; + HM10_TASK_LIST[i][0] = TASK_HM10_DONE; + HM10.mode.awaitingHT = true; + runningTaskLoop = false; + break; + case TASK_HM10_DONE: + + + if(HM10_TASK_LIST[i+1][0] == TASK_HM10_NOTASK) { + DEBUG_SENSOR_LOG(PSTR("%sno Tasks left"),D_CMND_HM10); + DEBUG_SENSOR_LOG(PSTR("%sHM10_TASK_DONE current slot %u"),D_CMND_HM10, i); + for (uint8_t j = 0; j < HM10_MAX_TASK_NUMBER+1; j++) { + DEBUG_SENSOR_LOG(PSTR("%sHM10_TASK cleanup slot %u"),D_CMND_HM10, j); + HM10_TASK_LIST[j][0] = TASK_HM10_NOTASK; + HM10_TASK_LIST[j][1] = 0; + } + runningTaskLoop = false; + HM10.mode.pending_task = 0; + break; + } + } + i++; + } + } + else { + HM10.current_task_delay--; + } +} + + + + + + +void HM10EverySecond(){ + if(HM10.firmware == 0) return; + if(HM10.mode.pending_task == 1) return; + if (MIBLEsensors.size()==0) return; + + static uint32_t _counter = 0; + static uint32_t _nextSensorSlot = 0; + if(_counter==0) { + HM10.state.sensor = _nextSensorSlot; + _nextSensorSlot++; + if(MIBLEsensors.at(HM10.state.sensor).type==LYWSD03MMC) { + HM10.mode.pending_task = 1; + HM10_Read_LYWSD03(); + } + if(MIBLEsensors.at(HM10.state.sensor).type==LYWSD02) { + HM10.mode.pending_task = 1; + HM10_Read_LYWSD02(); + } + if (HM10.state.sensor==MIBLEsensors.size()-1) { + _nextSensorSlot= 0; + _counter++; + } + DEBUG_SENSOR_LOG(PSTR("%s active sensor now: %u"),D_CMND_HM10, HM10.state.sensor); + } + else _counter++; + if (_counter>HM10.period) _counter = 0; +} + +bool HM10Cmd(void) { + char command[CMDSZ]; + bool serviced = true; + uint8_t disp_len = strlen(D_CMND_HM10); + + if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_HM10), disp_len)) { + uint32_t command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kHM10_Commands); + switch (command_code) { + case CMND_HM10_PERIOD: + if (XdrvMailbox.data_len > 0) { + if (command_code == CMND_HM10_PERIOD) { HM10.period = XdrvMailbox.payload; } + } + else { + if (command_code == CMND_HM10_PERIOD) XdrvMailbox.payload = HM10.period; + } + Response_P(S_JSON_HM10_COMMAND_NVALUE, command, XdrvMailbox.payload); + break; + case CMND_HM10_BAUD: + if (XdrvMailbox.data_len > 0) { + if (command_code == CMND_HM10_BAUD) { + HM10.serialSpeed = XdrvMailbox.payload; + HM10Serial->begin(HM10.serialSpeed); + } + } + else { + if (command_code == CMND_HM10_BAUD) XdrvMailbox.payload = HM10.serialSpeed; + } + Response_P(S_JSON_HM10_COMMAND_NVALUE, command, XdrvMailbox.payload); + break; + case CMND_HM10_TIME: + if (XdrvMailbox.data_len > 0) { + if(MIBLEsensors.size()>XdrvMailbox.payload){ + if(MIBLEsensors.at(XdrvMailbox.payload).type == LYWSD02){ + HM10.state.sensor = XdrvMailbox.payload; + HM10_Time_LYWSD02(); + } + } + } + Response_P(S_JSON_HM10_COMMAND_NVALUE, command, XdrvMailbox.payload); + break; + case CMND_HM10_AT: + HM10Serial->write("AT"); + if (strlen(XdrvMailbox.data)!=0) { + HM10Serial->write("+"); + HM10Serial->write(XdrvMailbox.data); + Response_P(S_JSON_HM10_COMMAND, ":AT+",XdrvMailbox.data); + } + else Response_P(S_JSON_HM10_COMMAND, ":AT",XdrvMailbox.data); + break; + case CMND_HM10_DISC_SCAN: + if (command_code == CMND_HM10_DISC_SCAN) { HM10_Discovery_Scan(); } + Response_P(S_JSON_HM10_COMMAND, command, ""); + break; + default: + + serviced = false; + break; + } + } else { + return false; + } + return serviced; +} + + + + + + +const char HTTP_HM10[] PROGMEM = "{s}HM10" " Firmware " "{m}%u{e}"; +const char HTTP_HM10_SERIAL[] PROGMEM = "{s}%s %s{m}%02x:%02x:%02x:%02x:%02x:%02x%{e}"; +const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u%%{e}"; +const char HTTP_HM10_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%sus/cm{e}"; +const char HTTP_HM10_HL[] PROGMEM = "{s}
{m}
{e}"; + +void HM10Show(bool json) +{ + if (json) { + for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { + char slave[33]; + sprintf_P(slave,"%s-%02x%02x%02x",kHM10SlaveType[MIBLEsensors.at(i).type-1],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]); + char temperature[33]; + dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature); + + ResponseAppend_P(PSTR(",\"%s\":{"),slave); + if(MIBLEsensors.at(i).temp!=-1000.0f){ + ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s"), temperature); + } + if (MIBLEsensors.at(i).type==FLORA){ + char lux[33]; + char moisture[33]; + char fertility[33]; + dtostrfd((float)MIBLEsensors.at(i).lux, 0, lux); + dtostrfd(MIBLEsensors.at(i).moisture, 0, moisture); + dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility); + if(MIBLEsensors.at(i).lux!=0xffff){ + ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":%s"), lux); + } + if(MIBLEsensors.at(i).moisture!=-1000.0f){ + ResponseAppend_P(PSTR(",\"" D_JSON_MOISTURE "\":%s"), moisture); + } + if(MIBLEsensors.at(i).fertility!=-1000.0f){ + ResponseAppend_P(PSTR(",\"Fertility\":%s"), fertility); + } + } + if (MIBLEsensors.at(i).type>FLORA){ + char humidity[33]; + dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity); + if(MIBLEsensors.at(i).hum!=-1.0f){ + ResponseAppend_P(PSTR(",\"" D_JSON_HUMIDITY "\":%s"), humidity); + } + if(MIBLEsensors.at(i).bat!=0xff){ + ResponseAppend_P(PSTR(",\"Battery\":%u"), MIBLEsensors.at(i).bat); + } + } + ResponseAppend_P(PSTR("}")); + } +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_HM10, HM10.firmware); + for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { + WSContentSend_PD(HTTP_HM10_HL); + WSContentSend_PD(HTTP_HM10_SERIAL, kHM10SlaveType[MIBLEsensors.at(i).type-1], D_MAC_ADDRESS, MIBLEsensors.at(i).serial[0], MIBLEsensors.at(i).serial[1],MIBLEsensors.at(i).serial[2],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]); + if(MIBLEsensors.at(i).temp!=-1000.0f){ + char temperature[33]; + dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature); + WSContentSend_PD(HTTP_SNS_TEMP, kHM10SlaveType[MIBLEsensors.at(i).type-1], temperature, TempUnit()); + } + if (MIBLEsensors.at(i).type==FLORA){ + if(MIBLEsensors.at(i).lux!=0xffff){ + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).lux); + } + if(MIBLEsensors.at(i).moisture!=-1000.0f){ + WSContentSend_PD(HTTP_SNS_MOISTURE, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).moisture); + } + if(MIBLEsensors.at(i).fertility!=-1000.0f){ + char fertility[33]; + dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility); + WSContentSend_PD(HTTP_HM10_FLORA_DATA, kHM10SlaveType[MIBLEsensors.at(i).type-1], fertility); + } + } + if (MIBLEsensors.at(i).type>FLORA){ + if(MIBLEsensors.at(i).hum!=-1.0f){ + char humidity[33]; + dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity); + WSContentSend_PD(HTTP_SNS_HUM, kHM10SlaveType[MIBLEsensors.at(i).type-1], humidity); + } + if(MIBLEsensors.at(i).bat!=0xff){ + WSContentSend_PD(HTTP_BATTERY, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).bat); + } + } + } +#endif + } +} + + + + + +bool Xsns62(uint8_t function) +{ + bool result = false; + + if ((pin[GPIO_HM10_RX] < 99) && (pin[GPIO_HM10_TX] < 99)) { + switch (function) { + case FUNC_INIT: + HM10SerialInit(); + break; + case FUNC_EVERY_50_MSECOND: + HM10SerialHandleFeedback(); + break; + case FUNC_EVERY_100_MSECOND: + if (HM10_TASK_LIST[0][0] != TASK_HM10_NOTASK) { + HM10_TaskEvery100ms(); + } + break; + case FUNC_EVERY_SECOND: + HM10EverySecond(); + break; + case FUNC_COMMAND: + result = HM10Cmd(); + break; + case FUNC_JSON_APPEND: + HM10Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + HM10Show(0); + break; +#endif + } + } + return result; +} +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_63_aht1x.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_63_aht1x.ino" +#ifdef USE_I2C +#ifdef USE_AHT1x + + + + + + +#define XSNS_63 63 +#define XI2C_43 43 + +#define AHT10_ADDR 0x38 + +uint8_t eSensorCalibrateCmd[3] = {0xE1, 0x08, 0x00}; +uint8_t eSensorNormalCmd[3] = {0xA8, 0x00, 0x00}; +uint8_t eSensorMeasureCmd[3] = {0xAC, 0x33, 0x00}; +uint8_t eSensorResetCmd = 0xBA; + +struct AHT10 { + float humidity = NAN; + float temperature = NAN; + uint8_t valid = 0; + uint8_t count = 0; + char name[6] = "AHT1x"; +} AHT10; + +bool AHT10Read(void) +{ + if (AHT10.valid) { AHT10.valid--; } + + uint8_t data[6]; + + Wire.beginTransmission(AHT10_ADDR); + Wire.write(eSensorMeasureCmd, 3); + Wire.endTransmission(); + delay(100); + + Wire.requestFrom(AHT10_ADDR, 6); + for (uint32_t i = 0; Wire.available() > 0; i++) { + data[i] = Wire.read(); + } + + uint32_t result_h = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; + uint32_t result_t = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; + + float humidity = result_h * 100 / 1048576; + float temperature = ((200 * result_t) / 1048576) - 50; + + if (isnan(temperature) || isnan(humidity)) { return false; } + + AHT10.humidity = ConvertHumidity(humidity); + AHT10.temperature = ConvertTemp(temperature); + + AHT10.valid = SENSOR_MAX_MISS; + return true; +} + + + +bool AHT10Init(void) +{ + Wire.begin(AHT10_ADDR); + Wire.beginTransmission(AHT10_ADDR); + Wire.write(eSensorCalibrateCmd, 3); + Wire.endTransmission(); + + delay(500); + + return (0x08 == (AHT10ReadStatus() & 0x68)); +} + +uint8_t AHT10ReadStatus(void) +{ + Wire.requestFrom(AHT10_ADDR, 1); + uint8_t result = Wire.read(); + return result; +} + +void AHT10Reset(void) +{ + Wire.beginTransmission(AHT10_ADDR); + Wire.write(eSensorResetCmd); + Wire.endTransmission(); + delay(20); +} + + + +void AHT10Detect(void) +{ + if (I2cActive(AHT10_ADDR)) { return; } + + if (AHT10Init()) { + I2cSetActiveFound(AHT10_ADDR, AHT10.name); + AHT10.count = 1; + } +} + +void AHT10EverySecond(void) +{ + if (uptime &1) { + + if (!AHT10Read()) { + AddLogMissed(AHT10.name, AHT10.valid); + } + } +} + +void AHT10Show(bool json) +{ + if (AHT10.valid) { + char temperature[33]; + dtostrfd(AHT10.temperature, Settings.flag2.temperature_resolution, temperature); + char humidity[33]; + dtostrfd(AHT10.humidity, Settings.flag2.humidity_resolution, humidity); + + if (json) { + ResponseAppend_P(JSON_SNS_TEMPHUM, AHT10.name, temperature, humidity); +#ifdef USE_DOMOTICZ + if ((0 == tele_period)) { + DomoticzTempHumSensor(temperature, humidity); + } +#endif +#ifdef USE_KNX + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, AHT10.temperature); + KnxSensor(KNX_HUMIDITY, AHT10.humidity); + } +#endif +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, AHT10.name, temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_HUM, AHT10.name, humidity); +#endif + } + } +} + + + + + +bool Xsns63(uint8_t function) +{ + if (!I2cEnabled(XI2C_43)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + AHT10Detect(); + } + else if (AHT10.count) { + switch (function) { + case FUNC_EVERY_SECOND: + AHT10EverySecond(); + break; + case FUNC_JSON_APPEND: + AHT10Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + AHT10Show(0); + break; +#endif + } + } + return result; +} + +#endif +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_91_prometheus.ino" +# 22 "S:/Development/Tasmota/tasmota/xsns_91_prometheus.ino" +#ifdef USE_PROMETHEUS + + + + +#define XSNS_91 91 + +void HandleMetrics(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR("Prometheus")); + + WSContentBegin(200, CT_PLAIN); + + + char parameter[FLOATSZ]; + + if (global_temperature != 9999) { + dtostrfd(global_temperature, Settings.flag2.temperature_resolution, parameter); + WSContentSend_P(PSTR("# TYPE global_temperature gauge\nglobal_temperature %s\n"), parameter); + } + if (global_humidity != 0) { + dtostrfd(global_humidity, Settings.flag2.humidity_resolution, parameter); + WSContentSend_P(PSTR("# TYPE global_humidity gauge\nglobal_humidity %s\n"), parameter); + } + if (global_pressure != 0) { + dtostrfd(global_pressure, Settings.flag2.pressure_resolution, parameter); + WSContentSend_P(PSTR("# TYPE global_pressure gauge\nglobal_pressure %s\n"), parameter); + } + +#ifdef USE_ENERGY_SENSOR + dtostrfd(Energy.voltage[0], Settings.flag2.voltage_resolution, parameter); + WSContentSend_P(PSTR("# TYPE voltage gauge\nvoltage %s\n"), parameter); + dtostrfd(Energy.current[0], Settings.flag2.current_resolution, parameter); + WSContentSend_P(PSTR("# TYPE current gauge\ncurrent %s\n"), parameter); + dtostrfd(Energy.active_power[0], Settings.flag2.wattage_resolution, parameter); + WSContentSend_P(PSTR("# TYPE active_power gauge\nactive_power %s\n"), parameter); + dtostrfd(Energy.daily, Settings.flag2.energy_resolution, parameter); + WSContentSend_P(PSTR("# TYPE energy_daily gauge\nenergy_daily %s\n"), parameter); + dtostrfd(Energy.total, Settings.flag2.energy_resolution, parameter); + WSContentSend_P(PSTR("# TYPE energy_total counter\nenergy_total %s\n"), parameter); +#endif +# 80 "S:/Development/Tasmota/tasmota/xsns_91_prometheus.ino" + WSContentEnd(); +} + + + + + +bool Xsns91(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/metrics", HandleMetrics); + break; + } + return result; +} + +#endif +# 1 "S:/Development/Tasmota/tasmota/xsns_interface.ino" +# 20 "S:/Development/Tasmota/tasmota/xsns_interface.ino" +#ifdef XFUNC_PTR_IN_ROM +bool (* const xsns_func_ptr[])(uint8_t) PROGMEM = { +#else +bool (* const xsns_func_ptr[])(uint8_t) = { +#endif + +#ifdef XSNS_01 + &Xsns01, +#endif + +#ifdef XSNS_02 + &Xsns02, +#endif + +#ifdef XSNS_03 + &Xsns03, +#endif + +#ifdef XSNS_04 + &Xsns04, +#endif + +#ifdef XSNS_05 + &Xsns05, +#endif + +#ifdef XSNS_06 + &Xsns06, +#endif + +#ifdef XSNS_07 + &Xsns07, +#endif + +#ifdef XSNS_08 + &Xsns08, +#endif + +#ifdef XSNS_09 + &Xsns09, +#endif + +#ifdef XSNS_10 + &Xsns10, +#endif + +#ifdef XSNS_11 + &Xsns11, +#endif + +#ifdef XSNS_12 + &Xsns12, +#endif + +#ifdef XSNS_13 + &Xsns13, +#endif + +#ifdef XSNS_14 + &Xsns14, +#endif + +#ifdef XSNS_15 + &Xsns15, +#endif + +#ifdef XSNS_16 + &Xsns16, +#endif + +#ifdef XSNS_17 + &Xsns17, +#endif + +#ifdef XSNS_18 + &Xsns18, +#endif + +#ifdef XSNS_19 + &Xsns19, +#endif + +#ifdef XSNS_20 + &Xsns20, +#endif + +#ifdef XSNS_21 + &Xsns21, +#endif + +#ifdef XSNS_22 + &Xsns22, +#endif + +#ifdef XSNS_23 + &Xsns23, +#endif + +#ifdef XSNS_24 + &Xsns24, +#endif + +#ifdef XSNS_25 + &Xsns25, +#endif + +#ifdef XSNS_26 + &Xsns26, +#endif + +#ifdef XSNS_27 + &Xsns27, +#endif + +#ifdef XSNS_28 + &Xsns28, +#endif + +#ifdef XSNS_29 + &Xsns29, +#endif + +#ifdef XSNS_30 + &Xsns30, +#endif + +#ifdef XSNS_31 + &Xsns31, +#endif + +#ifdef XSNS_32 + &Xsns32, +#endif + +#ifdef XSNS_33 + &Xsns33, +#endif + +#ifdef XSNS_34 + &Xsns34, +#endif + +#ifdef XSNS_35 + &Xsns35, +#endif + +#ifdef XSNS_36 + &Xsns36, +#endif + +#ifdef XSNS_37 + &Xsns37, +#endif + +#ifdef XSNS_38 + &Xsns38, +#endif + +#ifdef XSNS_39 + &Xsns39, +#endif + +#ifdef XSNS_40 + &Xsns40, +#endif + +#ifdef XSNS_41 + &Xsns41, +#endif + +#ifdef XSNS_42 + &Xsns42, +#endif + +#ifdef XSNS_43 + &Xsns43, +#endif + +#ifdef XSNS_44 + &Xsns44, +#endif + +#ifdef XSNS_45 + &Xsns45, +#endif + +#ifdef XSNS_46 + &Xsns46, +#endif + +#ifdef XSNS_47 + &Xsns47, +#endif + +#ifdef XSNS_48 + &Xsns48, +#endif + +#ifdef XSNS_49 + &Xsns49, +#endif + +#ifdef XSNS_50 + &Xsns50, +#endif + +#ifdef XSNS_51 + &Xsns51, +#endif + +#ifdef XSNS_52 + &Xsns52, +#endif + +#ifdef XSNS_53 + &Xsns53, +#endif + +#ifdef XSNS_54 + &Xsns54, +#endif + +#ifdef XSNS_55 + &Xsns55, +#endif + +#ifdef XSNS_56 + &Xsns56, +#endif + +#ifdef XSNS_57 + &Xsns57, +#endif + +#ifdef XSNS_58 + &Xsns58, +#endif + +#ifdef XSNS_59 + &Xsns59, +#endif + +#ifdef XSNS_60 + &Xsns60, +#endif + +#ifdef XSNS_61 + &Xsns61, +#endif + +#ifdef XSNS_62 + &Xsns62, +#endif + +#ifdef XSNS_63 + &Xsns63, +#endif + +#ifdef XSNS_64 + &Xsns64, +#endif + +#ifdef XSNS_65 + &Xsns65, +#endif + +#ifdef XSNS_66 + &Xsns66, +#endif + +#ifdef XSNS_67 + &Xsns67, +#endif + +#ifdef XSNS_68 + &Xsns68, +#endif + +#ifdef XSNS_69 + &Xsns69, +#endif + +#ifdef XSNS_70 + &Xsns70, +#endif + +#ifdef XSNS_71 + &Xsns71, +#endif + +#ifdef XSNS_72 + &Xsns72, +#endif + +#ifdef XSNS_73 + &Xsns73, +#endif + +#ifdef XSNS_74 + &Xsns74, +#endif + +#ifdef XSNS_75 + &Xsns75, +#endif + +#ifdef XSNS_76 + &Xsns76, +#endif + +#ifdef XSNS_77 + &Xsns77, +#endif + +#ifdef XSNS_78 + &Xsns78, +#endif + +#ifdef XSNS_79 + &Xsns79, +#endif + +#ifdef XSNS_80 + &Xsns80, +#endif + +#ifdef XSNS_81 + &Xsns81, +#endif + +#ifdef XSNS_82 + &Xsns82, +#endif + +#ifdef XSNS_83 + &Xsns83, +#endif + +#ifdef XSNS_84 + &Xsns84, +#endif + +#ifdef XSNS_85 + &Xsns85, +#endif + +#ifdef XSNS_86 + &Xsns86, +#endif + +#ifdef XSNS_87 + &Xsns87, +#endif + +#ifdef XSNS_88 + &Xsns88, +#endif + +#ifdef XSNS_89 + &Xsns89, +#endif + +#ifdef XSNS_90 + &Xsns90, +#endif + +#ifdef XSNS_91 + &Xsns91, +#endif + +#ifdef XSNS_92 + &Xsns92, +#endif + +#ifdef XSNS_93 + &Xsns93, +#endif + +#ifdef XSNS_94 + &Xsns94, +#endif + +#ifdef XSNS_95 + &Xsns95, +#endif + +#ifdef XSNS_96 + &Xsns96, +#endif + +#ifdef XSNS_97 + &Xsns97, +#endif + +#ifdef XSNS_98 + &Xsns98, +#endif + +#ifdef XSNS_99 + &Xsns99 +#endif +}; + +const uint8_t xsns_present = sizeof(xsns_func_ptr) / sizeof(xsns_func_ptr[0]); + + + + + +#ifdef XFUNC_PTR_IN_ROM +const uint8_t kXsnsList[] PROGMEM = { +#else +const uint8_t kXsnsList[] = { +#endif + +#ifdef XSNS_01 + XSNS_01, +#endif + +#ifdef XSNS_02 + XSNS_02, +#endif + +#ifdef XSNS_03 + XSNS_03, +#endif + +#ifdef XSNS_04 + XSNS_04, +#endif + +#ifdef XSNS_05 + XSNS_05, +#endif + +#ifdef XSNS_06 + XSNS_06, +#endif + +#ifdef XSNS_07 + XSNS_07, +#endif + +#ifdef XSNS_08 + XSNS_08, +#endif + +#ifdef XSNS_09 + XSNS_09, +#endif + +#ifdef XSNS_10 + XSNS_10, +#endif + +#ifdef XSNS_11 + XSNS_11, +#endif + +#ifdef XSNS_12 + XSNS_12, +#endif + +#ifdef XSNS_13 + XSNS_13, +#endif + +#ifdef XSNS_14 + XSNS_14, +#endif + +#ifdef XSNS_15 + XSNS_15, +#endif + +#ifdef XSNS_16 + XSNS_16, +#endif + +#ifdef XSNS_17 + XSNS_17, +#endif + +#ifdef XSNS_18 + XSNS_18, +#endif + +#ifdef XSNS_19 + XSNS_19, +#endif + +#ifdef XSNS_20 + XSNS_20, +#endif + +#ifdef XSNS_21 + XSNS_21, +#endif + +#ifdef XSNS_22 + XSNS_22, +#endif + +#ifdef XSNS_23 + XSNS_23, +#endif + +#ifdef XSNS_24 + XSNS_24, +#endif + +#ifdef XSNS_25 + XSNS_25, +#endif + +#ifdef XSNS_26 + XSNS_26, +#endif + +#ifdef XSNS_27 + XSNS_27, +#endif + +#ifdef XSNS_28 + XSNS_28, +#endif + +#ifdef XSNS_29 + XSNS_29, +#endif + +#ifdef XSNS_30 + XSNS_30, +#endif + +#ifdef XSNS_31 + XSNS_31, +#endif + +#ifdef XSNS_32 + XSNS_32, +#endif + +#ifdef XSNS_33 + XSNS_33, +#endif + +#ifdef XSNS_34 + XSNS_34, +#endif + +#ifdef XSNS_35 + XSNS_35, +#endif + +#ifdef XSNS_36 + XSNS_36, +#endif + +#ifdef XSNS_37 + XSNS_37, +#endif + +#ifdef XSNS_38 + XSNS_38, +#endif + +#ifdef XSNS_39 + XSNS_39, +#endif + +#ifdef XSNS_40 + XSNS_40, +#endif + +#ifdef XSNS_41 + XSNS_41, +#endif + +#ifdef XSNS_42 + XSNS_42, +#endif + +#ifdef XSNS_43 + XSNS_43, +#endif + +#ifdef XSNS_44 + XSNS_44, +#endif + +#ifdef XSNS_45 + XSNS_45, +#endif + +#ifdef XSNS_46 + XSNS_46, +#endif + +#ifdef XSNS_47 + XSNS_47, +#endif + +#ifdef XSNS_48 + XSNS_48, +#endif + +#ifdef XSNS_49 + XSNS_49, +#endif + +#ifdef XSNS_50 + XSNS_50, +#endif + +#ifdef XSNS_51 + XSNS_51, +#endif + +#ifdef XSNS_52 + XSNS_52, +#endif + +#ifdef XSNS_53 + XSNS_53, +#endif + +#ifdef XSNS_54 + XSNS_54, +#endif + +#ifdef XSNS_55 + XSNS_55, +#endif + +#ifdef XSNS_56 + XSNS_56, +#endif + +#ifdef XSNS_57 + XSNS_57, +#endif + +#ifdef XSNS_58 + XSNS_58, +#endif + +#ifdef XSNS_59 + XSNS_59, +#endif + +#ifdef XSNS_60 + XSNS_60, +#endif + +#ifdef XSNS_61 + XSNS_61, +#endif + +#ifdef XSNS_62 + XSNS_62, +#endif + +#ifdef XSNS_63 + XSNS_63, +#endif + +#ifdef XSNS_64 + XSNS_64, +#endif + +#ifdef XSNS_65 + XSNS_65, +#endif + +#ifdef XSNS_66 + XSNS_66, +#endif + +#ifdef XSNS_67 + XSNS_67, +#endif + +#ifdef XSNS_68 + XSNS_68, +#endif + +#ifdef XSNS_69 + XSNS_69, +#endif + +#ifdef XSNS_70 + XSNS_70, +#endif + +#ifdef XSNS_71 + XSNS_71, +#endif + +#ifdef XSNS_72 + XSNS_72, +#endif + +#ifdef XSNS_73 + XSNS_73, +#endif + +#ifdef XSNS_74 + XSNS_74, +#endif + +#ifdef XSNS_75 + XSNS_75, +#endif + +#ifdef XSNS_76 + XSNS_76, +#endif + +#ifdef XSNS_77 + XSNS_77, +#endif + +#ifdef XSNS_78 + XSNS_78, +#endif + +#ifdef XSNS_79 + XSNS_79, +#endif + +#ifdef XSNS_80 + XSNS_80, +#endif + +#ifdef XSNS_81 + XSNS_81, +#endif + +#ifdef XSNS_82 + XSNS_82, +#endif + +#ifdef XSNS_83 + XSNS_83, +#endif + +#ifdef XSNS_84 + XSNS_84, +#endif + +#ifdef XSNS_85 + XSNS_85, +#endif + +#ifdef XSNS_86 + XSNS_86, +#endif + +#ifdef XSNS_87 + XSNS_87, +#endif + +#ifdef XSNS_88 + XSNS_88, +#endif + +#ifdef XSNS_89 + XSNS_89, +#endif + +#ifdef XSNS_90 + XSNS_90, +#endif + +#ifdef XSNS_91 + XSNS_91, +#endif + +#ifdef XSNS_92 + XSNS_92, +#endif + +#ifdef XSNS_93 + XSNS_93, +#endif + +#ifdef XSNS_94 + XSNS_94, +#endif + +#ifdef XSNS_95 + XSNS_95, +#endif + +#ifdef XSNS_96 + XSNS_96, +#endif + +#ifdef XSNS_97 + XSNS_97, +#endif + +#ifdef XSNS_98 + XSNS_98, +#endif + +#ifdef XSNS_99 + XSNS_99 +#endif +}; + + + +bool XsnsEnabled(uint32_t sns_index) +{ + if (sns_index < sizeof(kXsnsList)) { +#ifdef XFUNC_PTR_IN_ROM + uint32_t index = pgm_read_byte(kXsnsList + sns_index); +#else + uint32_t index = kXsnsList[sns_index]; +#endif + return bitRead(Settings.sensors[index / 32], index % 32); + } + return true; +} + +void XsnsSensorState(void) +{ + ResponseAppend_P(PSTR("\"")); + for (uint32_t i = 0; i < sizeof(kXsnsList); i++) { +#ifdef XFUNC_PTR_IN_ROM + uint32_t sensorid = pgm_read_byte(kXsnsList + i); +#else + uint32_t sensorid = kXsnsList[i]; +#endif + bool disabled = false; + if (sensorid < MAX_XSNS_DRIVERS) { + disabled = !bitRead(Settings.sensors[sensorid / 32], sensorid % 32); + } + ResponseAppend_P(PSTR("%s%s%d"), (i) ? "," : "", (disabled) ? "!" : "", sensorid); + } + ResponseAppend_P(PSTR("\"")); +} + + + + + +bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index) +{ + xsns_index++; + if (xsns_index == xsns_present) { xsns_index = 0; } + +#ifndef USE_DEBUG_DRIVER + if (FUNC_WEB_SENSOR == Function) { +#endif + uint32_t max_disabled = xsns_present; + while (!XsnsEnabled(xsns_index) && max_disabled--) { + xsns_index++; + if (xsns_index == xsns_present) { xsns_index = 0; } + } +#ifndef USE_DEBUG_DRIVER + } +#endif + + return xsns_func_ptr[xsns_index](Function); +} + +bool XsnsCall(uint8_t Function) +{ + bool result = false; + + DEBUG_TRACE_LOG(PSTR("SNS: %d"), Function); + +#ifdef PROFILE_XSNS_EVERY_SECOND + uint32_t profile_start_millis = millis(); +#endif + + for (uint32_t x = 0; x < xsns_present; x++) { +#ifdef USE_DEBUG_DRIVER + if (XsnsEnabled(x)) { +#endif + + if ((FUNC_WEB_SENSOR == Function) && !XsnsEnabled(x)) { continue; } + +#ifdef PROFILE_XSNS_SENSOR_EVERY_SECOND + uint32_t profile_start_millis = millis(); +#endif + result = xsns_func_ptr[x](Function); + +#ifdef PROFILE_XSNS_SENSOR_EVERY_SECOND + uint32_t profile_millis = millis() - profile_start_millis; + if (profile_millis) { + if (FUNC_EVERY_SECOND == Function) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PRF: At %08u XsnsCall %d to Sensor %d took %u mS"), uptime, Function, x, profile_millis); + } + } +#endif + + if (result && ((FUNC_COMMAND == Function) || + (FUNC_PIN_STATE == Function) || + (FUNC_COMMAND_SENSOR == Function) + )) { + break; + } +#ifdef USE_DEBUG_DRIVER + } +#endif + } + +#ifdef PROFILE_XSNS_EVERY_SECOND + uint32_t profile_millis = millis() - profile_start_millis; + if (profile_millis) { + if (FUNC_EVERY_SECOND == Function) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PRF: At %08u XsnsCall %d took %u mS"), uptime, Function, profile_millis); + } + } +#endif + + return result; +} +# 1 "S:/Development/Tasmota/tasmota/xx2c_interface.ino" +# 20 "S:/Development/Tasmota/tasmota/xx2c_interface.ino" +#ifdef USE_I2C + +#ifdef XFUNC_PTR_IN_ROM +const uint8_t kI2cList[] PROGMEM = { +#else +const uint8_t kI2cList[] = { +#endif + +#ifdef XI2C_01 + XI2C_01, +#endif + +#ifdef XI2C_02 + XI2C_02, +#endif + +#ifdef XI2C_03 + XI2C_03, +#endif + +#ifdef XI2C_04 + XI2C_04, +#endif + +#ifdef XI2C_05 + XI2C_05, +#endif + +#ifdef XI2C_06 + XI2C_06, +#endif + +#ifdef XI2C_07 + XI2C_07, +#endif + +#ifdef XI2C_08 + XI2C_08, +#endif + +#ifdef XI2C_09 + XI2C_09, +#endif + +#ifdef XI2C_10 + XI2C_10, +#endif + +#ifdef XI2C_11 + XI2C_11, +#endif + +#ifdef XI2C_12 + XI2C_12, +#endif + +#ifdef XI2C_13 + XI2C_13, +#endif + +#ifdef XI2C_14 + XI2C_14, +#endif + +#ifdef XI2C_15 + XI2C_15, +#endif + +#ifdef XI2C_16 + XI2C_16, +#endif + +#ifdef XI2C_17 + XI2C_17, +#endif + +#ifdef XI2C_18 + XI2C_18, +#endif + +#ifdef XI2C_19 + XI2C_19, +#endif + +#ifdef XI2C_20 + XI2C_20, +#endif + +#ifdef XI2C_21 + XI2C_21, +#endif + +#ifdef XI2C_22 + XI2C_22, +#endif + +#ifdef XI2C_23 + XI2C_23, +#endif + +#ifdef XI2C_24 + XI2C_24, +#endif + +#ifdef XI2C_25 + XI2C_25, +#endif + +#ifdef XI2C_26 + XI2C_26, +#endif + +#ifdef XI2C_27 + XI2C_27, +#endif + +#ifdef XI2C_28 + XI2C_28, +#endif + +#ifdef XI2C_29 + XI2C_29, +#endif + +#ifdef XI2C_30 + XI2C_30, +#endif + +#ifdef XI2C_31 + XI2C_31, +#endif + +#ifdef XI2C_32 + XI2C_32, +#endif + +#ifdef XI2C_33 + XI2C_33, +#endif + +#ifdef XI2C_34 + XI2C_34, +#endif + +#ifdef XI2C_35 + XI2C_35, +#endif + +#ifdef XI2C_36 + XI2C_36, +#endif + +#ifdef XI2C_37 + XI2C_37, +#endif + +#ifdef XI2C_38 + XI2C_38, +#endif + +#ifdef XI2C_39 + XI2C_39, +#endif + +#ifdef XI2C_40 + XI2C_40, +#endif + +#ifdef XI2C_41 + XI2C_41, +#endif + +#ifdef XI2C_42 + XI2C_42, +#endif + +#ifdef XI2C_43 + XI2C_43, +#endif + +#ifdef XI2C_44 + XI2C_44, +#endif + +#ifdef XI2C_45 + XI2C_45, +#endif + +#ifdef XI2C_46 + XI2C_46, +#endif + +#ifdef XI2C_47 + XI2C_47, +#endif + +#ifdef XI2C_48 + XI2C_48, +#endif + +#ifdef XI2C_49 + XI2C_49, +#endif + +#ifdef XI2C_50 + XI2C_50, +#endif + +#ifdef XI2C_51 + XI2C_51, +#endif + +#ifdef XI2C_52 + XI2C_52, +#endif + +#ifdef XI2C_53 + XI2C_53, +#endif + +#ifdef XI2C_54 + XI2C_54, +#endif + +#ifdef XI2C_55 + XI2C_55, +#endif + +#ifdef XI2C_56 + XI2C_56, +#endif + +#ifdef XI2C_57 + XI2C_57, +#endif + +#ifdef XI2C_58 + XI2C_58, +#endif + +#ifdef XI2C_59 + XI2C_59, +#endif + +#ifdef XI2C_60 + XI2C_60, +#endif + +#ifdef XI2C_61 + XI2C_61, +#endif + +#ifdef XI2C_62 + XI2C_62, +#endif + +#ifdef XI2C_63 + XI2C_63, +#endif + +#ifdef XI2C_64 + XI2C_64, +#endif + +#ifdef XI2C_65 + XI2C_65, +#endif + +#ifdef XI2C_66 + XI2C_66, +#endif + +#ifdef XI2C_67 + XI2C_67, +#endif + +#ifdef XI2C_68 + XI2C_68, +#endif + +#ifdef XI2C_69 + XI2C_69, +#endif + +#ifdef XI2C_70 + XI2C_70, +#endif + +#ifdef XI2C_71 + XI2C_71, +#endif + +#ifdef XI2C_72 + XI2C_72, +#endif + +#ifdef XI2C_73 + XI2C_73, +#endif + +#ifdef XI2C_74 + XI2C_74, +#endif + +#ifdef XI2C_75 + XI2C_75, +#endif + +#ifdef XI2C_76 + XI2C_76, +#endif + +#ifdef XI2C_77 + XI2C_77, +#endif + +#ifdef XI2C_78 + XI2C_78, +#endif + +#ifdef XI2C_79 + XI2C_79, +#endif + +#ifdef XI2C_80 + XI2C_80, +#endif + +#ifdef XI2C_81 + XI2C_81, +#endif + +#ifdef XI2C_82 + XI2C_82, +#endif + +#ifdef XI2C_83 + XI2C_83, +#endif + +#ifdef XI2C_84 + XI2C_84, +#endif + +#ifdef XI2C_85 + XI2C_85, +#endif + +#ifdef XI2C_86 + XI2C_86, +#endif + +#ifdef XI2C_87 + XI2C_87, +#endif + +#ifdef XI2C_88 + XI2C_88, +#endif + +#ifdef XI2C_89 + XI2C_89, +#endif + +#ifdef XI2C_90 + XI2C_90, +#endif + +#ifdef XI2C_91 + XI2C_91, +#endif + +#ifdef XI2C_92 + XI2C_92, +#endif + +#ifdef XI2C_93 + XI2C_93, +#endif + +#ifdef XI2C_94 + XI2C_94, +#endif + +#ifdef XI2C_95 + XI2C_95, +#endif + +#ifdef XI2C_96 + XI2C_96 +#endif +}; + + + +bool I2cEnabled(uint32_t i2c_index) +{ + return (i2c_flg && bitRead(Settings.i2c_drivers[i2c_index / 32], i2c_index % 32)); +} + +void I2cDriverState(void) +{ + ResponseAppend_P(PSTR("\"")); + for (uint32_t i = 0; i < sizeof(kI2cList); i++) { +#ifdef XFUNC_PTR_IN_ROM + uint32_t i2c_driver_id = pgm_read_byte(kI2cList + i); +#else + uint32_t i2c_driver_id = kI2cList[i]; +#endif + bool disabled = false; + if (i2c_driver_id < MAX_I2C_DRIVERS) { + disabled = !bitRead(Settings.i2c_drivers[i2c_driver_id / 32], i2c_driver_id % 32); + } + ResponseAppend_P(PSTR("%s%s%d"), (i) ? "," : "", (disabled) ? "!" : "", i2c_driver_id); + } + ResponseAppend_P(PSTR("\"")); +} + +#endif \ No newline at end of file From 13eaa3c814e9f41fad073ac86ae95d9492d3df95 Mon Sep 17 00:00:00 2001 From: Paul C Diem Date: Mon, 24 Feb 2020 19:10:57 -0600 Subject: [PATCH 4/4] Disable debugging --- tasmota/support_device_groups.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/support_device_groups.ino b/tasmota/support_device_groups.ino index 7036169e8..c94ea2f57 100644 --- a/tasmota/support_device_groups.ino +++ b/tasmota/support_device_groups.ino @@ -22,7 +22,7 @@ */ #ifdef USE_DEVICE_GROUPS -#define DEVICE_GROUPS_DEBUG +//#define DEVICE_GROUPS_DEBUG extern bool udp_connected;