Merge branch 'device-groups' of https://github.com/pcdiem/Tasmota into pwm-dimmer

This commit is contained in:
Paul C Diem 2020-02-24 07:19:28 -06:00
commit b8203a717b
8 changed files with 254 additions and 12 deletions

45
Device_Groups.md Normal file
View File

@ -0,0 +1,45 @@
# Device Groups
The device groups module provides a framework to allow multiple devices to be in a group with values such as power, light color/temperature/brightness, PWM values, sensor values, etc. shared with other devices in the group. For example, with multiple light modules in a device group, the light settings can be changed on one module and the settings will automatically be changed on the other light modules. Dimmer switch modules could be in a device group with light modules and the dimmer switch could control the power, brightness and colors of all the lights in the group. Multiple dimmer switches could be in a device group to form a 3-way/4-way dimmer switch.
UDP 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.
To include device groups support in the build, define USE_DEVICE_GROUPS in your user_config_override. This adds 3.5K to the code size. All devices in a group must be running firmware with device group support and have device groups enabled.
To enable device groups, set Option16 to 1 and **restart the device**.
## Device Groups Operation
The device group name is the MQTT group topic set with the GroupTopic command. All devices in the same IP network with the same group topic are in the same group. Some modules may define additional device groups. For example, if Remote Device Mode is enabled, the PWM Dimmer module defines three devices groups.
The items that are sent to the group and the items that are received from the group are selected with the DevGroupShare command. By default all items are sent and received from the group. An example of when the DevGroupShare command would be used is when you have a group of lights that you control with a dimmer switch and home automation software. You want the dimmer switch to be able to control all items. The home automation software controls each light individually. When it controls the whole group, it actually sends command to each light in the group. If you use the home automation software to turn an individual light on or off or change its 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).
### Commands
<table>
<tr>
<td><strong>Command</strong>
</td>
<td><strong>Parameters</strong>
</td>
</tr>
<tr>
<td>DevGroupShare
</td>
<td><in>,&lt;out> = set incoming and outgoing shared item mask (default = 255,255)
<p>
1 = Power, 2 = Light brightness, 4 = Light fade/speed, 8 = Light scheme, 16 = Light color, 32 = Minimum brightness
</td>
</tr>
<tr>
<td>GroupTopic&lt;x>
</td>
<td>1 = reset device group &lt;x> MQTT group topic to firmware default (MQTT_GRPTOPIC) and restart
<p>
<value> = set device group &lt;x> MQTT group topic (32 chars max) and restart
</td>
</tr>
</table>

View File

@ -490,12 +490,15 @@
#define D_CMND_ZIGBEE_FORGET "Forget"
#define D_CMND_ZIGBEE_SAVE "Save"
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
#define D_CMND_ZIGBEE_ENDPOINT "Endpoint"
#define D_CMND_ZIGBEE_READ "Read"
#define D_CMND_ZIGBEE_SEND "Send"
#define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent"
#define D_JSON_ZIGBEE_RECEIVED "ZbReceived"
#define D_JSON_ZIGBEE_RECEIVED_LEGACY "ZigbeeReceived"
#define D_CMND_ZIGBEE_BIND "Bind"
#define D_CMND_ZIGBEE_PING "Ping"
#define D_JSON_ZIGBEE_PING "ZbPing"
// Commands xdrv_25_A4988_Stepper.ino
#define D_CMND_MOTOR "MOTOR"

View File

@ -490,6 +490,9 @@
// #define USE_DHT12 // [I2cDriver41] Enable DHT12 humidity and temperature sensor (I2C address 0x5C) (+0k7 code)
// #define USE_DS1624 // [I2cDriver42] Enable DS1624, DS1621 temperature sensor (I2C addresses 0x48 - 0x4F) (+1k2 code)
// #define USE_AHT1x // [I2cDriver43] Enable AHT10/15 humidity and temperature sensor (I2C address 0x38) (+0k8 code)
// #define USE_WEMOS_MOTOR_V1 // [I2cDriver44] Enable Wemos motor driver V1 (I2C addresses 0x2D - 0x30) (+0k7 code)
// #define WEMOS_MOTOR_V1_ADDR 0x30 // Default I2C address 0x30
// #define WEMOS_MOTOR_V1_FREQ 1000 // Default frequency
// #define USE_DISPLAY // Add I2C Display Support (+2k code)
#define USE_DISPLAY_MODES1TO5 // Enable display mode 1 to 5 in addition to mode 0

View File

