mirror of https://github.com/arendst/Tasmota.git
889 lines
26 KiB
C++
889 lines
26 KiB
C++
/*
|
|
xdrv_45_shelly_dimmer.ino - shelly dimmer support for Tasmota
|
|
|
|
Copyright (C) 2021 James Turton
|
|
|
|
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_LIGHT
|
|
#ifdef USE_SHELLY_DIMMER
|
|
/*********************************************************************************************\
|
|
* Shelly WiFi Dimmer v1 and v2 (ESP8266 w/ separate co-processor dimmer)
|
|
*
|
|
* {"NAME":"Shelly Dimmer 1","GPIO":[0,3200,0,3232,5568,5600,0,0,192,0,193,288,0,4736],"FLAG":0,"BASE":18}
|
|
* {"NAME":"Shelly Dimmer 2","GPIO":[0,3200,0,3232,5568,5600,0,0,193,0,192,0,320,4736],"FLAG":0,"BASE":18}
|
|
*
|
|
* https://shelly.cloud/wifi-smart-home-automation-shelly-dimmer/
|
|
* https://shelly.cloud/products/shelly-dimmer-2-smart-home-light-controller/
|
|
\*********************************************************************************************/
|
|
|
|
#define XDRV_45 45
|
|
#define XNRG_31 31
|
|
|
|
// #define SHELLY_DIMMER_DEBUG
|
|
// #define SHELLY_HW_DIMMING
|
|
|
|
#define SHD_LOGNAME "SHD: "
|
|
|
|
#ifdef SHELLY_CMDS
|
|
#define D_PRFX_SHD "Shd"
|
|
#define D_CMND_LEADINGEDGE "LeadingEdge"
|
|
#define D_CMND_WARMUPBRIGHTNESS "WarmupBrightness"
|
|
#define D_CMND_WARMUPTIME "WarmupTime"
|
|
#endif // SHELLY_CMDS
|
|
|
|
#define SHD_SWITCH_CMD 0x01
|
|
#define SHD_SWITCH_FADE_CMD 0x02
|
|
#define SHD_POLL_CMD 0x10
|
|
#define SHD_VERSION_CMD 0x11
|
|
#define SHD_SETTINGS_CMD 0x20
|
|
#define SHD_WARMUP_CMD 0x21
|
|
#define SHD_CALIBRATION1_CMD 0x30
|
|
#define SHD_CALIBRATION2_CMD 0x31
|
|
|
|
#define SHD_SWITCH_SIZE 2
|
|
#define SHD_SWITCH_FADE_SIZE 6
|
|
#define SHD_SETTINGS_SIZE 10
|
|
#define SHD_WARMUP_SIZE 4
|
|
#define SHD_CALIBRATION_SIZE 200
|
|
|
|
#define SHD_START_BYTE 0x01
|
|
#define SHD_END_BYTE 0x04
|
|
|
|
#define SHD_BUFFER_SIZE 256
|
|
#define SHD_ACK_TIMEOUT 200 // 200 ms ACK timeout
|
|
|
|
#ifdef SHELLY_FW_UPGRADE
|
|
#include <stm32flash.h>
|
|
#endif // SHELLY_FW_UPGRADE
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *ShdSerial = nullptr;
|
|
|
|
typedef struct
|
|
{
|
|
uint8_t version_major = 0;
|
|
uint8_t version_minor = 0;
|
|
|
|
uint32_t brightness = 0;
|
|
uint32_t power = 0;
|
|
uint32_t fade_rate = 0;
|
|
} SHD_DIMMER;
|
|
|
|
struct SHD
|
|
{
|
|
SHD_DIMMER dimmer;
|
|
uint8_t *buffer = nullptr; // Serial receive buffer
|
|
int byte_counter = 0; // Index in serial receive buffer
|
|
#ifdef USE_ENERGY_SENSOR
|
|
uint32_t last_power_check = 0; // Time when last power was checked
|
|
#endif // USE_ENERGY_SENSOR
|
|
uint32_t start_time = 0;
|
|
uint16_t req_brightness = 0;
|
|
uint16_t req_fade_rate = 0;
|
|
uint16_t leading_edge = 2; // Leading edge = 2 Trailing edge = 1
|
|
uint16_t warmup_brightness = 100; // 10%
|
|
uint16_t warmup_time = 20; // 20ms
|
|
uint8_t counter = 1; // Packet counter
|
|
uint8_t hw_version = 0; // Dimmer 1 = 1 Dimmer 2 = 2
|
|
bool present = false;
|
|
bool req_on = false;
|
|
} Shd;
|
|
|
|
/*********************************************************************************************\
|
|
* SHD firmware update Functions
|
|
\*********************************************************************************************/
|
|
|
|
#ifdef SHELLY_FW_UPGRADE
|
|
|
|
void ShdResetToDFUMode()
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Request co-processor reset in dfu mode"));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
pinMode(Pin(GPIO_SHELLY_DIMMER_RST_INV), OUTPUT);
|
|
digitalWrite(Pin(GPIO_SHELLY_DIMMER_RST_INV), LOW);
|
|
|
|
pinMode(Pin(GPIO_SHELLY_DIMMER_BOOT0), OUTPUT);
|
|
digitalWrite(Pin(GPIO_SHELLY_DIMMER_BOOT0), HIGH);
|
|
|
|
delay(50);
|
|
|
|
// clear in the receive buffer
|
|
while (Serial.available())
|
|
Serial.read();
|
|
|
|
digitalWrite(Pin(GPIO_SHELLY_DIMMER_RST_INV), HIGH); // pull out of reset
|
|
delay(50); // wait 50ms fot the co-processor to come online
|
|
}
|
|
|
|
bool ShdUpdateFirmware(uint8_t* data, uint32_t size)
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Update firmware"));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
bool ret = true;
|
|
stm32_t *stm = stm32_init(&Serial, STREAM_SERIAL, 1);
|
|
if (stm)
|
|
{
|
|
off_t offset = 0;
|
|
uint8_t buffer[256];
|
|
unsigned int len;
|
|
const uint8_t *p_st = data;
|
|
uint32_t addr, start, end;
|
|
stm32_err_t s_err;
|
|
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "STM32 erase memory"));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
stm32_erase_memory(stm, 0, STM32_MASS_ERASE);
|
|
|
|
addr = stm->dev->fl_start;
|
|
end = addr + size;
|
|
while(addr < end && offset < size)
|
|
{
|
|
uint32_t left = end - addr;
|
|
len = sizeof(buffer) > left ? left : sizeof(buffer);
|
|
len = len > size - offset ? size - offset : len;
|
|
|
|
if (len == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
memcpy(buffer, p_st, sizeof(buffer)); // We need 4-byte bounadry flash access
|
|
p_st += sizeof(buffer);
|
|
|
|
s_err = stm32_write_memory(stm, addr, buffer, len);
|
|
if (s_err != STM32_ERR_OK)
|
|
{
|
|
ret = false;
|
|
break;
|
|
}
|
|
|
|
addr += len;
|
|
offset += len;
|
|
}
|
|
stm32_close(stm);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool ShdPresent(void) {
|
|
return Shd.present;
|
|
}
|
|
|
|
uint32_t ShdFlash(uint8_t* data, size_t size) {
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_INFO, PSTR(SHD_LOGNAME "Updating firmware v%u.%u with %u bytes"), Shd.dimmer.version_major, Shd.dimmer.version_minor, size);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
Serial.end();
|
|
Serial.begin(115200, SERIAL_8E1);
|
|
ShdResetToDFUMode();
|
|
bool error = !ShdUpdateFirmware(data, size); // Allow flash access without ESP.flashRead
|
|
Serial.end();
|
|
ShdResetToAppMode();
|
|
Serial.begin(115200, SERIAL_8N1);
|
|
ShdSendVersion();
|
|
|
|
return error;
|
|
}
|
|
|
|
#endif // SHELLY_FW_UPGRADE
|
|
|
|
/*********************************************************************************************\
|
|
* Helper Functions
|
|
\*********************************************************************************************/
|
|
|
|
uint16_t checksum(uint8_t *buf, int len)
|
|
{
|
|
uint16_t chksm = 0;
|
|
for (uint8_t i = 0; i < len; i++)
|
|
chksm += buf[i];
|
|
return chksm;
|
|
}
|
|
|
|
int check_byte()
|
|
{
|
|
uint8_t index = Shd.byte_counter;
|
|
uint8_t byte = Shd.buffer[index];
|
|
|
|
if (index == 0)
|
|
return byte == SHD_START_BYTE;
|
|
|
|
if (index < 4)
|
|
return 1;
|
|
|
|
uint8_t data_length = Shd.buffer[3];
|
|
if ((4 + data_length + 3) > SHD_BUFFER_SIZE)
|
|
return 0;
|
|
|
|
if (index < 4 + data_length + 1)
|
|
return 1;
|
|
|
|
if (index == 4 + data_length + 1)
|
|
{
|
|
uint16_t chksm = (Shd.buffer[index - 1] << 8 | Shd.buffer[index]);
|
|
uint16_t chksm_calc = checksum(&Shd.buffer[1], 3 + data_length);
|
|
if (chksm != chksm_calc)
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Checksum: %x calculated: %x"), chksm, chksm_calc);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (index == 4 + data_length + 2 && byte == SHD_END_BYTE)
|
|
return index;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Internal Functions
|
|
\*********************************************************************************************/
|
|
|
|
bool ShdSerialSend(const uint8_t data[] = nullptr, uint16_t len = 0)
|
|
{
|
|
int retries = 3;
|
|
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(SHD_LOGNAME "Tx Packet %*_H"), len, (uint8_t*)data);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
while (retries--)
|
|
{
|
|
ShdSerial->write(data, len);
|
|
ShdSerial->flush();
|
|
|
|
// wait for any response
|
|
uint32_t snd_time = millis() + SHD_ACK_TIMEOUT;
|
|
while (!TimeReached(snd_time))
|
|
{
|
|
if (ShdSerialInput())
|
|
return true;
|
|
|
|
delay(1);
|
|
}
|
|
|
|
// timeout
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(SHD_LOGNAME "Serial send timeout"));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShdSendCmd(uint8_t cmd, uint8_t *payload, uint8_t len)
|
|
{
|
|
uint8_t data[4 + 72 + 3]; // maximum payload for 0x30 packet is 72
|
|
uint16_t chksm;
|
|
uint8_t pos = 0;
|
|
|
|
data[0] = SHD_START_BYTE;
|
|
data[1] = Shd.counter++;
|
|
data[2] = cmd;
|
|
data[3] = len;
|
|
|
|
pos += 4;
|
|
|
|
if (payload)
|
|
{
|
|
memcpy(data + 4, payload, len);
|
|
pos += len;
|
|
}
|
|
|
|
// calculate checksum from id and onwards
|
|
chksm = checksum(data + 1, 3 + len);
|
|
data[pos++] = chksm >> 8;
|
|
data[pos++] = chksm & 0xff;
|
|
data[pos++] = SHD_END_BYTE;
|
|
|
|
return ShdSerialSend(data, pos);
|
|
}
|
|
|
|
void ShdSetBrightness()
|
|
{
|
|
// Payload format:
|
|
// [0-1] Brightness (%) * 10
|
|
|
|
uint8_t payload[SHD_SWITCH_SIZE];
|
|
|
|
payload[0] = Shd.req_brightness & 0xff;
|
|
payload[1] = Shd.req_brightness >> 8;
|
|
|
|
ShdSendCmd(SHD_SWITCH_CMD, payload, SHD_SWITCH_SIZE);
|
|
}
|
|
|
|
void ShdSetBrightnessFade()
|
|
{
|
|
uint16_t delta = 0;
|
|
if (Shd.req_brightness > Shd.dimmer.brightness)
|
|
delta = (Shd.req_brightness - Shd.dimmer.brightness) * 0.8;
|
|
else
|
|
delta = (Shd.dimmer.brightness - Shd.req_brightness) * 0.8;
|
|
|
|
// Payload format:
|
|
// [0-1] Brightness (%) * 10
|
|
// [2-3] Delta brightness (%) * 8
|
|
// [4-5] 0?? ToDo(jamesturton): Find out what this word is!
|
|
|
|
uint8_t payload[SHD_SWITCH_FADE_SIZE];
|
|
|
|
payload[0] = Shd.req_brightness & 0xff;
|
|
payload[1] = Shd.req_brightness >> 8;
|
|
|
|
payload[2] = delta & 0xff;
|
|
payload[3] = delta >> 8;
|
|
|
|
payload[4] = 0;
|
|
payload[5] = 0;
|
|
|
|
ShdSendCmd(SHD_SWITCH_FADE_CMD, payload, SHD_SWITCH_FADE_SIZE);
|
|
}
|
|
|
|
void ShdSendSettings()
|
|
{
|
|
// as specified in STM32 assembly
|
|
uint16_t fade_rate = Shd.req_fade_rate;
|
|
if (fade_rate > 100)
|
|
fade_rate = 100;
|
|
|
|
// Payload format:
|
|
// [0-1] Brightness (%) * 10
|
|
// [2-3] Leading / trailing edge (1=leading, 2=trailing) ToDo(jamesturton): Are there any other values this can take?
|
|
// [4-5] Fade rate (units unknown) ToDo(jamesturton): Find fade rate units
|
|
// [6-7] Warm up brightness (%) * 10
|
|
// [8-9] Warm up duration (ms)
|
|
|
|
uint8_t payload[SHD_SETTINGS_SIZE];
|
|
|
|
payload[0] = Shd.req_brightness & 0xff;
|
|
payload[1] = Shd.req_brightness >> 8;
|
|
|
|
payload[2] = Shd.leading_edge & 0xff;
|
|
payload[3] = Shd.leading_edge >> 8;
|
|
|
|
payload[4] = fade_rate & 0xff;
|
|
payload[5] = fade_rate >> 8;
|
|
|
|
payload[6] = Shd.warmup_brightness & 0xff;
|
|
payload[7] = Shd.warmup_brightness >> 8;
|
|
|
|
payload[8] = Shd.warmup_time & 0xff;
|
|
payload[9] = Shd.warmup_time >> 8;
|
|
|
|
ShdSendCmd(SHD_SETTINGS_CMD, payload, SHD_SETTINGS_SIZE);
|
|
}
|
|
|
|
void ShdSendWarmup()
|
|
{
|
|
// Payload format:
|
|
// [0-1] Warm up brightness (%) * 10
|
|
// [2-3] Warm up duration (ms)
|
|
|
|
uint8_t payload[SHD_WARMUP_SIZE];
|
|
|
|
payload[0] = Shd.warmup_brightness & 0xff;
|
|
payload[1] = Shd.warmup_brightness >> 8;
|
|
|
|
payload[2] = Shd.warmup_time & 0xff;
|
|
payload[3] = Shd.warmup_time >> 8;
|
|
|
|
ShdSendCmd(SHD_WARMUP_CMD, payload, SHD_WARMUP_SIZE);
|
|
}
|
|
|
|
void ShdSendCalibration(uint16_t brightness, uint16_t func, uint16_t fade_rate)
|
|
{
|
|
// Payload format:
|
|
// ??? ToDo(jamesturton): Find calibration payload format!
|
|
uint8_t payload[SHD_CALIBRATION_SIZE];
|
|
|
|
memset(payload, 0, sizeof(payload));
|
|
|
|
ShdSendCmd(SHD_CALIBRATION1_CMD, payload, SHD_CALIBRATION_SIZE);
|
|
ShdSendCmd(SHD_CALIBRATION2_CMD, payload, SHD_CALIBRATION_SIZE);
|
|
}
|
|
|
|
bool ShdSyncState()
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Serial %p"), ShdSerial);
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Set Brightness Want %d, Is %d"), Shd.req_brightness, Shd.dimmer.brightness);
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Set Fade Want %d, Is %d"), Settings->light_speed, Shd.dimmer.fade_rate);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
if (!ShdSerial)
|
|
return false;
|
|
|
|
#ifdef SHELLY_HW_DIMMING
|
|
// TODO(jamesturton): HW dimming seems to conflict with SW dimming. See how
|
|
// we can disbale SW dimming when using HW dimming.
|
|
if (Settings->light_speed != Shd.dimmer.fade_rate)
|
|
{
|
|
ShdSetBrightnessFade();
|
|
ShdDebugState();
|
|
}
|
|
else
|
|
#endif // SHELLY_HW_DIMMING
|
|
if (Shd.req_brightness != Shd.dimmer.brightness)
|
|
{
|
|
ShdSetBrightness();
|
|
ShdDebugState();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ShdDebugState()
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "MCU v%d.%d, Brightness:%d(%d%%), Power:%d, Fade:%d"),
|
|
Shd.dimmer.version_major, Shd.dimmer.version_minor,
|
|
Shd.dimmer.brightness,
|
|
changeUIntScale(Shd.dimmer.brightness, 0, 1000, 0, 100),
|
|
Shd.dimmer.power,
|
|
Shd.dimmer.fade_rate);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
}
|
|
|
|
bool ShdPacketProcess(void)
|
|
{
|
|
uint8_t pos = 0;
|
|
uint8_t id, cmd, len;
|
|
bool ret = false;
|
|
|
|
if (Shd.buffer[pos++] != SHD_START_BYTE)
|
|
return false;
|
|
|
|
id = Shd.buffer[pos++];
|
|
cmd = Shd.buffer[pos++];
|
|
len = Shd.buffer[pos++];
|
|
|
|
switch (cmd)
|
|
{
|
|
case SHD_POLL_CMD:
|
|
{
|
|
// 1 when returning fade_rate, 0 when returning wattage, brightness?
|
|
uint8_t hw_version_raw = Shd.buffer[pos + 0];
|
|
|
|
uint16_t unknown_0 = Shd.buffer[pos + 1];
|
|
|
|
uint16_t brightness = Shd.buffer[pos + 3] << 8 |
|
|
Shd.buffer[pos + 2];
|
|
|
|
uint32_t wattage_raw = Shd.buffer[pos + 7] << 24 |
|
|
Shd.buffer[pos + 6] << 16 |
|
|
Shd.buffer[pos + 5] << 8 |
|
|
Shd.buffer[pos + 4];
|
|
|
|
uint32_t voltage_raw = Shd.buffer[pos + 11] << 24 |
|
|
Shd.buffer[pos + 10] << 16 |
|
|
Shd.buffer[pos + 9] << 8 |
|
|
Shd.buffer[pos + 8];
|
|
|
|
uint32_t current_raw = Shd.buffer[pos + 15] << 24 |
|
|
Shd.buffer[pos + 14] << 16 |
|
|
Shd.buffer[pos + 13] << 8 |
|
|
Shd.buffer[pos + 12];
|
|
|
|
uint32_t fade_rate = Shd.buffer[pos + 16];
|
|
|
|
float wattage = 0;
|
|
if (wattage_raw > 0)
|
|
wattage = 880373 / (float)wattage_raw;
|
|
|
|
float voltage = 0;
|
|
if (voltage_raw > 0)
|
|
voltage = 347800 / (float)voltage_raw;
|
|
|
|
float current = 0;
|
|
if (current_raw > 0)
|
|
current = 1448 / (float)current_raw;
|
|
|
|
if (hw_version_raw == 0)
|
|
Shd.hw_version = 1;
|
|
else if (hw_version_raw == 1)
|
|
Shd.hw_version = 2;
|
|
|
|
#ifdef USE_ENERGY_SENSOR
|
|
if (Shd.hw_version == 2)
|
|
{
|
|
Energy->current_available = true;
|
|
Energy->voltage_available = true;
|
|
}
|
|
Energy->active_power[0] = wattage;
|
|
Energy->voltage[0] = voltage;
|
|
Energy->current[0] = current;
|
|
if (Shd.last_power_check > 10 && Energy->active_power[0] > 0) {
|
|
uint32_t time_passed = abs(TimePassedSince(Shd.last_power_check)); // Time passed in milliseconds
|
|
uint32_t deca_microWh = (uint32_t)(Energy->active_power[0] * time_passed) / 36;
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "%4_f W is %u dmWh during %u ms"), &Energy->active_power[0], deca_microWh, time_passed);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
Energy->kWhtoday_delta[0] += deca_microWh;
|
|
EnergyUpdateToday();
|
|
}
|
|
Shd.last_power_check = millis();
|
|
#endif // USE_ENERGY_SENSOR
|
|
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "ShdPacketProcess: Dimmer %d Brightness:%d Power:%lu Voltage:%lu Current:%lu Fade:%d"), Shd.hw_version, brightness, wattage_raw, voltage_raw, current_raw, fade_rate);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
Shd.dimmer.brightness = brightness;
|
|
Shd.dimmer.power = wattage_raw;
|
|
Shd.dimmer.fade_rate = fade_rate;
|
|
}
|
|
break;
|
|
case SHD_VERSION_CMD:
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "ShdPacketProcess: Version: %u.%u"), Shd.buffer[pos + 1], Shd.buffer[pos]);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
Shd.dimmer.version_minor = Shd.buffer[pos];
|
|
Shd.dimmer.version_major = Shd.buffer[pos + 1];
|
|
}
|
|
break;
|
|
case SHD_SWITCH_CMD:
|
|
case SHD_SWITCH_FADE_CMD:
|
|
case SHD_SETTINGS_CMD:
|
|
case SHD_WARMUP_CMD:
|
|
case SHD_CALIBRATION1_CMD:
|
|
case SHD_CALIBRATION2_CMD:
|
|
{
|
|
ret = (Shd.buffer[pos] == 0x01);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ShdResetToAppMode()
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Request co-processor reset in app mode"));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
pinMode(Pin(GPIO_SHELLY_DIMMER_RST_INV), OUTPUT);
|
|
digitalWrite(Pin(GPIO_SHELLY_DIMMER_RST_INV), LOW);
|
|
|
|
pinMode(Pin(GPIO_SHELLY_DIMMER_BOOT0), OUTPUT);
|
|
digitalWrite(Pin(GPIO_SHELLY_DIMMER_BOOT0), LOW);
|
|
|
|
delay(50);
|
|
|
|
// clear in the receive buffer
|
|
while (Serial.available())
|
|
Serial.read();
|
|
|
|
digitalWrite(Pin(GPIO_SHELLY_DIMMER_RST_INV), HIGH); // pull out of reset
|
|
delay(50); // wait 50ms fot the co-processor to come online
|
|
}
|
|
|
|
void ShdPoll(void)
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Poll"));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
if (!ShdSerial)
|
|
return;
|
|
|
|
ShdSendCmd(SHD_POLL_CMD, 0, 0);
|
|
ShdSyncState();
|
|
}
|
|
|
|
bool ShdSendVersion(void)
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_INFO, PSTR(SHD_LOGNAME "Sending version command"));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
return ShdSendCmd(SHD_VERSION_CMD, 0, 0);
|
|
}
|
|
|
|
void ShdGetSettings(void)
|
|
{
|
|
char parameters[32];
|
|
// Shd.req_brightness = 0;
|
|
Shd.leading_edge = 0;
|
|
// Shd.req_fade_rate = 0;
|
|
Shd.warmup_brightness = 0;
|
|
Shd.warmup_time = 0;
|
|
if (strstr(SettingsText(SET_SHD_PARAM), ",") != nullptr)
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_INFO, PSTR(SHD_LOGNAME "Loading params: %s"), SettingsText(SET_SHD_PARAM));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
// Shd.req_brightness = atoi(subStr(parameters, SettingsText(SET_SHD_PARAM), ",", 1));
|
|
Shd.leading_edge = atoi(subStr(parameters, SettingsText(SET_SHD_PARAM), ",", 2));
|
|
// Shd.req_fade_rate = atoi(subStr(parameters, SettingsText(SET_SHD_PARAM), ",", 3));
|
|
Shd.warmup_brightness = atoi(subStr(parameters, SettingsText(SET_SHD_PARAM), ",", 4));
|
|
Shd.warmup_time = atoi(subStr(parameters, SettingsText(SET_SHD_PARAM), ",", 5));
|
|
}
|
|
}
|
|
|
|
void ShdSaveSettings(void)
|
|
{
|
|
char parameters[32];
|
|
snprintf_P(parameters, sizeof(parameters), PSTR("%d,%d,%d,%d,%d"),
|
|
Shd.req_brightness, Shd.leading_edge, Shd.req_fade_rate, Shd.warmup_brightness, Shd.warmup_time);
|
|
SettingsUpdateText(SET_SHD_PARAM, parameters);
|
|
}
|
|
|
|
void ShdInit(void)
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_INFO, PSTR(SHD_LOGNAME "Shelly Dimmer Driver Starting Tx %d Rx %d"), Pin(GPIO_TXD), Pin(GPIO_RXD));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
Shd.buffer = (uint8_t *)malloc(SHD_BUFFER_SIZE);
|
|
if (Shd.buffer != nullptr)
|
|
{
|
|
ShdSerial = new TasmotaSerial(Pin(GPIO_RXD), Pin(GPIO_TXD), 2, 0, SHD_BUFFER_SIZE);
|
|
if (ShdSerial->begin(115200))
|
|
{
|
|
if (ShdSerial->hardwareSerial())
|
|
ClaimSerial();
|
|
|
|
#ifdef ESP32
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Serial UART%d"), ShdSerial->getUart());
|
|
#endif
|
|
|
|
ShdSerial->flush();
|
|
|
|
ShdResetToAppMode();
|
|
bool got_version = ShdSendVersion();
|
|
AddLog(LOG_LEVEL_INFO, PSTR(SHD_LOGNAME "Shelly Dimmer Co-processor Version v%u.%u"), Shd.dimmer.version_major, Shd.dimmer.version_minor);
|
|
ShdGetSettings();
|
|
ShdSaveSettings();
|
|
|
|
ShdSendSettings();
|
|
ShdSyncState();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ShdSerialInput(void)
|
|
{
|
|
while (ShdSerial->available())
|
|
{
|
|
yield();
|
|
uint8_t serial_in_byte = ShdSerial->read();
|
|
Shd.buffer[Shd.byte_counter] = serial_in_byte;
|
|
|
|
int check = check_byte();
|
|
|
|
if (check > 1)
|
|
{
|
|
// finished
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
Shd.byte_counter++;
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(SHD_LOGNAME "Rx Packet %*_H"), Shd.byte_counter, Shd.buffer);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
Shd.byte_counter = 0;
|
|
|
|
ShdPacketProcess();
|
|
|
|
return true;
|
|
}
|
|
else if (check == 0)
|
|
{
|
|
// wrong data
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Byte %i of received data frame is invalid. Rx Packet %*_H"),
|
|
Shd.byte_counter, Shd.byte_counter +1, Shd.buffer);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
Shd.byte_counter = 0;
|
|
}
|
|
else
|
|
{
|
|
Shd.byte_counter++;
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* API Functions
|
|
\*********************************************************************************************/
|
|
|
|
bool ShdModuleSelected(void) {
|
|
if (PinUsed(GPIO_SHELLY_DIMMER_BOOT0) && PinUsed(GPIO_SHELLY_DIMMER_RST_INV)) {
|
|
UpdateDevicesPresent(1);
|
|
TasmotaGlobal.light_type = LT_SERIAL1;
|
|
|
|
Shd.present = true;
|
|
}
|
|
return Shd.present;
|
|
}
|
|
|
|
bool ShdSetChannels(void)
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(SHD_LOGNAME "SetChannels %*_H"), XdrvMailbox.data_len, (uint8_t *)XdrvMailbox.data);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
uint16_t brightness = ((uint32_t *)XdrvMailbox.data)[0];
|
|
// Use dimmer_hw_min and dimmer_hw_max to constrain our values if the light should be on
|
|
if (brightness > 0)
|
|
brightness = changeUIntScale(brightness, 0, 255, Settings->dimmer_hw_min * 10, Settings->dimmer_hw_max * 10);
|
|
Shd.req_brightness = brightness;
|
|
|
|
ShdDebugState();
|
|
|
|
return ShdSyncState();
|
|
}
|
|
|
|
bool ShdSetPower(void)
|
|
{
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_INFO, PSTR(SHD_LOGNAME "Set Power, Power 0x%02x"), XdrvMailbox.index);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
|
|
Shd.req_on = (bool)XdrvMailbox.index;
|
|
return ShdSyncState();
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Commands
|
|
\*********************************************************************************************/
|
|
|
|
#ifdef SHELLY_CMDS
|
|
|
|
const char kShdCommands[] PROGMEM = D_PRFX_SHD "|" // Prefix
|
|
D_CMND_LEADINGEDGE "|" D_CMND_WARMUPBRIGHTNESS "|" D_CMND_WARMUPTIME;
|
|
|
|
void (* const ShdCommand[])(void) PROGMEM = {
|
|
&CmndShdLeadingEdge, &CmndShdWarmupBrightness, &CmndShdWarmupTime };
|
|
|
|
void CmndShdLeadingEdge(void)
|
|
{
|
|
if (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1)
|
|
{
|
|
Shd.leading_edge = 2 - XdrvMailbox.payload;
|
|
Settings->shd_leading_edge = XdrvMailbox.payload;
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
if (Shd.leading_edge == 1)
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Set to trailing edge"));
|
|
else
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Set to leading edge"));
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
ShdSendSettings();
|
|
}
|
|
ShdSaveSettings();
|
|
ResponseCmndNumber(Settings->shd_leading_edge);
|
|
}
|
|
|
|
void CmndShdWarmupBrightness(void)
|
|
{
|
|
if ((10 <= XdrvMailbox.payload) && (XdrvMailbox.payload <= 100))
|
|
{
|
|
Shd.warmup_brightness = XdrvMailbox.payload * 10;
|
|
Settings->shd_warmup_brightness = XdrvMailbox.payload;
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Set warmup brightness to %d%%"), XdrvMailbox.payload);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
ShdSendSettings();
|
|
}
|
|
ShdSaveSettings();
|
|
ResponseCmndNumber(Settings->shd_warmup_brightness);
|
|
}
|
|
|
|
void CmndShdWarmupTime(void)
|
|
{
|
|
if ((20 <= XdrvMailbox.payload) && (XdrvMailbox.payload <= 200))
|
|
{
|
|
Shd.warmup_time = XdrvMailbox.payload;
|
|
Settings->shd_warmup_time = XdrvMailbox.payload;
|
|
#ifdef SHELLY_DIMMER_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR(SHD_LOGNAME "Set warmup time to %dms"), XdrvMailbox.payload);
|
|
#endif // SHELLY_DIMMER_DEBUG
|
|
ShdSendSettings();
|
|
}
|
|
ShdSaveSettings();
|
|
ResponseCmndNumber(Settings->shd_warmup_time);
|
|
}
|
|
|
|
#endif // SHELLY_CMDS
|
|
|
|
/*********************************************************************************************\
|
|
* Energy Interface
|
|
\*********************************************************************************************/
|
|
|
|
#ifdef USE_ENERGY_SENSOR
|
|
bool Xnrg31(uint32_t function) {
|
|
bool result = false;
|
|
|
|
if (Shd.present) {
|
|
if (FUNC_PRE_INIT == function) {
|
|
#ifndef SHELLY_VOLTAGE_MON
|
|
Energy->current_available = false;
|
|
Energy->voltage_available = false;
|
|
#endif // SHELLY_VOLTAGE_MON
|
|
Energy->use_overtemp = true; // Use global temperature for overtemp detection
|
|
TasmotaGlobal.energy_driver = XNRG_31;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif // USE_ENERGY_SENSOR
|
|
|
|
/*********************************************************************************************\
|
|
* Driver Interface
|
|
\*********************************************************************************************/
|
|
|
|
bool Xdrv45(uint32_t function) {
|
|
bool result = false;
|
|
|
|
if (FUNC_MODULE_INIT == function) {
|
|
result = ShdModuleSelected();
|
|
} else if (Shd.present) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
ShdPoll();
|
|
break;
|
|
case FUNC_INIT:
|
|
ShdInit();
|
|
break;
|
|
case FUNC_SET_DEVICE_POWER:
|
|
result = ShdSetPower();
|
|
break;
|
|
case FUNC_SET_CHANNELS:
|
|
result = ShdSetChannels();
|
|
break;
|
|
#ifdef SHELLY_CMDS
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kShdCommands, ShdCommand);
|
|
break;
|
|
#endif // SHELLY_CMDS
|
|
case FUNC_ACTIVE:
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif // USE_SHELLY_DIMMER
|
|
#endif // USE_LIGHT
|