@ -36,7 +36,6 @@ struct device_group_member {
struct device_group {
uint32_t next_ack_check_time;
power_t power;
uint16_t last_full_status_sequence;
uint16_t message_length;
uint8_t message_header_length;
@ -64,7 +63,7 @@ void DeviceGroupsInit(void)
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, "DeviceGroups: error allocating %u-element device group array", device_group_count);
AddLog_P2(LOG_LEVEL_ERROR, "DGR: error allocating %u-element device group array", device_group_count);
Settings.flag.device_groups_enabled = false;
return;
}
@ -132,7 +131,7 @@ void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * la
}
delay(10);
}
AddLog_P2(LOG_LEVEL_ERROR, "DeviceGroups: error sending %s packet", label);
AddLog_P2(LOG_LEVEL_ERROR, "DGR: error sending %s packet", label);
}
void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...)
@ -377,7 +376,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType
// Broadcast the packet.
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, "DeviceGroups: 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, "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);
#endif // DEVICE_GROUPS_DEBUG
SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, "Broadcast");
device_group->next_ack_check_time = millis() + 100;
@ -419,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, "DeviceGroups: error allocating device group member block");
AddLog_P2(LOG_LEVEL_ERROR, "DGR: error allocating device group member block");
return;
}
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, "DeviceGroups: adding member %s (%p)", IPAddressToString(remote_ip), device_group_member);
AddLog_P2(LOG_LEVEL_DEBUG, "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;
@ -538,7 +537,7 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length)
case DGR_ITEM_LIGHT_CHANNELS:
break;
default:
AddLog_P2(LOG_LEVEL_ERROR, "DeviceGroups: ********** invalid item=%u received from device group %s member %s", item, device_group->group_name, IPAddressToString(remote_ip));
AddLog_P2(LOG_LEVEL_ERROR, "DGR: ********** invalid item=%u received from device group %s member %s", item, device_group->group_name, IPAddressToString(remote_ip));
}
#endif // DEVICE_GROUPS_DEBUG
@ -607,7 +606,7 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length)
return;
badmsg:
AddLog_P2(LOG_LEVEL_ERROR, "DeviceGroups: malformed message received from %s", IPAddressToString(remote_ip));
AddLog_P2(LOG_LEVEL_ERROR, "DGR: malformed message received from %s", IPAddressToString(remote_ip));
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, "packet_length=%u, offset=%u", packet_length, message_ptr - packet);
#endif // DEVICE_GROUPS_DEBUG
@ -650,7 +649,7 @@ void DeviceGroupsLoop(void)
if (device_group->next_ack_check_time <= now) {
if (device_group->initial_status_requests_remaining) {
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, "DeviceGroups: sending initial status request for group %s", device_group->group_name);
AddLog_P2(LOG_LEVEL_DEBUG, "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");
@ -672,14 +671,14 @@ void DeviceGroupsLoop(void)
if (device_group_member->timeout_time && device_group_member->timeout_time < now) {
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, "DeviceGroups: removing member %s (%p)", IPAddressToString(device_group_member->ip_address), device_group_member);
AddLog_P2(LOG_LEVEL_DEBUG, "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, "DeviceGroups: 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, "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");
if (!device_group_member->timeout_time) device_group_member->timeout_time = now + 15000;

View File

@ -624,12 +624,17 @@ void WifiConnect(void)
// Re-enabled from 6.3.0.7 with ESP.restart replaced by ESP.reset
void WifiDisconnect(void)
{
#ifdef USE_WIFI_SDK_ERASE // Do not enable with DeepSleep as it will wear out flash
SettingsSdkWifiErase();
#else
// Courtesy of EspEasy
WiFi.persistent(true); // use SDK storage of SSID/WPA parameters
ETS_UART_INTR_DISABLE();
wifi_station_disconnect(); // this will store empty ssid/wpa into sdk storage
ETS_UART_INTR_ENABLE();
WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters
delay(100); // Flush anything in the network buffers.
#endif // USE_WIFI_SDK_ERASE
}
void WifiShutdown(void)

View File

@ -168,6 +168,9 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
//#define USE_PCF8574 // Enable PCF8574 I/O Expander (I2C addresses 0x20 - 0x26 and 0x39 - 0x3F) (+1k9 code)
#define USE_HIH6 // Enable Honywell HIH Humidity and Temperature sensor (I2C address 0x27) (+0k6)
//#define USE_AHT1x // Enable AHT10/15 humidity and temperature sensor (I2C address 0x38) (+0k8 code)
#define USE_WEMOS_MOTOR_V1 // Enable Wemos motor driver V1 (I2C addresses 0x2D - 0x30) (+0k7 code)
#define WEMOS_MOTOR_V1_ADDR 0x30 // Default I2C address 0x30
#define WEMOS_MOTOR_V1_FREQ 1000 // Default frequency
#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)

View File

@ -398,7 +398,7 @@ void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retain
free(mqtt_save);
bool result = MqttClient.publish(romram, mqtt_data, false);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram);
yield(); // #3313
}
#endif // USE_MQTT_AWS_IOT

View File

@ -0,0 +1,184 @@
/*
xdrv_34_wemos_motor_v1.ino - Support for I2C WEMOS motor shield (6612FNG)
Copyright (C) 2020 Denis Sborets and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_I2C
#ifdef USE_WEMOS_MOTOR_V1
/*********************************************************************************************\
* WEMOS_MOTOR_V1 - DC motor driver shield (6612FNG) v 1.0.0
*
* I2C Address: 0x30
*
* Command format:
* driver44 <command>,<motor>,<direction>{,<duty>}
* command:
* RESET - reset a motor shield
* SETMOTOR - seter motor state
* motor:
* 0 - motor A
* 1 - motor B
* direction:
* 0 - short break
* 1 - CCW
* 2 - CW
* 3 - stop
* 4 - standby
* duty (optional):
* 0 - 100% (100% by default)
\*********************************************************************************************/
#define XDRV_34 34
#define XI2C_44 44 // See I2CDEVICES.md
#ifndef WEMOS_MOTOR_V1_ADDR
#define WEMOS_MOTOR_V1_ADDR 0x30 // Default I2C address 0x30
#endif
#ifndef WEMOS_MOTOR_V1_FREQ
#define WEMOS_MOTOR_V1_FREQ 1000 // Default frequency
#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)
{
// Wire.begin();
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(); // stop transmitting
// delay(100);
}
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();
// delay(100);
}
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;
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
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 // USE_WEMOS_MOTOR_V1
#endif // USE_IC2