Tasmota/tasmota/tasmota.ino.cpp

77284 lines
2.0 MiB

# 1 "C:\\Users\\martin\\AppData\\Local\\Temp\\tmpeqetuoko"
#include <Arduino.h>
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota.ino"
# 29 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota.ino"
#include <core_version.h>
#include "tasmota_version.h"
#include "tasmota.h"
#include "my_user_config.h"
#ifdef USE_MQTT_TLS
#include <t_bearssl.h>
#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 <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#include <StreamString.h>
#include <ArduinoJson.h>
#ifdef USE_ARDUINO_OTA
#include <ArduinoOTA.h>
#ifndef USE_DISCOVERY
#define USE_DISCOVERY
#endif
#endif
#ifdef USE_DISCOVERY
#include <ESP8266mDNS.h>
#endif
#ifdef USE_I2C
#include <Wire.h>
#endif
#ifdef USE_SPI
#include <SPI.h>
#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.h>
LinkedList<String> 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 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 CmndSensor(void);
void CmndDriver(void);
void CmndCrash(void);
void CmndWDT(void);
void CmndBlockedLoop(void);
void CrashDumpClear(void);
bool CrashFlag(void);
void CrashDump(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);
int32_t DriftTime(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);
String GetTime(int 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]);
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);
const __FlashStringHelper* zigbeeFindCommand(const char *command);
inline bool isXYZ(char c);
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_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_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 ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp, uint8_t transacId);
inline int8_t hexValue(char c);
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data);
void CmndZbSend(void);
void CmndZbBind(void);
void CmndZbProbe(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 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);
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 Tx20StartRead(void);
void Tx20Read(void);
void Tx20Init(void);
void Tx20Show(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 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 begin();
bool AHT10Read(void);
unsigned char readStatus(void);
void AHT10Detect(void);
void AHT10EverySecond(void);
void AHT10Show(bool json);
bool Xsns64(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 "C:/shared/sonoff/Git/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
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 "C:/shared/sonoff/Git/Tasmota/tasmota/sendemail.ino"
#ifdef USE_SENDMAIL
#include "sendemail.h"
# 25 "C:/shared/sonoff/Git/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 (mem<SEND_MAIL_MINRAM) {
return 4;
}
while (*buffer==' ') buffer++;
if (*buffer!='[') {
goto exit;
}
buffer++;
endcmd=strchr(buffer,']');
if (!endcmd) {
goto exit;
}
blen=(uint32_t)endcmd-(uint32_t)buffer;
oparams=(char*)calloc(blen+2,1);
if (!oparams) return 4;
params=oparams;
strncpy(oparams,buffer,blen+2);
oparams[blen]=0;
cmd=endcmd+1;
#ifdef DEBUG_EMAIL_PORT
AddLog_P2(LOG_LEVEL_INFO, PSTR("mailsize: %d"),blen);
#endif
mserv=strtok(params,":");
if (!mserv) {
goto exit;
}
pstr=strtok(NULL,":");
if (!pstr) {
goto exit;
}
#ifdef EMAIL_PORT
if (*pstr=='*') {
port=EMAIL_PORT;
} else {
port=atoi(pstr);
}
#else
port=atoi(pstr);
#endif
user=strtok(NULL,":");
if (!user) {
goto exit;
}
passwd=strtok(NULL,":");
if (!passwd) {
goto exit;
}
from=strtok(NULL,":");
if (!from) {
goto exit;
}
to=strtok(NULL,":");
if (!to) {
goto exit;
}
subject=strtok(NULL,"]");
if (!subject) {
goto exit;
}
#ifdef EMAIL_USER
if (*user=='*') {
user=xPSTR(EMAIL_USER);
}
#endif
#ifdef EMAIL_PASSWORD
if (*passwd=='*') {
passwd=xPSTR(EMAIL_PASSWORD);
}
#endif
#ifdef EMAIL_SERVER
if (*mserv=='*') {
mserv=xPSTR(EMAIL_SERVER);
}
#endif
#ifdef DEBUG_EMAIL_PORT
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s - %d - %s - %s"),mserv,port,user,passwd);
#endif
#ifndef MAIL_TIMEOUT
#define MAIL_TIMEOUT 2000
#endif
mail = new SendEmail(mserv,port,user,passwd, MAIL_TIMEOUT, auth);
#ifdef EMAIL_FROM
if (*from=='*') {
from=xPSTR(EMAIL_FROM);
}
#endif
#ifdef DEBUG_EMAIL_PORT
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s - %s - %s - %s"),from,to,subject,cmd);
#endif
if (mail) {
bool result=mail->send(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 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino"
# 20 "C:/shared/sonoff/Git/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);
}
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)
{
# 590 "C:/shared/sonoff/Git/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)
{
# 733 "C:/shared/sonoff/Git/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;
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " %d " D_UNIT_SECTORS), _sectorEnd - _sectorStart);
EsptoolErase(_sectorStart, _sectorEnd);
#endif
}
void SettingsSdkErase(void)
{
WiFi.disconnect(true);
SettingsErase(1);
delay(1000);
}
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;
# 944 "C:/shared/sonoff/Git/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;
# 979 "C:/shared/sonoff/Git/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) {
# 1323 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino"
IPAddress syslog_host_addr;
uint32_t syslog_host_hash = 0;
extern "C" {
extern struct rst_info resetInfo;
}
#include <Ticker.h>
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
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_OSWATCH " FreeRam %d, rssi %d %% (%d dBm), last_run %d"), ESP.getFreeHeap(), WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.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)
{
# 101 "C:/shared/sonoff/Git/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;
# 916 "C:/shared/sonoff/Git/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<unsigned long>(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
# 1559 "C:/shared/sonoff/Git/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)
{
# 1721 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_button.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_command.ino"
# 20 "C:/shared/sonoff/Git/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
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
&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));
}
# 148 "C:/shared/sonoff/Git/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\"}}"),
GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(),
GetTime(3).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}}"),
GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(),
GetTime(3).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
void CmndSensor(void)
{
XsnsCall(FUNC_COMMAND_SENSOR);
}
void CmndDriver(void)
{
XdrvCall(FUNC_COMMAND_DRIVER);
}
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_crash_recorder.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_esptool.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_features.ino"
# 24 "C:/shared/sonoff/Git/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
#ifdef USE_TX20_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_AHT10
feature5 |= 0x10000000;
#endif
feature6 = 0x00000000;
# 568 "C:/shared/sonoff/Git/Tasmota/tasmota/support_features.ino"
}
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino"
# 39 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 *)&sector.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 *)&sector.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; i<num_sectors; i++){
bool success = true;
DEBUG_SENSOR_LOG(PSTR("FLOG: read sector: %u"), i);
_readSector(i);
for (uint32_t j = 0; j<(sizeof(sector.dword_buffer)/4); j++){
if(sector.dword_buffer[j]!=0xffffffff){
success = false;
}
}
if(success){
first_erased_sector = i;
sector.header.physical_start_sector = i;
current_sector = i;
DEBUG_SENSOR_LOG(PSTR("FLOG: first erased sector: %u, now init ..."), first_erased_sector);
return;
}
}
DEBUG_SENSOR_LOG(PSTR("FLOG: no erased sector found"));
first_erased_sector = 0xffff;
}
void FLOG::_searchSaves(void){
found_saved_data = false;
uint32_t s;
if(first_erased_sector==0){
DEBUG_SENSOR_LOG(PSTR("FLOG: sector 0 was erased before, examine sector: %u"), num_sectors);
s = num_sectors;
}
else{
s = first_erased_sector-1;
DEBUG_SENSOR_LOG(PSTR("FLOG: examine sector: %u"), s);
}
_readSector(s);
if(sector.header.magic_word!=MAGIC_WORD_FL){
DEBUG_SENSOR_LOG(PSTR("FLOG: wrong magic number, no saved data found"));
return;
}
sectors_left = sector.header.number + 1;
_saved_header = sector.header;
s = sector.header.physical_start_sector;
DEBUG_SENSOR_LOG(PSTR("FLOG: will check pysical start sector: %u"), s);
_readSector(s);
_showBuffer();
if(sector.header.magic_word!=MAGIC_WORD_FL){
DEBUG_SENSOR_LOG(PSTR("FLOG: wrong magic number, no saved data found"));
sectors_left = 0;
return;
}
if(sector.header.number==0){
DEBUG_SENSOR_LOG(PSTR("FLOG: possible saved data found"));
found_saved_data = true;
}
else{
DEBUG_SENSOR_LOG(PSTR("FLOG: number: %u should be 0"), sector.header.number);
sectors_left = 0;
}
}
void FLOG::_initBuffer(void){
if(!found_saved_data){
sector.header.physical_start_sector = (uint16_t)first_erased_sector;
}
DEBUG_SENSOR_LOG(PSTR("FLOG: init header"));
sector.header.magic_word = MAGIC_WORD_FL;
sector.header.number = 0;
sector.header.buf_pointer = (uint16_t)sizeof(sector.header);
current_sector = first_erased_sector;
ready = true;
_clearBuffer();
}
void FLOG::_showBuffer(void){
DEBUG_SENSOR_LOG(PSTR("FLOG: Header: %c %c"), sector.byte_buffer[0],sector.byte_buffer[1]);
DEBUG_SENSOR_LOG(PSTR("FLOG: V_Start_sector: %u, sector number: %u, pointer: %u "), sector.header.physical_start_sector, sector.header.number, sector.header.buf_pointer);
uint32_t j = 0;
for (uint32_t i = sector.header.buf_pointer-16; i<(sizeof(sector.byte_buffer)); i+=8){
DEBUG_SENSOR_LOG(PSTR("FLOG: buffer: %u %u %u %u %u %u %u %u "), sector.byte_buffer[i], sector.byte_buffer[i+1], sector.byte_buffer[i+2], sector.byte_buffer[i+3], sector.byte_buffer[i+4], sector.byte_buffer[i+5], sector.byte_buffer[i+6], sector.byte_buffer[i+7]);
j++;
if(j>3){
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 "C:/shared/sonoff/Git/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*)&sector.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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino"
# 23 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino"
return totalValue;
}
# 154 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_legacy_cores.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rotary.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rtc.ino"
# 25 "C:/shared/sonoff/Git/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.h>
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 drift_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;
}
int32_t DriftTime(void)
{
return Rtc.drift_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);
}
# 181 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rtc.ino"
String GetDateAndTime(uint8_t time_type)
{
uint32_t time = Rtc.local_time;
switch (time_type) {
case DT_BOOTCOUNT:
time = Settings.bootcount_reset_time;
break;
case DT_ENERGY:
time = Settings.energy_kWhtotal_time;
break;
case DT_UTC:
time = Rtc.utc_time;
break;
case DT_RESTART:
if (Rtc.restart_time == 0) {
return "";
}
time = Rtc.restart_time;
break;
}
String dt = GetDT(time);
if (Settings.flag3.time_append_timezone && (DT_LOCAL == time_type)) {
dt += GetTimeZone();
}
return dt;
}
String GetTime(int type)
{
char stime[25];
uint32_t time = Rtc.utc_time;
if (1 == type) time = Rtc.local_time;
if (2 == type) time = Rtc.daylight_saving_time;
if (3 == type) time = Rtc.standard_time;
snprintf_P(stime, sizeof(stime), sntp_get_real_time(time));
return String(stime);
}
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;
if (Rtc.utc_time > START_VALID_TIME) { Rtc.drift_time = Rtc.ntp_time - Rtc.utc_time; }
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: Drift %d, (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"),
DriftTime(), GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_static_buffer.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_statistics.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_switch.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_switch.ino"
#define SWITCH_V2
#ifdef SWITCH_V2
const uint8_t SWITCH_PROBE_INTERVAL = 10;
#include <Ticker.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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;
}
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();
}
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(WiFi.RSSI()), WiFi.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();
}
}
# 710 "C:/shared/sonoff/Git/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) {
# 915 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/support_udp.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_udp.ino"
#ifdef USE_EMULATION
#define UDP_BUFFER_SIZE 200
#define UDP_MSEARCH_SEND_DELAY 1500
#include <Ticker.h>
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);
#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;
}
}
optimistic_yield(100);
}
}
#endif
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_wifi.ino"
# 24 "C:/shared/sonoff/Git/Tasmota/tasmota/support_wifi.ino"
#ifndef WIFI_RSSI_THRESHOLD
#define WIFI_RSSI_THRESHOLD 5
#endif
#ifndef WIFI_RESCAN_MINUTES
#define WIFI_RESCAN_MINUTES 5
#endif
const uint8_t WIFI_CONFIG_SEC = 180;
const uint8_t WIFI_CHECK_SEC = 5;
const uint8_t WIFI_RETRY_OFFSET_SEC = 20;
#include <ESP8266WiFi.h>
#if LWIP_IPV6
#include <AddrList.h>
#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] = {0};
uint8_t bssid_last[6] = {0};
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)
{
# 156 "C:/shared/sonoff/Git/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) {
uint32_t number_known = 0;
int32_t channel_max = 0;
int8_t ap_max = 3;
uint8_t bssid_max[6];
memcpy((void*) &bssid_max, (void*) &Wifi.bssid, sizeof(bssid_max));
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;
number_known++;
if (rssi_scan > Wifi.best_network_db) {
if (sec_scan == ENC_TYPE_NONE || SettingsText(SET_STAPWD1 + j)) {
memcpy((void*) &bssid_max, (void*) bssid_scan, sizeof(bssid_max));
channel_max = chan_scan;
ap_max = j;
for (uint32_t i = 0; i < sizeof(Wifi.bssid_last); i++) {
if (bssid_scan[i] != Wifi.bssid_last[i]) {
Wifi.best_network_db = (int8_t)rssi_scan;
channel = chan_scan;
ap = j;
memcpy((void*) &Wifi.bssid, (void*) bssid_scan, sizeof(Wifi.bssid));
memcpy((void*) &Wifi.bssid_last, (void*) bssid_scan, sizeof(Wifi.bssid_last));
break;
}
}
}
}
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);
}
if (number_known == 1) {
memset((void*) &Wifi.bssid_last, 0, sizeof(Wifi.bssid_last));
memcpy((void*) &Wifi.bssid, (void*) bssid_max, sizeof(Wifi.bssid));
channel = channel_max;
ap = ap_max;
}
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<uint32_t>(WiFi.localIP()) != 0)) {
#endif
memset((void*) &Wifi.bssid_last, 0, sizeof(Wifi.bssid_last));
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));
}
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_station_dhcpc_start();
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.retry_init / 2) == 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<uint32_t>(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
if (Settings.flag2.emulation) { UdpConnect(); }
#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);
Wifi.retry = Wifi.retry_init;
Wifi.counter = 1;
}
void WifiDisconnect(void)
{
WiFi.persistent(true);
ETS_UART_INTR_DISABLE();
wifi_station_disconnect();
ETS_UART_INTR_ENABLE();
WiFi.persistent(false);
}
void WifiShutdown(void)
{
delay(100);
if (Settings.flag.mqtt_enabled) {
MqttDisconnect();
}
WifiDisconnect();
}
void EspRestart(void)
{
WifiShutdown();
CrashDumpClear();
ESP.reset();
}
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino"
# 24 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino"
#ifdef USE_MQTT_TLS_CA_CERT
#ifndef USE_MQTT_AWS_IOT
# 38 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_01_webserver.ino"
# 20 "C:/shared/sonoff/Git/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 <ESP8266WebServer.h>
#include <DNSServer.h>
#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 =
"<!DOCTYPE html><html lang=\"" D_HTML_LANGUAGE "\" class=\"\">"
"<head>"
"<meta charset='utf-8'>"
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,user-scalable=no\"/>"
"<title>%s - %s</title>"
"<script>"
"var x=null,lt,to,tp,pc='';"
#ifdef USE_JAVASCRIPT_ES6
"eb=s=>document.getElementById(s);"
"qs=s=>document.querySelector(s);"
"sp=i=>eb(i).type=(eb(i).type==='text'?'password':'text');"
"wl=f=>window.addEventListener('load',f);"
;
#else
"function eb(s){"
"return document.getElementById(s);"
"}"
"function qs(s){"
"return document.querySelector(s);"
"}"
"function sp(i){"
"eb(i).type=(eb(i).type==='text'?'password':'text');"
"}"
"function wl(f){"
"window.addEventListener('load',f);"
"}";
#endif
const char HTTP_SCRIPT_COUNTER[] PROGMEM =
"var cn=180;"
"function u(){"
"if(cn>=0){"
"eb('t').innerHTML='" D_RESTART_IN " '+cn+' " D_SECONDS "';"
"cn--;"
"setTimeout(u,1000);"
"}"
"}"
"wl(u);";
const char HTTP_SCRIPT_ROOT[] PROGMEM =
#ifdef USE_SCRIPT_WEB_DISPLAY
"var rfsh=1;"
"function la(p){"
"var a='';"
"if(la.arguments.length==1){"
"a=p;"
"clearTimeout(lt);"
"}"
"if(x!=null){x.abort();}"
"x=new XMLHttpRequest();"
"x.onreadystatechange=function(){"
"if(x.readyState==4&&x.status==200){"
"var s=x.responseText.replace(/{t}/g,\"<table style='width:100%%'>\").replace(/{s}/g,\"<tr><th>\").replace(/{m}/g,\"</th><td>\").replace(/{e}/g,\"</td></tr>\").replace(/{c}/g,\"%%'><div style='text-align:center;font-weight:\");"
"eb('l1').innerHTML=s;"
"}"
"};"
"if (rfsh) {"
"x.open('GET','.?m=1'+a,true);"
"x.send();"
"lt=setTimeout(la,%d);"
"}"
"}"
"function seva(par,ivar){"
"la('&sv='+ivar+'_'+par);"
"}"
"function siva(par,ivar){"
"rfsh=1;"
"la('&sv='+ivar+'_'+par);"
"rfsh=0;"
"}"
"function pr(f){"
"if (f) {"
"lt=setTimeout(la,%d);"
"rfsh=1;"
"} else {"
"clearTimeout(lt);"
"rfsh=0;"
"}"
"}";
#else
"function la(p){"
"var a='';"
"if(la.arguments.length==1){"
"a=p;"
"clearTimeout(lt);"
"}"
"if(x!=null){x.abort();}"
"x=new XMLHttpRequest();"
"x.onreadystatechange=function(){"
"if(x.readyState==4&&x.status==200){"
"var s=x.responseText.replace(/{t}/g,\"<table style='width:100%%'>\").replace(/{s}/g,\"<tr><th>\").replace(/{m}/g,\"</th><td>\").replace(/{e}/g,\"</td></tr>\").replace(/{c}/g,\"%%'><div style='text-align:center;font-weight:\");"
"eb('l1').innerHTML=s;"
"}"
"};"
"x.open('GET','.?m=1'+a,true);"
"x.send();"
"lt=setTimeout(la,%d);"
"}";
#endif
const char HTTP_SCRIPT_ROOT_PART2[] PROGMEM =
"function lc(v,i,p){"
"if(eb('s')){"
"if(v=='h'||v=='d'){"
"var sl=eb('sl4').value;"
"eb('s').style.background='linear-gradient(to right,rgb('+sl+'%%,'+sl+'%%,'+sl+'%%),hsl('+eb('sl2').value+',100%%,50%%))';"
"}"
"}"
"la('&'+v+i+'='+p);"
"}"
"wl(la);";
const char HTTP_SCRIPT_WIFI[] PROGMEM =
"function c(l){"
"eb('s1').value=l.innerText||l.textContent;"
"eb('p1').focus();"
"}";
const char HTTP_SCRIPT_RELOAD[] PROGMEM =
"setTimeout(function(){location.href='.';}," STR(HTTP_RESTART_RECONNECT_TIME) ");";
const char HTTP_SCRIPT_RELOAD_OTA[] PROGMEM =
"setTimeout(function(){location.href='.';}," STR(HTTP_OTA_RESTART_RECONNECT_TIME) ");";
const char HTTP_SCRIPT_CONSOL[] PROGMEM =
"var sn=0,id=0;"
"function l(p){"
"var c,o='',t;"
"clearTimeout(lt);"
"t=eb('t1');"
"if(p==1){"
"c=eb('c1');"
"o='&c1='+encodeURIComponent(c.value);"
"c.value='';"
"t.scrollTop=99999;"
"sn=t.scrollTop;"
"}"
"if(t.scrollTop>=sn){"
"if(x!=null){x.abort();}"
"x=new XMLHttpRequest();"
"x.onreadystatechange=function(){"
"if(x.readyState==4&&x.status==200){"
"var z,d;"
"d=x.responseText.split(/}1/);"
"id=d.shift();"
"if(d.shift()==0){t.value='';}"
"z=d.shift();"
"if(z.length>0){t.value+=z;}"
"t.scrollTop=99999;"
"sn=t.scrollTop;"
"}"
"};"
"x.open('GET','cs?c2='+id+o,true);"
"x.send();"
"}"
"lt=setTimeout(l,%d);"
"return false;"
"}"
"wl(l);";
const char HTTP_MODULE_TEMPLATE_REPLACE[] PROGMEM =
"}2%d'>%s (%d}3";
const char HTTP_SCRIPT_MODULE_TEMPLATE[] PROGMEM =
"var os;"
"function sk(s,g){"
"var o=os.replace(/}2/g,\"<option value='\").replace(/}3/g,\")</option>\");"
"eb('g'+g).innerHTML=o;"
"eb('g'+g).value=s;"
"}"
"function ld(u,f){"
"var x=new XMLHttpRequest();"
"x.onreadystatechange=function(){"
"if(this.readyState==4&&this.status==200){"
"f(this);"
"}"
"};"
"x.open('GET',u,true);"
"x.send();"
"}";
const char HTTP_SCRIPT_TEMPLATE[] PROGMEM =
"var c;"
"function x1(b){"
"var i,j,g,k,o;"
"o=b.responseText.split(/}1/);"
"k=o.shift();"
"if(eb('s1').value==''){"
"eb('s1').value=k;"
"}"
"os=o.shift();"
"as=o.shift();"
"g=o.shift().split(',');"
"j=0;"
"for(i=0;i<13;i++){"
"if(6==i){j=9;}"
"if(8==i){j=12;}"
"sk(g[i],j);"
"j++;"
"}"
"g=o.shift();"
"os=as;"
"sk(g&15,17);"
"g>>=4;"
"for(i=0;i<" STR(GPIO_FLAG_USED) ";i++){"
"p=(g>>i)&1;"
"eb('c'+i).checked=p;"
"}"
"if(" STR(USER_MODULE) "==c){"
"g=o.shift();"
"eb('g99').value=g;"
"}"
"}"
"function st(t){"
"c=t;"
"var a='tp?t='+t;"
"ld(a,x1);"
"}"
"function x2(a){"
"os=a.responseText;"
"sk(17,99);"
"st(" STR(USER_MODULE) ");"
"}"
#ifdef USE_JAVASCRIPT_ES6
"sl=()=>ld('tp?m=1',x2);"
#else
"function sl(){"
"ld('tp?m=1',x2);"
"}"
#endif
"wl(sl);";
const char HTTP_SCRIPT_MODULE1[] PROGMEM =
"function x1(a){"
"os=a.responseText;"
"sk(%d,99);"
"}"
"function x2(b){"
"os=b.responseText;";
const char HTTP_SCRIPT_MODULE2[] PROGMEM =
"}"
"function x3(a){"
"os=a.responseText;"
"sk(%d,17);"
"}"
"function sl(){"
"ld('md?m=1',x1);"
"ld('md?g=1',x2);"
"if(eb('g17')){"
"ld('md?a=1',x3);"
"}"
"}"
"wl(sl);";
const char HTTP_SCRIPT_INFO_BEGIN[] PROGMEM =
"function i(){"
"var s,o=\"";
const char HTTP_SCRIPT_INFO_END[] PROGMEM =
"\";"
"s=o.replace(/}1/g,\"</td></tr><tr><th>\").replace(/}2/g,\"</th><td>\");"
"eb('i').innerHTML=s;"
"}"
"wl(i);";
const char HTTP_HEAD_LAST_SCRIPT[] PROGMEM =
"function jd(){"
"var t=0,i=document.querySelectorAll('input,button,textarea,select');"
"while(i.length>=t){"
"if(i[t]){"
"i[t]['name']=(i[t].hasAttribute('id')&&(!i[t].hasAttribute('name')))?i[t]['id']:i[t]['name'];"
"}"
"t++;"
"}"
"}"
"wl(jd);"
"</script>";
const char HTTP_HEAD_STYLE1[] PROGMEM =
"<style>"
"div,fieldset,input,select{padding:5px;font-size:1em;}"
"fieldset{background:#%06x;}"
"p{margin:0.5em 0;}"
"input{width:100%%;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;background:#%06x;color:#%06x;}"
"input[type=checkbox],input[type=radio]{width:1em;margin-right:6px;vertical-align:-1px;}"
"input[type=range]{width:99%%;}"
"select{width:100%%;background:#%06x;color:#%06x;}"
"textarea{resize:vertical;width:98%%;height:318px;padding:5px;overflow:auto;background:#%06x;color:#%06x;}"
"body{text-align:center;font-family:verdana,sans-serif;background:#%06x;}"
"td{padding:0px;}";
const char HTTP_HEAD_STYLE2[] PROGMEM =
"button{border:0;border-radius:0.3rem;background:#%06x;color:#%06x;line-height:2.4rem;font-size:1.2rem;width:100%%;-webkit-transition-duration:0.4s;transition-duration:0.4s;cursor:pointer;}"
"button:hover{background:#%06x;}"
".bred{background:#%06x;}"
".bred:hover{background:#%06x;}"
".bgrn{background:#%06x;}"
".bgrn:hover{background:#%06x;}"
"a{color:#%06x;text-decoration:none;}"
".p{float:left;text-align:left;}"
".q{float:right;text-align:right;}"
".r{border-radius:0.3em;padding:2px;margin:6px 2px;}";
const char HTTP_HEAD_STYLE3[] PROGMEM =
"</style>"
"</head>"
"<body>"
"<div style='text-align:left;display:inline-block;color:#%06x;min-width:340px;'>"
#ifdef FIRMWARE_MINIMAL
"<div style='text-align:center;color:#%06x;'><h3>" D_MINIMAL_FIRMWARE_PLEASE_UPGRADE "</h3></div>"
#endif
"<div style='text-align:center;color:#%06x;'><noscript>" D_NOSCRIPT "<br></noscript>"
#ifdef LANGUAGE_MODULE_NAME
"<h3>" D_MODULE " %s</h3>"
#else
"<h3>%s " D_MODULE "</h3>"
#endif
"<h2>%s</h2>";
const char HTTP_MSG_SLIDER_GRADIENT[] PROGMEM =
"<div id='%s' class='r' style='background-image:linear-gradient(to right,%s,%s);'>"
"<input id='sl%d' type='range' min='%d' max='%d' value='%d' onchange='lc(\"%c\",%d,value)'>"
"</div>";
const char HTTP_MSG_SLIDER_SHUTTER[] PROGMEM =
"<div><span class='p'>" D_CLOSE "</span><span class='q'>" D_OPEN "</span></div>"
"<div><input type='range' min='0' max='100' value='%d' onchange='lc(\"u\",%d,value)'></div>";
const char HTTP_MSG_RSTRT[] PROGMEM =
"<br><div style='text-align:center;'>" D_DEVICE_WILL_RESTART "</div><br>";
const char HTTP_FORM_LOGIN[] PROGMEM =
"<fieldset>"
"<form method='post' action='/'>"
"<p><b>" D_USER "</b><br><input name='USER1' placeholder='" D_USER "'></p>"
"<p><b>" D_PASSWORD "</b><br><input name='PASS1' type='password' placeholder='" D_PASSWORD "'></p>"
"<br>"
"<button>" D_OK "</button>"
"</form></fieldset>";
const char HTTP_FORM_TEMPLATE[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_TEMPLATE_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='tp'>";
const char HTTP_FORM_TEMPLATE_FLAG[] PROGMEM =
"<p></p>"
"<fieldset><legend><b>&nbsp;" D_TEMPLATE_FLAGS "&nbsp;</b></legend><p>"
"</p></fieldset>";
const char HTTP_FORM_MODULE[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_MODULE_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='md'>"
"<p></p><b>" D_MODULE_TYPE "</b> (%s)<br><select id='g99'></select><br>"
"<br><table>";
const char HTTP_FORM_WIFI[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_WIFI_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='wi'>"
"<p><b>" D_AP1_SSID "</b> (" STA_SSID1 ")<br><input id='s1' placeholder='" STA_SSID1 "' value='%s'></p>"
"<p><b>" D_AP1_PASSWORD "</b><input type='checkbox' onclick='sp(\"p1\")'><br><input id='p1' type='password' placeholder='" D_AP1_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><b>" D_AP2_SSID "</b> (" STA_SSID2 ")<br><input id='s2' placeholder='" STA_SSID2 "' value='%s'></p>"
"<p><b>" D_AP2_PASSWORD "</b><input type='checkbox' onclick='sp(\"p2\")'><br><input id='p2' type='password' placeholder='" D_AP2_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><b>" D_HOSTNAME "</b> (%s)<br><input id='h' placeholder='%s' value='%s'></p>"
"<p><b>" D_CORS_DOMAIN "</b><input id='c' placeholder='" CORS_DOMAIN "' value='%s'></p>";
const char HTTP_FORM_LOG1[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_LOGGING_PARAMETERS "&nbsp;</b>"
"</legend><form method='get' action='lg'>";
const char HTTP_FORM_LOG2[] PROGMEM =
"<p><b>" D_SYSLOG_HOST "</b> (" SYS_LOG_HOST ")<br><input id='lh' placeholder='" SYS_LOG_HOST "' value='%s'></p>"
"<p><b>" D_SYSLOG_PORT "</b> (" STR(SYS_LOG_PORT) ")<br><input id='lp' placeholder='" STR(SYS_LOG_PORT) "' value='%d'></p>"
"<p><b>" D_TELEMETRY_PERIOD "</b> (" STR(TELE_PERIOD) ")<br><input id='lt' placeholder='" STR(TELE_PERIOD) "' value='%d'></p>";
const char HTTP_FORM_OTHER[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_OTHER_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='co'>"
"<p></p>"
"<fieldset><legend><b>&nbsp;" D_TEMPLATE "&nbsp;</b></legend>"
"<p><input id='t1' placeholder='" D_TEMPLATE "' value='%s'></p>"
"<p><input id='t2' type='checkbox'%s><b>" D_ACTIVATE "</b></p>"
"</fieldset>"
"<br>"
"<b>" D_WEB_ADMIN_PASSWORD "</b><input type='checkbox' onclick='sp(\"wp\")'><br><input id='wp' type='password' placeholder='" D_WEB_ADMIN_PASSWORD "' value='" D_ASTERISK_PWD "'><br>"
"<br>"
"<input id='b1' type='checkbox'%s><b>" D_MQTT_ENABLE "</b><br>"
"<br>";
const char HTTP_FORM_END[] PROGMEM =
"<br>"
"<button name='save' type='submit' class='button bgrn'>" D_SAVE "</button>"
"</form></fieldset>";
const char HTTP_FORM_RST[] PROGMEM =
"<div id='f1' style='display:block;'>"
"<fieldset><legend><b>&nbsp;" D_RESTORE_CONFIGURATION "&nbsp;</b></legend>";
const char HTTP_FORM_UPG[] PROGMEM =
"<div id='f1' style='display:block;'>"
"<fieldset><legend><b>&nbsp;" D_UPGRADE_BY_WEBSERVER "&nbsp;</b></legend>"
"<form method='get' action='u1'>"
"<br><b>" D_OTA_URL "</b><br><input id='o' placeholder='OTA_URL' value='%s'><br>"
"<br><button type='submit'>" D_START_UPGRADE "</button></form>"
"</fieldset><br><br>"
"<fieldset><legend><b>&nbsp;" D_UPGRADE_BY_FILE_UPLOAD "&nbsp;</b></legend>";
const char HTTP_FORM_RST_UPG[] PROGMEM =
"<form method='post' action='u2' enctype='multipart/form-data'>"
"<br><input type='file' name='u2'><br>"
"<br><button type='submit' onclick='eb(\"f1\").style.display=\"none\";eb(\"f2\").style.display=\"block\";this.form.submit();'>" D_START " %s</button></form>"
"</fieldset>"
"</div>"
"<div id='f2' style='display:none;text-align:center;'><b>" D_UPLOAD_STARTED " ...</b></div>";
const char HTTP_FORM_CMND[] PROGMEM =
"<br><textarea readonly id='t1' cols='340' wrap='off'></textarea><br><br>"
"<form method='get' onsubmit='return l(1);'>"
"<input id='c1' placeholder='" D_ENTER_COMMAND "' autofocus><br>"
"</form>";
const char HTTP_TABLE100[] PROGMEM =
"<table style='width:100%%'>";
const char HTTP_COUNTER[] PROGMEM =
"<br><div id='t' style='text-align:center;'></div>";
const char HTTP_END[] PROGMEM =
"<div style='text-align:right;font-size:11px;'><hr/><a href='https://bit.ly/tasmota' target='_blank' style='color:#aaa;'>Tasmota %s " D_BY " Theo Arends</a></div>"
"</div>"
"</body>"
"</html>";
const char HTTP_DEVICE_CONTROL[] PROGMEM = "<td style='width:%d%%'><button onclick='la(\"&o=%d\");'>%s%s</button></td>";
const char HTTP_DEVICE_STATE[] PROGMEM = "<td style='width:%d{c}%s;font-size:%dpx'>%s</div></td>";
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<uint32_t>(WiFi.localIP()) != 0);
bool sip = (static_cast<uint32_t>(WiFi.softAPIP()) != 0);
WSContentSend_P(PSTR("<h4>%s%s (%s%s%s)</h4>"),
my_hostname,
(Wifi.mdns_begun) ? ".local" : "",
(lip) ? WiFi.localIP().toString().c_str() : "",
(lip && sip) ? ", " : "",
(sip) ? WiFi.softAPIP().toString().c_str() : "");
}
WSContentSend_P(PSTR("</div>"));
}
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("<p><form action='%s' method='get' onsubmit='return confirm(\"%s\");'><button name='%s' class='button bred'>%s</button></form></p>"),
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("<p><form action='%s' method='get'><button>%s</button></form></p>"),
GetTextIndexed(action, sizeof(action), title_index, kButtonAction),
GetTextIndexed(title, sizeof(title), title_index, kButtonTitle));
}
}
void WSContentSpaceButton(uint32_t title_index)
{
WSContentSend_P(PSTR("<div></div>"));
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("<div style='text-align:center;'><b>" D_CONFIGURATION_SAVED "</b><br>"));
if (2 == type) {
WSContentSend_P(PSTR("<br>" D_TRYING_TO_CONNECT "<br>"));
}
WSContentSend_P(PSTR("</div>"));
}
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("<div id='l1' name='l1'></div>"));
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("<tr>"));
#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("</tr></table>"));
}
#ifdef USE_SONOFF_RF
if (SONOFF_BRIDGE == my_module_type) {
WSContentSend_P(HTTP_TABLE100);
WSContentSend_P(PSTR("<tr>"));
uint32_t idx = 0;
for (uint32_t i = 0; i < 4; i++) {
if (idx > 0) { WSContentSend_P(PSTR("</tr><tr>")); }
for (uint32_t j = 0; j < 4; j++) {
idx++;
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), idx);
WSContentSend_P(PSTR("<td style='width:25%%'><button onclick='la(\"&k=%d\");'>%s</button></td>"), idx,
(strlen(SettingsText(SET_BUTTON1 + idx -1))) ? SettingsText(SET_BUTTON1 + idx -1) : stemp);
}
}
WSContentSend_P(PSTR("</tr></table>"));
}
#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("</table>"));
if (devices_present) {
WSContentSend_P(PSTR("{t}<tr>"));
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("</tr></table>"));
}
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("<tr><td><b>" D_TEMPLATE_NAME "</b></td><td style='width:200px'><input id='s1' placeholder='" D_TEMPLATE_NAME "'></td></tr>"
"<tr><td><b>" D_BASE_TYPE "</b></td><td><select id='g99' onchange='st(this.value)'></select></td></tr>"
"</table>"
"<hr/>"));
WSContentSend_P(HTTP_TABLE100);
for (uint32_t i = 0; i < 17; i++) {
if ((i < 6) || ((i > 8) && (i != 11))) {
WSContentSend_P(PSTR("<tr><td><b><font color='#%06x'>" D_GPIO "%d</font></b></td><td%s><select id='g%d'></select></td></tr>"),
((9==i)||(10==i)) ? WebColor(COL_TEXT_WARNING) : WebColor(COL_TEXT), i, (0==i) ? " style='width:200px'" : "", i);
}
}
WSContentSend_P(PSTR("<tr><td><b><font color='#%06x'>" D_ADC "0</font></b></td><td><select id='g17'></select></td></tr>"), WebColor(COL_TEXT));
WSContentSend_P(PSTR("</table>"));
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("<font color='#%06x'>ESP8285</font>"), WebColor(COL_TEXT_WARNING));
WSContentSend_P(PSTR("<tr><td style='width:190px'>%s <b>" D_GPIO "%d</b> %s</td><td style='width:176px'><select id='g%d'></select></td></tr>"),
(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("<tr><td>%s <b>" D_ADC "0</b></td><td style='width:176px'><select id='g17'></select></td></tr>"), (WEMOS==my_module_type)?"A0":"");
}
#endif
WSContentSend_P(PSTR("</table>"));
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 = "&amp;|&gt;|&lt;|&quot;|&apos;";
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; }
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]), WiFi.RSSI(indices[i]));
int quality = WifiGetRssiAsQuality(WiFi.RSSI(indices[i]));
int auth = WiFi.encryptionType(indices[i]);
char encryption[20];
WSContentSend_P(PSTR("<div><a href='#p' onclick='c(this)'>%s</a>&nbsp;(%d)&nbsp<span class='q'>%s %d%% (%d dBm)</span></div>"),
HtmlEscape(WiFi.SSID(indices[i])).c_str(),
WiFi.channel(indices[i]),
GetTextIndexed(encryption, sizeof(encryption), auth +1, kEncryptionType),
quality, WiFi.RSSI(indices[i])
);
delay(0);
}
WSContentSend_P(PSTR("<br>"));
}
} else {
WSContentSend_P(PSTR("<div><a href='/wi?scan='>" D_SCAN_FOR_WIFI_NETWORKS "</a></div><br>"));
}
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("<p><b>%s</b> (%s)<br><select id='l%d'>"),
GetTextIndexed(stemp1, sizeof(stemp1), idx, kLoggingOptions),
GetTextIndexed(stemp2, sizeof(stemp2), dlevel[idx], kLoggingLevels),
idx);
for (uint32_t i = LOG_LEVEL_NONE; i <= LOG_LEVEL_DEBUG_MORE; i++) {
WSContentSend_P(PSTR("<option%s value='%d'>%d %s</option>"),
(i == llevel) ? " selected" : "", i, i,
GetTextIndexed(stemp1, sizeof(stemp1), i, kLoggingLevels));
}
WSContentSend_P(PSTR("</select></p>"));
}
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("<b>" D_FRIENDLY_NAME " %d</b> (" FRIENDLY_NAME "%s)<br><input id='a%d' placeholder='" FRIENDLY_NAME "%s' value='%s'><p></p>"),
i +1,
(i) ? stemp : "",
i,
(i) ? stemp : "",
SettingsText(SET_FRIENDLYNAME1 + i));
}
#ifdef USE_EMULATION
WSContentSend_P(PSTR("<p></p><fieldset><legend><b>&nbsp;" D_EMULATION "&nbsp;</b></legend><p>"));
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("<input id='r%d' name='b2' type='radio' value='%d'%s><b>%s</b> %s<br>"),
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("</p></fieldset>"));
#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("<div style='text-align:center;'>" D_CONFIGURATION_RESET "</div>"));
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("<table style='width:100%%'><tr><th>"));
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&nbsp;"));
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(WiFi.RSSI()), WiFi.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<uint32_t>(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<uint32_t>(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&nbsp;"));
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&nbsp;"));
#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&nbsp;"));
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("</td></tr></table>"));
WSContentSend_P(HTTP_SCRIPT_INFO_END);
WSContentSendStyle();
WSContentSend_P(PSTR("<style>td{padding:0px 5px;}</style>"
"<div id='i' name='i'></div>"));
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("<div style='text-align:center;'><b>" D_UPGRADE_STARTED " ...</b></div>"));
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("<div style='text-align:center;'><b>" D_UPLOAD " <font color='#"));
if (Web.upload_error) {
WSContentSend_P(PSTR("%06x'>" D_FAILED "</font></b><br><br>"), 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 "</font></b><br>"), 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("</div><br>"));
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++);
# 2672 "C:/shared/sonoff/Git/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
if (Settings.flag2.emulation) { PollUdp(); }
#endif
break;
case FUNC_COMMAND:
result = DecodeCommand(kWebCommands, WebCommand);
break;
}
return result;
}
#endif
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_02_mqtt.ino"
# 20 "C:/shared/sonoff/Git/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 <base64.hpp>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_02_mqtt.ino"
#include <PubSubClient.h>
#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_INFO, 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)
{
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
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 =
"<p><form action='" WEB_HANDLE_MQTT "' method='get'><button>" D_CONFIGURE_MQTT "</button></form></p>";
const char HTTP_FORM_MQTT1[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_MQTT_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='" WEB_HANDLE_MQTT "'>"
"<p><b>" D_HOST "</b> (" MQTT_HOST ")<br><input id='mh' placeholder='" MQTT_HOST" ' value='%s'></p>"
"<p><b>" D_PORT "</b> (" STR(MQTT_PORT) ")<br><input id='ml' placeholder='" STR(MQTT_PORT) "' value='%d'></p>"
"<p><b>" D_CLIENT "</b> (%s)<br><input id='mc' placeholder='%s' value='%s'></p>";
const char HTTP_FORM_MQTT2[] PROGMEM =
"<p><b>" D_USER "</b> (" MQTT_USER ")<br><input id='mu' placeholder='" MQTT_USER "' value='%s'></p>"
"<p><b>" D_PASSWORD "</b><input type='checkbox' onclick='sp(\"mp\")'><br><input id='mp' type='password' placeholder='" D_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><b>" D_TOPIC "</b> = %%topic%% (%s)<br><input id='mt' placeholder='%s' value='%s'></p>"
"<p><b>" D_FULL_TOPIC "</b> (%s)<br><input id='mf' placeholder='%s' value='%s'></p>";
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_03_energy.ino"
# 20 "C:/shared/sonoff/Git/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 <Ticker.h>
#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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
#ifdef USE_LIGHT
# 124 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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) {
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<Light.subtype; i++) {
uint32_t adjust = change8to10(Settings.rgbwwTable[i]);
cur_col_10[i] = changeUIntScale(cur_col_10[i], 0, 1023, 0, adjust);
}
}
for (uint32_t i = 0; i < LST_MAX; i++) {
cur_col_10[i] = (cur_col_10[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]);
}
}
}
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;
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;
}
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;
}
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;
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_05_irremote.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_05_irremote.ino"
#if defined(USE_IR_REMOTE) && !defined(USE_IR_REMOTE_FULL)
#define XDRV_05 5
#include <IRremoteESP8266.h>
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.h>
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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_05_irremote_full.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_05_irremote_full.ino"
#ifdef USE_IR_REMOTE_FULL
#define XDRV_05 5
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <IRrecv.h>
#include <IRutils.h>
#include <IRac.h>
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<uint16_t*>(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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_06_snfbridge.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_07_domoticz.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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
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 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;
}
# 391 "C:/shared/sonoff/Git/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 =
"<p><form action='" WEB_HANDLE_DOMOTICZ "' method='get'><button>" D_CONFIGURE_DOMOTICZ "</button></form></p>";
const char HTTP_FORM_DOMOTICZ[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_DOMOTICZ_PARAMETERS "&nbsp;</b></legend>"
"<form method='post' action='" WEB_HANDLE_DOMOTICZ "'>"
"<table>";
const char HTTP_FORM_DOMOTICZ_RELAY[] PROGMEM =
"<tr><td style='width:260px'><b>" D_DOMOTICZ_IDX " %d</b></td><td style='width:70px'><input id='r%d' placeholder='0' value='%d'></td></tr>"
"<tr><td style='width:260px'><b>" D_DOMOTICZ_KEY_IDX " %d</b></td><td style='width:70px'><input id='k%d' placeholder='0' value='%d'></td></tr>";
const char HTTP_FORM_DOMOTICZ_SWITCH[] PROGMEM =
"<tr><td style='width:260px'><b>" D_DOMOTICZ_SWITCH_IDX " %d</b></td><td style='width:70px'><input id='s%d' placeholder='0' value='%d'></td></tr>";
const char HTTP_FORM_DOMOTICZ_SENSOR[] PROGMEM =
"<tr><td style='width:260px'><b>" D_DOMOTICZ_SENSOR_IDX " %d</b> %s</td><td style='width:70px'><input id='l%d' placeholder='0' value='%d'></td></tr>";
const char HTTP_FORM_DOMOTICZ_TIMER[] PROGMEM =
"<tr><td style='width:260px'><b>" D_DOMOTICZ_UPDATE_TIMER "</b> (" STR(DOMOTICZ_UPDATE_TIMER) ")</td><td style='width:70px'><input id='ut' placeholder='" STR(DOMOTICZ_UPDATE_TIMER) "' value='%d'></td></tr>";
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("</table>"));
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_08_serial_bridge.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_09_timers.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_09_timers.ino"
#ifdef USE_TIMERS
# 39 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 =
"<p><form action='" WEB_HANDLE_TIMER "' method='get'><button>" D_CONFIGURE_TIMER "</button></form></p>";
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<o.length;i++){o[i].style.cssText=\"background:#%06x;color:#%06x;font-weight:normal;\"}"
"e.style.cssText=\"background:#%06x;color:#%06x;font-weight:bold;\";"
"s=pt[ct];"
#ifdef USE_SUNRISE
"p=(s>>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+=\"<button type='button' class='tl' onclick='ot(\"+i+\",this)'\"+b+\">\"+(i+1)+\"</button>\""
"}"
"eb('bt').innerHTML=s;"
"if(%d>0){"
"eb('oa').innerHTML=\"<b>" D_TIMER_OUTPUT "</b>&nbsp;<span><select style='width:60px;' id='d1'></select></span>&emsp;<b>" D_TIMER_ACTION "</b>&nbsp;<select style='width:99px;' id='p1'></select>\";"
"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=\"<b>" D_TIMER_ACTION "</b> " 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+=\"<input id='w\"+i+\"' type='checkbox'><b>\"+a.substring(i*3,(i*3)+3)+\"</b> \"}"
"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 =
"<fieldset style='min-width:470px;text-align:center;'>"
"<legend style='text-align:left;'><b>&nbsp;" D_TIMER_PARAMETERS "&nbsp;</b></legend>"
"<form method='post' action='" WEB_HANDLE_TIMER "' onsubmit='return st();'>"
"<br><input id='e0' type='checkbox'%s><b>" D_TIMER_ENABLE "</b><br><br><hr>"
"<input id='t0' value='";
const char HTTP_FORM_TIMER2[] PROGMEM =
"' hidden><div id='bt'></div><br><br><br>"
"<div id='oa' name='oa'></div><br>"
"<div>"
"<input id='a0' type='checkbox'><b>" D_TIMER_ARM "</b>&emsp;"
"<input id='r0' type='checkbox'><b>" D_TIMER_REPEAT "</b>"
"</div><br>"
"<div>";
#ifdef USE_SUNRISE
const char HTTP_FORM_TIMER3[] PROGMEM =
"<fieldset style='width:%dpx;margin:auto;text-align:left;border:0;'>"
"<input id='b0' name='rd' type='radio' value='0' onclick='gt();'><b>" D_TIMER_TIME "</b><br>"
"<input id='b1' name='rd' type='radio' value='1' onclick='gt();'><b>" D_SUNRISE "</b> (%s)<br>"
"<input id='b2' name='rd' type='radio' value='2' onclick='gt();'><b>" D_SUNSET "</b> (%s)<br>"
"</fieldset>"
"<p></p>"
"<span><select style='width:46px;' id='dr'></select></span>"
"&nbsp;";
#else
const char HTTP_FORM_TIMER3[] PROGMEM =
"<b>" D_TIMER_TIME "</b>&nbsp;";
#endif
const char HTTP_FORM_TIMER4[] PROGMEM =
"<span><select style='width:60px;' id='ho'></select></span>"
"&nbsp;" D_HOUR_MINUTE_SEPARATOR "&nbsp;"
"<span><select style='width:60px;' id='mi'></select></span>"
"&emsp;<b>+/-</b>&nbsp;"
"<span><select style='width:60px;' id='mw'></select></span>"
"</div><br>"
"<div id='ds' name='ds'></div>";
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
#ifdef USE_RULES
#ifndef USE_SCRIPT
# 67 "C:/shared/sonoff/Git/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 <LinkedList.h>
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 <LinkedList.h>
typedef struct {
String Event;
String Topic;
String Key;
} MQTT_Subscription;
LinkedList<MQTT_Subscription> 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 "C:/shared/sonoff/Git/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, "=", &parameter);
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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<float> object_values;
LinkedList<int8_t> 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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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<bool> values;
LinkedList<int8_t> 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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
#ifdef USE_SCRIPT
#ifndef USE_RULES
# 40 "C:/shared/sonoff/Git/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 <SPI.h>
#include <SD.h>
#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 <LinkedList.h>
typedef struct {
String Event;
String Topic;
String Key;
} MQTT_Subscription;
LinkedList<MQTT_Subscription> subscriptions;
#endif
#ifdef USE_DISPLAY
#ifdef USE_TOUCH_BUTTONS
#include <renderer.h>
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; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_timer) {
float *fp=&glob_script_mem.fvars[vtp[count].index];
if (*fp>0) {
*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 <Eeprom24C128_256.h>
#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 (numperm<SCRIPT_MAXPERM) {
vtypes[vars].bits.is_permanent=1;
numperm++;
}
} else {
vtypes[vars].bits.is_permanent=0;
}
if (*lp=='t' && *(lp+1)==':') {
lp+=2;
vtypes[vars].bits.is_timer=1;
} else {
vtypes[vars].bits.is_timer=0;
}
if (*lp=='i' && *(lp+1)==':') {
lp+=2;
vtypes[vars].bits.is_autoinc=1;
} else {
vtypes[vars].bits.is_autoinc=0;
}
if ((*lp=='m' || *lp=='M') && *(lp+1)==':') {
uint8_t flg=*lp;
lp+=2;
if (flg=='M') mfilt[numflt].numvals=8;
else mfilt[numflt].numvals=5;
vtypes[vars].bits.is_filter=1;
mfilt[numflt].index=0;
if (flg=='M') {
mfilt[numflt].numvals|=0x80;
}
vtypes[vars].index=numflt;
numflt++;
if (numflt>MAXFILT) {
return -6;
}
} else {
vtypes[vars].bits.is_filter=0;
}
*vnp_p++=vnames_p;
while (lp<op) {
*vnames_p++=*lp++;
}
*vnames_p++=0;
op++;
if (*op!='"') {
float fv;
if (*op=='0' && *(op+1)=='x') {
op+=2;
fv=strtol(op,&op,16);
} else {
fv=CharToFloat(op);
}
fvalues[nvars]=fv;
vtypes[vars].bits.is_string=0;
if (!vtypes[vars].bits.is_filter) vtypes[vars].index=nvars;
nvars++;
if (nvars>MAXNVARS) {
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; count<numflt; count++) {
fsize+=sizeof(struct M_FILT)+((mfilt[count].numvals&0x7f)-1)*sizeof(float);
}
uint16_t script_mem_size=
(sizeof(float)*nvars)+
(sizeof(float)*nvars)+
(vnames_p-vnames)+
(sizeof(uint8_t)*vars)+
(glob_script_mem.max_ssize*svars)+
(sizeof(struct T_INDEX)*vars)+
fsize;
script_mem_size+=16;
uint8_t *script_mem;
script_mem=(uint8_t*)calloc(script_mem_size,1);
if (!script_mem) {
return -4;
}
glob_script_mem.script_mem=script_mem;
glob_script_mem.script_mem_size=script_mem_size;
glob_script_mem.fvars=(float*)script_mem;
uint16_t size=sizeof(float)*nvars;
memcpy(script_mem,fvalues,size);
script_mem+=size;
glob_script_mem.s_fvars=(float*)script_mem;
size=sizeof(float)*nvars;
memcpy(script_mem,fvalues,size);
script_mem+=size;
glob_script_mem.mfilt=(struct M_FILT*)script_mem;
script_mem+=fsize;
glob_script_mem.type=(struct T_INDEX*)script_mem;
size=sizeof(struct T_INDEX)*vars;
memcpy(script_mem,vtypes,size);
script_mem+=size;
char *namep=(char*)script_mem;
glob_script_mem.glob_vnp=(char*)script_mem;
size=vnames_p-vnames;
memcpy(script_mem,vnames,size);
script_mem+=size;
glob_script_mem.vnp_offset=(uint8_t*)script_mem;
size=vars*sizeof(uint8_t);
script_mem+=size;
char *snamep=(char*)script_mem;
glob_script_mem.glob_snp=(char*)script_mem;
size=glob_script_mem.max_ssize*svars;
script_mem+=size;
uint16_t index=0;
uint8_t *cp=glob_script_mem.vnp_offset;
for (count=0;count<vars;count++) {
*cp++=index;
while (*namep) {
index++;
namep++;
}
namep++;
index++;
if (index>255) {
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; count<svars;count++) {
strcpy(cp1,sp);
sp+=strlen(sp)+1;
cp1+=glob_script_mem.max_ssize;
}
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
for (count=0; count<numflt; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
mflp->numvals=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; count<glob_script_mem.numvars; count++) {
if (dvtp[count].bits.is_string) {
} else {
char string[32];
dtostrfd(glob_script_mem.fvars[dvtp[count].index],glob_script_mem.script_dprec,string);
toLog(string);
}
}
#endif
float *fp=(float*)glob_script_mem.script_pram;
struct T_INDEX *vtp=glob_script_mem.type;
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
uint8_t index=vtp[count].index;
if (!isnan(*fp)) {
glob_script_mem.fvars[index]=*fp;
} else {
*fp=glob_script_mem.fvars[index];
}
fp++;
}
}
sp=(char*)fp;
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_permanent && vtp[count].bits.is_string) {
uint8_t index=vtp[count].index;
char *dp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize);
uint8_t slen=strlen(sp);
strlcpy(dp,sp,glob_script_mem.max_ssize);
sp+=slen+1;
}
}
#ifdef USE_SCRIPT_FATFS
if (!glob_script_mem.script_sd_found) {
if (SD.begin(USE_SCRIPT_FATFS)) {
glob_script_mem.script_sd_found=1;
} else {
glob_script_mem.script_sd_found=0;
}
}
for (uint8_t cnt=0;cnt<SFS_MAX;cnt++) {
glob_script_mem.file_flags[cnt].is_open=0;
}
#endif
#if SCRIPT_DEBUG>0
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;cnt<len;cnt++) {
if (cnt>Settings.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; hcnt<len/2+1; hcnt++) {
for (uint8_t mcnt=0; mcnt<len; mcnt++) {
flg=0;
for (uint8_t icnt=0; icnt<index; icnt++) {
if (ind[icnt]==mcnt) {
flg=1;
}
}
if (!flg) {
if (array[mcnt]<min) {
min=array[mcnt];
mind=mcnt;
}
}
}
ind[index]=mind;
index++;
min=FLT_MAX;
}
return array[ind[len/2]];
}
float *Get_MFAddr(uint8_t index,uint8_t *len) {
*len=0;
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
for (uint8_t count=0; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
*len=mflp->numvals&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; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
uint8_t maxind=mflp->numvals&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; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
uint8_t maxind=mflp->numvals&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; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
if (mflp->numvals&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; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
if (mflp->numvals&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 ])=+-/*%><!^&|}";
for (count=0; count<sizeof(vname); count++) {
char iob=lp[count];
if (!iob || strchr(term,iob)) {
vname[count]=0;
break;
}
vname[count]=iob;
len+=1;
}
if (!vname[0]) {
*vtype=VAR_NV;
tind->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; count<glob_script_mem.numvars; count++) {
char *cp=glob_script_mem.glob_vnp+glob_script_mem.vnp_offset[count];
uint8_t slen=strlen(cp);
if (slen==olen && *cp==dvnam[0]) {
if (!strncmp(cp,dvnam,olen)) {
uint8_t index=vtp[count].index;
*tind=vtp[count];
tind->index=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<char*>(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;cnt++) {
if (!glob_script_mem.file_flags[cnt].is_open) {
if (mode==0) {
glob_script_mem.files[cnt]=SD.open(str,FILE_READ);
if (glob_script_mem.files[cnt].isDirectory()) {
glob_script_mem.files[cnt].rewindDirectory();
glob_script_mem.file_flags[cnt].is_dir=1;
} else {
glob_script_mem.file_flags[cnt].is_dir=0;
}
}
else glob_script_mem.files[cnt]=SD.open(str,FILE_WRITE);
if (glob_script_mem.files[cnt]) {
fvar=cnt;
glob_script_mem.file_flags[cnt].is_open=1;
} else {
AddLog_P(LOG_LEVEL_INFO,PSTR("file open failed"));
}
break;
}
}
lp++;
len=0;
goto exit;
}
if (!strncmp(vname,"fc(",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].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;i<GPIO_SENSOR_END;i++) {
if (pin[i]==gpiopin) {
fvar=i;
len++;
goto exit;
}
}
fvar=999;
goto exit;
}
if (!strncmp(vname,"prefix1",7)) {
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX1),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"prefix2",7)) {
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX2),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"prefix3",7)) {
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX3),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"pow(",4)) {
float fvar1;
lp=GetNumericResult(lp+4,OPER_EQU,&fvar1,0);
SCRIPT_SKIP_SPACES
float fvar2;
lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
lp++;
fvar=FastPrecisePow(fvar1,fvar2);
len=0;
goto exit;
}
if (!strncmp(vname,"pwr[",4)) {
GetNumericResult(vname+4,OPER_EQU,&fvar,0);
uint8_t index=fvar;
if (index<=devices_present) {
fvar=bitRead(power,index-1);
} else {
fvar=-1;
}
len+=1;
goto exit;
}
if (!strncmp(vname,"pc[",3)) {
GetNumericResult(vname+3,OPER_EQU,&fvar,0);
uint8_t index=fvar;
if (index<1 || index>MAX_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 <cont.h>
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 <cont.h>
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<dstsize;count++) {
if (*cp=='%') {
cp++;
if (*cp=='%') {
dstbuf[count]=*cp++;
} else {
if (isdigit(*cp)) {
dprec=*cp&0xf;
cp++;
} else {
dprec=glob_script_mem.script_dprec;
}
cp=isvar(cp,&vtype,&ind,&fvar,string,0);
if (vtype!=VAR_NV) {
if (vtype==NUM_RES || (vtype&STYPE)==0) {
dtostrfd(fvar,dprec,string);
} else {
}
uint8_t slen=strlen(string);
if (count+slen<dstsize-1) {
strcpy(&dstbuf[count],string);
count+=slen-1;
}
cp++;
} else {
strcpy(&dstbuf[count],"???");
count+=2;
while (*cp!='%') {
if (*cp==0 || *cp==SCRIPT_EOL) {
dstbuf[count+1]=0;
return;
}
cp++;
}
cp++;
}
}
} else {
if (*cp=='\\') {
cp++;
if (*cp=='n') {
dstbuf[count]='\n';
} else if (*cp=='r') {
dstbuf[count]='\r';
} else if (*cp=='\\') {
dstbuf[count]='\\';
} else {
dstbuf[count]=*cp;
}
} else {
dstbuf[count]=*cp;
}
if (*cp==0) {
break;
}
cp++;
}
}
dstbuf[count]=0;
}
void toLog(const char *str) {
if (!str) return;
AddLog_P(LOG_LEVEL_INFO, str);
}
void toLogN(const char *cp,uint8_t len) {
if (!cp) return;
char str[32];
if (len>=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=(*dfvar<fvar1);
break;
case OPER_LOWEQU:
res=(*dfvar<=fvar1);
break;
case OPER_GRT:
res=(*dfvar>fvar1);
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<IF_NEST-1) ifstck++;
if_state[ifstck]=1;
if_result[ifstck]=0;
if (ifstck==1) if_exe[ifstck]=1;
else if_exe[ifstck]=if_exe[ifstck-1];
and_or=0;
} else if (!strncmp(lp,"then",4) && if_state[ifstck]==1) {
lp+=4;
if_state[ifstck]=2;
if (if_exe[ifstck-1]) if_exe[ifstck]=if_result[ifstck];
} else if (!strncmp(lp,"else",4) && if_state[ifstck]==2) {
lp+=4;
if_state[ifstck]=3;
if (if_exe[ifstck-1]) if_exe[ifstck]=!if_result[ifstck];
} else if (!strncmp(lp,"endif",5) && if_state[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<SCRIPT_CMDMEM/2-2; count++) {
if (!*lp || *lp=='\r' || *lp=='\n') {
cmd[count]=0;
break;
}
cmd[count]=*lp++;
}
char *tmp=cmdmem+SCRIPT_CMDMEM/2;
Replace_Cmd_Vars(cmd,tmp,SCRIPT_CMDMEM/2);
if (!strncmp(tmp,"print",5) || pflg) {
if (pflg) toLog(tmp);
else toLog(&tmp[5]);
} else {
if (!sflag) {
tasm_cmd_activ=1;
AddLog_P2(glob_script_mem.script_loglevel&0x7f, PSTR("Script: performs \"%s\""), tmp);
} else if (sflag==2) {
} else {
tasm_cmd_activ=1;
svmqtt=Settings.flag.mqtt_enabled;
swll=Settings.weblog_level;
Settings.flag.mqtt_enabled=0;
Settings.weblog_level=0;
}
ExecuteCommand((char*)tmp, SRC_RULE);
tasm_cmd_activ=0;
if (sflag==1) {
Settings.flag.mqtt_enabled=svmqtt;
Settings.weblog_level=swll;
}
}
if (cmdmem) free(cmdmem);
}
lp=slp;
goto next_line;
} else if (!strncmp(lp,"=#",2)) {
lp+=1;
char *slp=lp;
uint8_t plen=0;
while (*lp) {
if (*lp=='\n'|| *lp=='\r'|| *lp=='(') {
break;
}
lp++;
plen++;
}
if (fromscriptcmd) {
char *sp=glob_script_mem.scriptptr;
glob_script_mem.scriptptr=glob_script_mem.scriptptr_bu;
Run_Scripter(slp,plen,0);
glob_script_mem.scriptptr=sp;
} else {
Run_Scripter(slp,plen,0);
}
lp=slp;
goto next_line;
} else if (!strncmp(lp,"=(",2)) {
lp+=2;
char str[128];
str[0]='>';
lp=GetStringResult(lp,OPER_EQU,&str[1],0);
lp++;
execute_script(str);
}
if (if_state[ifstck]==1) {
lp=Evaluate_expression(lp,and_or,&if_result[ifstck],jo);
SCRIPT_SKIP_SPACES
if (*lp=='{' && if_state[ifstck]==1) {
lp+=1;
if_state[ifstck]=2;
if (if_exe[ifstck-1]) if_exe[ifstck]=if_result[ifstck];
}
goto next_line;
} else {
lp=isvar(lp,&vtype,&ind,&sysvar,0,0);
if (vtype!=VAR_NV) {
globvindex=ind.index;
globaindex=last_findex;
uint8_t index=glob_script_mem.type[ind.index].index;
if ((vtype&STYPE)==0) {
if (ind.bits.settable || ind.bits.is_filter) {
dfvar=&sysvar;
if (ind.bits.settable) {
sysv_type=ind.index;
} else {
sysv_type=0;
}
} else {
dfvar=&glob_script_mem.fvars[index];
sysv_type=0;
}
numeric=1;
lp=getop(lp,&lastop);
char *slp=lp;
glob_script_mem.glob_error=0;
lp=GetNumericResult(lp,OPER_EQU,&fvar,jo);
if (glob_script_mem.glob_error==1) {
lp=isvar(slp,&vtype,&ind,0,cmpstr,jo);
fvar=CharToFloat(cmpstr);
}
switch (lastop) {
case OPER_EQU:
if (glob_script_mem.var_not_found) {
if (!js) toLogEOL("var not found: ",lp);
goto next_line;
}
*dfvar=fvar;
break;
case OPER_PLSEQU:
*dfvar+=fvar;
break;
case OPER_MINEQU:
*dfvar-=fvar;
break;
case OPER_MULEQU:
*dfvar*=fvar;
break;
case OPER_DIVEQU:
*dfvar/=fvar;
break;
case OPER_PERCEQU:
*dfvar=fmodf(*dfvar,fvar);
break;
case OPER_ANDEQU:
*dfvar=(uint32_t)*dfvar&(uint32_t)fvar;
break;
case OPER_OREQU:
*dfvar=(uint32_t)*dfvar|(uint32_t)fvar;
break;
case OPER_XOREQU:
*dfvar=(uint32_t)*dfvar^(uint32_t)fvar;
break;
default:
break;
}
glob_script_mem.type[globvindex].bits.changed=1;
if (glob_script_mem.type[globvindex].bits.is_filter) {
if (globaindex>=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; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
uint8_t index=vtp[count].index;
mlen+=sizeof(float);
if (mlen>PMEM_SIZE) {
vtp[count].bits.is_permanent=0;
return;
}
*fp++=glob_script_mem.fvars[index];
}
}
char *cp=(char*)fp;
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_permanent && vtp[count].bits.is_string) {
uint8_t index=vtp[count].index;
char *sp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize);
uint8_t slen=strlen(sp);
mlen+=slen+1;
if (mlen>PMEM_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 =
"<p><form action='" WEB_HANDLE_SCRIPT "' method='get'><button>" D_CONFIGURE_SCRIPT "</button></form></p>";
const char HTTP_FORM_SCRIPT[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_SCRIPT "&nbsp;</b></legend>"
"<form method='post' action='" WEB_HANDLE_SCRIPT "'>";
const char HTTP_FORM_SCRIPT1[] PROGMEM =
"<div style='text-align:right' id='charNum'> </div>"
"<input style='width:3%%;' id='c%d' name='c%d' type='checkbox'%s><b>" D_SCRIPT_ENABLE "</b><br/>"
"<br><textarea id='t1' name='t1' rows='8' cols='80' maxlength='%d' style='font-size: 12pt' >";
const char HTTP_FORM_SCRIPT1b[] PROGMEM =
"</textarea>"
"<script type='text/javascript'>"
"eb('charNum').innerHTML='-';"
"var ta=eb('t1');"
"ta.addEventListener('keydown',function(e){"
"e = e || window.event;"
"var ml=this.getAttribute('maxlength');"
"var cl=this.value.length;"
"if(cl>=ml){"
"eb('charNum').innerHTML='" D_SCRIPT_CHARS_NO_MORE "';"
"}else{"
"eb('charNum').innerHTML=ml-cl+' " D_SCRIPT_CHARS_LEFT "';"
"}"
#if 0
"if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.which === 86) {"
"var paste = window.clipboardData.getData('Text');"
"var out=\"\";"
"var re=/\\r\\n|\\n\\r|\\n|\\r/g;"
"var allLines=paste.replace(re,\"\\n\").split(\"\\n\");"
"allLines.forEach((line) => {"
"if(line.length>0) {"
"if(line.charAt(0)!=';'){"
"out+=line+'\\n';"
"}"
"}"
"});"
"alert(out);"
"}"
#endif
"return true;"
"});"
#ifdef SCRIPT_STRIP_COMMENTS
"ta.addEventListener('paste',function(e){"
"let paste = (e.clipboardData || window.clipboardData).getData('text');"
"var ml=this.getAttribute('maxlength');"
"if(paste.length>=ml){"
"var out=\"\";"
"var re=/\\r\\n|\\n\\r|\\n|\\r/g;"
"var allLines=paste.replace(re,\"\\n\").split(\"\\n\");"
"allLines.forEach((line) => {"
"if(line.length>0) {"
"if(line.charAt(0)!=';'){"
"out+=line+'\\n';"
"}"
"}"
"});"
"event.preventDefault();"
"eb('t1').textContent=out;"
"}"
# 3145 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
"});"
#endif
"</script>";
const char HTTP_SCRIPT_FORM_END[] PROGMEM =
"<br/>"
"<button name='save' type='submit' formmethod='post' formenctype='multipart/form-data' formaction='/ta' class='button bgrn'>" D_SAVE "</button>"
"</form></fieldset>";
#ifdef USE_SCRIPT_FATFS
const char HTTP_FORM_SCRIPT1c[] PROGMEM =
"<button name='d%d' type='submit' class='button bgrn'>" D_SCRIPT_DOWNLOAD " '%s'</button>";
#ifdef SDCARD_DIR
const char HTTP_FORM_SCRIPT1d[] PROGMEM =
"<button method='post' name='upl' type='submit' class='button bgrn'>" D_SDCARD_DIR "</button>";
#else
const char HTTP_FORM_SCRIPT1d[] PROGMEM =
"<button method='post' name='upl' type='submit' class='button bgrn'>" D_SCRIPT_UPLOAD_FILES "</button>";
#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 =
"<div id='f1' name='f1' style='display:block;'>"
"<fieldset><legend><b>&nbsp;%s" "&nbsp;</b></legend>";
const char HTTP_FORM_FILE_UPG[] PROGMEM =
"<form method='post' action='u3' enctype='multipart/form-data'>"
"<br/><input type='file' name='u3'><br/>"
"<br/><button type='submit' onclick='eb(\"f1\").style.display=\"none\";eb(\"f2\").style.display=\"block\";this.form.submit();'>" D_START " %s</button></form>";
const char HTTP_FORM_FILE_UPGb[] PROGMEM =
"</fieldset>"
"</div>"
"<div id='f2' name='f2' style='display:none;text-align:center;'><b>" D_UPLOAD_STARTED " ...</b></div>";
const char HTTP_FORM_SDC_DIRa[] PROGMEM =
"<div style='text-align:left'>";
const char HTTP_FORM_SDC_DIRb[] PROGMEM =
"<pre><a href='%s' file='%s'>%s</a> %d</pre>";
const char HTTP_FORM_SDC_DIRd[] PROGMEM =
"<pre><a href='%s' file='%s'>%s</a></pre>";
const char HTTP_FORM_SDC_DIRc[] PROGMEM =
"</div>";
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;cnt<depth;cnt++) {
*cp++='-';
}
sprintf(cp,format,entry.name());
if (entry.isDirectory()) {
snprintf_P(npath,sizeof(npath),HTTP_FORM_SDC_HREF,WiFi.localIP().toString().c_str(),pp,entry.name());
WSContentSend_P(HTTP_FORM_SDC_DIRd,npath,entry.name(),name);
uint8_t plen=strlen(path);
if (plen>1) {
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("<div style='text-align:center;'><b>" D_UPLOAD " <font color='#"));
WSContentSend_P(PSTR("%06x'>" D_SUCCESSFUL "</font></b><br/>"), WebColor(COL_TEXT_SUCCESS));
WSContentSend_P(PSTR("</div><br/>"));
WSContentSend_P(PSTR("<p><form action='%s' method='get'><button>%s</button></form></p>"),"/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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; i<sizeof(line); i++) {
if (!*cp || *cp=='\n' || *cp=='\r') {
*cp=0;
break;
}
cp++;
}
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
cp=tmp;
cp=strchr(cp,',');
if (!cp) break;
*cp=0;
strlcpy(hue_script[hue_devs].name,tmp,HUE_DEV_NSIZE);
cp++;
while (*cp==' ') cp++;
hue_script[hue_devs].type=*cp;
for (vindex=0;vindex<HUE_DEV_MVNUM;vindex++) {
hue_script[hue_devs].index[vindex]=0;
}
vindex=0;
while (1) {
cp=strchr(cp,',');
if (!cp) break;
cp++;
while (*cp==' ') cp++;
vindex==0xff;
if (!strncmp(cp,"on=",3)) {
cp+=3;
vindex=0;
} else if (!strncmp(cp,"bri=",4)) {
cp+=4;
vindex=1;
} else if (!strncmp(cp,"hue=",4)) {
cp+=4;
vindex=2;
} else if (!strncmp(cp,"sat=",4)) {
cp+=4;
vindex=3;
} else if (!strncmp(cp,"ct=",3)) {
cp+=3;
vindex=4;
} else {
vindex==0xff;
break;
}
if (vindex!=0xff) {
struct T_INDEX ind;
uint8_t vtype;
char vname[16];
for (uint32_t cnt=0;cnt<sizeof(vname)-1;cnt++) {
if (*cp==',' || *cp==0) {
vname[cnt]=0;
break;
}
vname[cnt]=*cp++;
}
isvar(vname,&vtype,&ind,0,0,0);
if (vtype!=VAR_NV) {
if (vtype==NUM_RES || (vtype&STYPE)==0) {
hue_script[hue_devs].vindex[vindex]=ind.index;
hue_script[hue_devs].index[vindex]=glob_script_mem.type[ind.index].index+1;
} else {
}
}
}
}
if (response) {
if (devices_present) {
*response+=",\"";
}
else {
if (hue_devs>0) *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<XdrvMailbox.data_len; count++) {
if (XdrvMailbox.data[count]==';') XdrvMailbox.data[count]='\n';
}
execute_script(XdrvMailbox.data);
}
}
return serviced;
}
snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\",\"Free\":%d}"),command, GetStateText(bitRead(Settings.rule_enabled,0)),glob_script_mem.script_size-strlen(glob_script_mem.script_ram));
#ifdef SUPPORT_MQTT_EVENT
} else if (CMND_SUBSCRIBE == command_code) {
String result = ScriptSubscribe(XdrvMailbox.data, XdrvMailbox.data_len);
Response_P(S_JSON_COMMAND_SVALUE, command, result.c_str());
} else if (CMND_UNSUBSCRIBE == command_code) {
String result = ScriptUnsubscribe(XdrvMailbox.data, XdrvMailbox.data_len);
Response_P(S_JSON_COMMAND_SVALUE, command, result.c_str());
#endif
}
return serviced;
}
#ifdef USE_SCRIPT_FATFS
uint16_t xFAT_DATE(uint16_t year, uint8_t month, uint8_t day) {
return (year - 1980) << 9 | month << 5 | day;
}
uint16_t xFAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
return hour << 11 | minute << 5 | second >> 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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 =
"<button type='submit' style=\"width:%d%%\" onclick='seva(%d,\"%s\")'>%s</button>";
const char SCRIPT_MSG_BUTTONa_TBL[] PROGMEM =
"<td style=\"width:%d%%\"><button type='submit' onclick='seva(%d,\"%s\")'>%s</button></td>";
const char SCRIPT_MSG_BUTTONb[] PROGMEM =
"<img width=\"%d%%\"></img>";
const char SCRIPT_MSG_BUT_START[] PROGMEM =
"<div>";
const char SCRIPT_MSG_BUT_START_TBL[] PROGMEM =
"<table style='width:100%%'><tr>";
const char SCRIPT_MSG_BUT_STOP[] PROGMEM =
"</div>";
const char SCRIPT_MSG_BUT_STOP_TBL[] PROGMEM =
"</tr></table>";
const char SCRIPT_MSG_SLIDER[] PROGMEM =
"<div><span class='p'>%s</span><center><b>%s</b><span class='q'>%s</span></div>"
"<div><input type='range' min='%d' max='%d' value='%d' onchange='seva(value,\"%s\")'></div>";
const char SCRIPT_MSG_CHKBOX[] PROGMEM =
"<div><center><label><b>%s</b><input type='checkbox' %s onchange='seva(%d,\"%s\")'></label></div>";
const char SCRIPT_MSG_TEXTINP[] PROGMEM =
"<div><center><label><b>%s</b><input type='text' value='%s' style='width:200px' onfocusin='pr(0)' onfocusout='pr(1)' onchange='siva(value,\"%s\")'></label></div>";
const char SCRIPT_MSG_NUMINP[] PROGMEM =
"<div><center><label><b>%s</b><input min='%s' max='%s' step='%s' value='%s' type='number' style='width:200px' onfocusin='pr(0)' onfocusout='pr(1)' onchange='siva(value,\"%s\")'></label></div>";
void ScriptGetVarname(char *nbuf,char *sp, uint32_t blen) {
uint32_t cnt;
for (cnt=0;cnt<blen-1;cnt++) {
if (*sp==' ' || *sp==')') {
break;
}
nbuf[cnt]=*sp++;
}
nbuf[cnt]=0;
}
void ScriptWebShow(void) {
uint8_t web_script=Run_Scripter(">W",-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; i<sizeof(line); i++) {
if (!*cp || *cp=='\n' || *cp=='\r') {
*cp=0;
break;
}
cp++;
}
char *lin=line;
if (*lin=='@') {
lin++;
optflg=1;
} else {
optflg=0;
}
if (!strncmp(lin,"sl(",3)) {
char *lp=lin;
float min;
lp=GetNumericResult(lp+3,OPER_EQU,&min,0);
SCRIPT_SKIP_SPACES
float max;
lp=GetNumericResult(lp,OPER_EQU,&max,0);
SCRIPT_SKIP_SPACES
float val;
char *slp=lp;
lp=GetNumericResult(lp,OPER_EQU,&val,0);
SCRIPT_SKIP_SPACES
char vname[16];
ScriptGetVarname(vname,slp,sizeof(vname));
char left[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,left,0);
SCRIPT_SKIP_SPACES
char mid[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,mid,0);
SCRIPT_SKIP_SPACES
char right[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,right,0);
SCRIPT_SKIP_SPACES
WSContentSend_PD(SCRIPT_MSG_SLIDER,left,mid,right,(uint32_t)min,(uint32_t)max,(uint32_t)val,vname);
} else if (!strncmp(lin,"ck(",3)) {
char *lp=lin+3;
char *slp=lp;
float val;
lp=GetNumericResult(lp,OPER_EQU,&val,0);
SCRIPT_SKIP_SPACES
char vname[16];
ScriptGetVarname(vname,slp,sizeof(vname));
char label[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,label,0);
const char *cp;
uint8_t uval;
if (val>0) {
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;cnt<bcnt;cnt++) {
float val;
char *slp=lp;
lp=GetNumericResult(lp,OPER_EQU,&val,0);
SCRIPT_SKIP_SPACES
char vname[16];
ScriptGetVarname(vname,slp,sizeof(vname));
SCRIPT_SKIP_SPACES
char ontxt[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,ontxt,0);
SCRIPT_SKIP_SPACES
char offtxt[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,offtxt,0);
char *cp;
uint8_t uval;
if (val>0) {
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<bcnt-1) {
if (!optflg) WSContentSend_PD(SCRIPT_MSG_BUTTONb,2);
}
lp+=4;
}
if (optflg) WSContentSend_PD(SCRIPT_MSG_BUT_STOP_TBL);
else WSContentSend_PD(SCRIPT_MSG_BUT_STOP);
} else if (!strncmp(lin,"tx(",3)) {
char *lp=lin+3;
char *slp=lp;
char str[SCRIPT_MAXSSIZE];
lp=ForceStringVar(lp,str);
SCRIPT_SKIP_SPACES
char label[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,label,0);
char vname[16];
ScriptGetVarname(vname,slp,sizeof(vname));
WSContentSend_PD(SCRIPT_MSG_TEXTINP,label,str,vname);
} else if (!strncmp(lin,"nm(",3)) {
char *lp=lin;
float min;
lp=GetNumericResult(lp+3,OPER_EQU,&min,0);
SCRIPT_SKIP_SPACES
float max;
lp=GetNumericResult(lp,OPER_EQU,&max,0);
SCRIPT_SKIP_SPACES
float step;
lp=GetNumericResult(lp,OPER_EQU,&step,0);
SCRIPT_SKIP_SPACES
float val;
char *slp=lp;
lp=GetNumericResult(lp,OPER_EQU,&val,0);
SCRIPT_SKIP_SPACES
char vname[16];
ScriptGetVarname(vname,slp,sizeof(vname));
char label[SCRIPT_MAXSSIZE];
lp=GetStringResult(lp,OPER_EQU,label,0);
char vstr[16],minstr[16],maxstr[16],stepstr[16];
dtostrfd(val,4,vstr);
dtostrfd(min,4,minstr);
dtostrfd(max,4,maxstr);
dtostrfd(step,4,stepstr);
WSContentSend_PD(SCRIPT_MSG_NUMINP,label,minstr,maxstr,stepstr,vstr,vname);
} else {
Replace_Cmd_Vars(lin,tmp,sizeof(tmp));
if (optflg) {
WSContentSend_PD(PSTR("<div>%s</div>"),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; i<sizeof(line); i++) {
if (!*cp || *cp=='\n' || *cp=='\r') {
*cp=0;
break;
}
cp++;
}
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
client->println(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; i<sizeof(line); i++) {
if (!*cp || *cp=='\n' || *cp=='\r') {
*cp=0;
break;
}
cp++;
}
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
ResponseAppend_P(PSTR("%s"),tmp);
}
if (*lp==SCRIPT_EOL) {
lp++;
} else {
lp = strchr(lp, SCRIPT_EOL);
if (!lp) break;
lp++;
}
}
}
}
#endif
bool Xdrv10(uint8_t function)
{
bool result = false;
switch (function) {
case FUNC_PRE_INIT:
glob_script_mem.script_ram=Settings.rules[0];
glob_script_mem.script_size=MAX_SCRIPT_SIZE;
glob_script_mem.flags=0;
glob_script_mem.script_pram=(uint8_t*)Settings.script_pram[0];
glob_script_mem.script_pram_size=PMEM_SIZE;
#ifdef USE_BUTTON_EVENT
for (uint32_t cnt=0;cnt<MAX_KEYS;cnt++) {
script_button[cnt]=-1;
}
#endif
#ifdef USE_24C256
#ifndef USE_SCRIPT_FATFS
if (I2cEnabled(XI2C_37)) {
if (I2cSetDevice(EEPROM_ADDRESS)) {
char *script;
script=(char*)calloc(EEP_SCRIPT_SIZE+4,1);
if (!script) break;
glob_script_mem.script_ram=script;
glob_script_mem.script_size=EEP_SCRIPT_SIZE;
EEP_READ(0,EEP_SCRIPT_SIZE,script);
if (*script==0xff) {
memset(script,EEP_SCRIPT_SIZE,0);
}
script[EEP_SCRIPT_SIZE-1]=0;
glob_script_mem.script_pram=(uint8_t*)Settings.rules[0];
glob_script_mem.script_pram_size=MAX_SCRIPT_SIZE;
glob_script_mem.flags=1;
I2cSetActiveFound(EEPROM_ADDRESS, "EEPROM");
}
}
#endif
#endif
#ifdef USE_SCRIPT_FATFS
if (SD.begin(USE_SCRIPT_FATFS)) {
glob_script_mem.script_sd_found=1;
char *script;
script=(char*)calloc(FAT_SCRIPT_SIZE+4,1);
if (!script) break;
glob_script_mem.script_ram=script;
glob_script_mem.script_size=FAT_SCRIPT_SIZE;
if (SD.exists(FAT_SCRIPT_NAME)) {
File file=SD.open(FAT_SCRIPT_NAME,FILE_READ);
file.read((uint8_t*)script,FAT_SCRIPT_SIZE);
file.close();
}
script[FAT_SCRIPT_SIZE-1]=0;
glob_script_mem.script_pram=(uint8_t*)Settings.rules[0];
glob_script_mem.script_pram_size=MAX_SCRIPT_SIZE;
glob_script_mem.flags=1;
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
SdFile::dateTimeCallback(dateTime);
#endif
} else {
glob_script_mem.script_sd_found=0;
}
#endif
{ uint32_t ptr=(uint32_t)glob_script_mem.script_pram;
ptr&=0xfffffffc;
ptr+=4;
glob_script_mem.script_pram=(uint8_t*)ptr;
glob_script_mem.script_pram_size-=4;
}
if (bitRead(Settings.rule_enabled, 0)) Init_Scripter();
break;
case FUNC_INIT:
if (bitRead(Settings.rule_enabled, 0)) {
Run_Scripter(">B",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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_11_knx.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_11_knx.ino"
#ifdef USE_KNX
# 51 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_11_knx.ino"
#define XDRV_11 11
#include <esp-knx-ip.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 =
"<p><form action='kn' method='get'><button>" D_CONFIGURE_KNX "</button></form></p>";
const char HTTP_FORM_KNX[] PROGMEM =
"<fieldset style='min-width:530px;'>"
"<legend style='text-align:left;'><b>&nbsp;" D_KNX_PARAMETERS "&nbsp;</b></legend>"
"<form method='post' action='kn'>"
"<br><center>"
"<b>" D_KNX_PHYSICAL_ADDRESS " </b>"
"<input style='width:12%%;' type='number' name='area' min='0' max='15' value='%d'> . "
"<input style='width:12%%;' type='number' name='line' min='0' max='15' value='%d'> . "
"<input style='width:12%%;' type='number' name='member' min='0' max='255' value='%d'>"
"<br><br>" D_KNX_PHYSICAL_ADDRESS_NOTE "<br><br>"
"<input id='b1' type='checkbox'";
const char HTTP_FORM_KNX1[] PROGMEM =
"><b>" D_KNX_ENABLE "</b>&emsp;<input id='b2' type='checkbox'";
const char HTTP_FORM_KNX2[] PROGMEM =
"><b>" D_KNX_ENHANCEMENT "</b><br></center><br>"
"<fieldset><center>"
"<b>" D_KNX_GROUP_ADDRESS_TO_WRITE "</b><hr>"
"<select name='GAop' style='width:25%%;'>";
const char HTTP_FORM_KNX_OPT[] PROGMEM =
"<option value='%d'>%s</option>";
const char HTTP_FORM_KNX_GA[] PROGMEM =
"<input style='width:12%%;' type='number' id='%s' min='0' max='31' value='0'> / "
"<input style='width:12%%;' type='number' id='%s' min='0' max='7' value='0'> / "
"<input style='width:12%%;' type='number' id='%s' min='0' max='255' value='0'> ";
const char HTTP_FORM_KNX_ADD_BTN[] PROGMEM =
"<button type='submit' onclick='%s()' %s name='btn_add' value='%d' style='width:18%%;'>" D_ADD "</button><br><br>"
"<table style='width:80%%; font-size: 14px;'><col width='250'><col width='30'>";
const char HTTP_FORM_KNX_ADD_TABLE_ROW[] PROGMEM =
"<tr><td><b>%s -> %d / %d / %d </b></td>"
"<td><button type='submit' name='btn_del_ga' value='%d' class='button bred'> " D_DELETE " </button></td></tr>";
const char HTTP_FORM_KNX3[] PROGMEM =
"</table></center></fieldset><br>"
"<fieldset><form method='post' action='kn'><center>"
"<b>" D_KNX_GROUP_ADDRESS_TO_READ "</b><hr>";
const char HTTP_FORM_KNX4[] PROGMEM =
"-> <select name='CBop' style='width:25%%;'>";
const char HTTP_FORM_KNX_ADD_TABLE_ROW2[] PROGMEM =
"<tr><td><b>%d / %d / %d -> %s</b></td>"
"<td><button type='submit' name='btn_del_cb' value='%d' class='button bred'> " D_DELETE " </button></td></tr>";
void HandleKNXConfiguration(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_KNX);
char tmp[100];
String stmp;
if ( WebServer->hasArg("save") ) {
KNX_Save_Settings();
HandleConfiguration();
}
else
{
if ( WebServer->hasArg("btn_add") ) {
if ( WebServer->arg("btn_add") == "1" ) {
stmp = WebServer->arg("GAop");
uint8_t GAop = stmp.toInt();
stmp = WebServer->arg("GA_FNUM");
uint8_t GA_FNUM = stmp.toInt();
stmp = WebServer->arg("GA_AREA");
uint8_t GA_AREA = stmp.toInt();
stmp = WebServer->arg("GA_FDEF");
uint8_t GA_FDEF = stmp.toInt();
if (GAop) {
KNX_ADD_GA( GAop, GA_FNUM, GA_AREA, GA_FDEF );
}
}
else
{
stmp = WebServer->arg("CBop");
uint8_t CBop = stmp.toInt();
stmp = WebServer->arg("CB_FNUM");
uint8_t CB_FNUM = stmp.toInt();
stmp = WebServer->arg("CB_AREA");
uint8_t CB_AREA = stmp.toInt();
stmp = WebServer->arg("CB_FDEF");
uint8_t CB_FDEF = stmp.toInt();
if (CBop) {
KNX_ADD_CB( CBop, CB_FNUM, CB_AREA, CB_FDEF );
}
}
}
else if ( WebServer->hasArg("btn_del_ga") )
{
stmp = WebServer->arg("btn_del_ga");
uint8_t GA_NUM = stmp.toInt();
KNX_DEL_GA(GA_NUM);
}
else if ( WebServer->hasArg("btn_del_cb") )
{
stmp = WebServer->arg("btn_del_cb");
uint8_t CB_NUM = stmp.toInt();
KNX_DEL_CB(CB_NUM);
}
WSContentStart_P(S_CONFIGURE_KNX);
WSContentSend_P(
PSTR("function GAwarning()"
"{"
"var GA_FNUM=eb('GA_FNUM');"
"var GA_AREA=eb('GA_AREA');"
"var GA_FDEF=eb('GA_FDEF');"
"if(GA_FNUM!=null&&GA_FNUM.value=='0'&&GA_AREA.value=='0'&&GA_FDEF.value=='0'){"
"alert('" D_KNX_WARNING "');"
"}"
"}"
"function CBwarning()"
"{"
"var CB_FNUM=eb('CB_FNUM');"
"var CB_AREA=eb('CB_AREA');"
"var CB_FDEF=eb('CB_FDEF');"
"if(CB_FNUM!=null&&CB_FNUM.value=='0'&&CB_AREA.value=='0'&&CB_FDEF.value=='0'){"
"alert('" D_KNX_WARNING "');"
"}"
"}"));
WSContentSendStyle();
KNX_physs_addr.value = Settings.knx_physsical_addr;
WSContentSend_P(HTTP_FORM_KNX, KNX_physs_addr.pa.area, KNX_physs_addr.pa.line, KNX_physs_addr.pa.member);
if ( Settings.flag.knx_enabled ) { WSContentSend_P(PSTR(" checked")); }
WSContentSend_P(HTTP_FORM_KNX1);
if ( Settings.flag.knx_enable_enhancement ) { WSContentSend_P(PSTR(" checked")); }
WSContentSend_P(HTTP_FORM_KNX2);
for (uint32_t i = 0; i < KNX_MAX_device_param ; i++)
{
if ( device_param[i].show )
{
WSContentSend_P(HTTP_FORM_KNX_OPT, device_param[i].type, device_param_ga[i]);
}
}
WSContentSend_P(PSTR("</select> -> "));
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("</select> "));
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("</table></center></fieldset>"));
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_12_home_assistant.ino"
# 20 "C:/shared/sonoff/Git/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<JsonObject>();
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&>()) {
JsonArray& subsensors = subsensor.value.as<JsonArray&>();
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_13_display.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_13_display.ino"
#if defined(USE_I2C) || defined(USE_SPI)
#ifdef USE_DISPLAY
#define XDRV_13 13
#include <renderer.h>
#include <FT6236.h>
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 "C:/shared/sonoff/Git/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>()) {
JsonObject& Object2 = value;
for (JsonObject::iterator it2 = Object2.begin(); it2 != Object2.end(); ++it2) {
JsonVariant value2 = it2->value;
if (value2.is<JsonObject>()) {
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 "C:/shared/sonoff/Git/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<ysize; j++) {
for(int16_t i=0; i<xsize; i+=XBUFF) {
uint16_t rgb[XBUFF];
uint16_t len=fp.read((uint8_t*)rgb,XBUFF*2);
if (len>=2) renderer->pushColors(rgb,len/2,true);
}
OsWatchLoop();
}
renderer->setAddrWindow(0,0,0,0);
#else
for(int16_t j=0; j<ysize; j++) {
for(int16_t i=0; i<xsize; i++ ) {
uint16_t rgb;
uint8_t res=fp.read((uint8_t*)&rgb,2);
if (!res) break;
renderer->writePixel(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; count<xticks; count++) {
renderer->writeFastVLine(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 (cxp<gp->xs) {
renderer->writeFastHLine(gp->xp+cxp,czp,2,fg_color);
cxp+=6.0;
}
float cyp=0,yd=gp->ys/yticks;
for (count=0; count<yticks; count++) {
if ((czp-cyp)>gp->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; count<yticks; count++) {
renderer->writeFastHLine(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; count<index; count++) {
if (graph[count]) {
struct GRAPH *gp1=graph[count];
if ((gp->xp==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;count<NUM_GRAPHS;count++) {
gp=graph[count];
if (gp) {
if (gp->decimation>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 <SD.h>
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;count<gp->xs;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;count<gp->xs-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;count<gp->xs-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;count<gp->xs-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 (fval<gp->ymin) 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<NUM_GRAPHS;count++) {
graph[count]=0;
}
#endif
break;
case FUNC_EVERY_50_MSECOND:
if (Settings.display_model) { XdspCall(FUNC_DISPLAY_EVERY_50_MSECOND); }
break;
case FUNC_SET_POWER:
DisplaySetPower();
break;
case FUNC_EVERY_SECOND:
#ifdef USE_GRAPH
DisplayCheckGraph();
#endif
#ifdef USE_DISPLAY_MODES1TO5
if (Settings.display_model && Settings.display_mode) { XdspCall(FUNC_DISPLAY_EVERY_SECOND); }
#endif
break;
#ifdef USE_DISPLAY_MODES1TO5
case FUNC_MQTT_SUBSCRIBE:
DisplayMqttSubscribe();
break;
case FUNC_MQTT_DATA:
result = DisplayMqttData();
break;
case FUNC_SHOW_SENSOR:
DisplayLocalSensor();
break;
#endif
case FUNC_COMMAND:
result = DecodeCommand(kDisplayCommands, DisplayCommand);
break;
}
}
return result;
}
#endif
#endif
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_14_mp3.ino"
# 64 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_14_mp3.ino"
#ifdef USE_MP3_PLAYER
#define XDRV_14 14
#include <TasmotaSerial.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_15_pca9685.ino"
# 20 "C:/shared/sonoff/Git/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<XdrvMailbox.data_len;ca++) {
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
if (',' == XdrvMailbox.data[ca]) { paramcount++; }
}
UpperCase(XdrvMailbox.data,XdrvMailbox.data);
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET")) { PCA9685_Reset(); return serviced; }
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"STATUS")) { PCA9685_OutputTelemetry(false); return serviced; }
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"PWMF")) {
if (paramcount > 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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_16_tuyamcu.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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
};
# 126 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_16_tuyamcu.ino"
void CmndTuyaSend(void) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
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) {
uint16_t dpDataLen = Tuya.buffer[dpidStart + 2] << 8 | Tuya.buffer[dpidStart + 3];
ResponseAppend_P(PSTR(",\"%d\":{\"DpId\":%d,\"DpIdType\":%d,\"DpIdData\":\"%s\""), Tuya.buffer[dpidStart], Tuya.buffer[dpidStart], Tuya.buffer[dpidStart + 1], ToHex_P((unsigned char*)&Tuya.buffer[dpidStart + 4], dpDataLen, hex_char, sizeof(hex_char)));
if (TUYA_TYPE_STRING == Tuya.buffer[dpidStart + 1]) {
ResponseAppend_P(PSTR(",\"Type3Data\":\"%.*s\""), dpDataLen, (char *)&Tuya.buffer[dpidStart + 4]);
}
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_17_rcswitch.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_18_armtronix_dimmers.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_18_armtronix_dimmers.ino"
#ifdef USE_LIGHT
#ifdef USE_ARMTRONIX_DIMMERS
#define XDRV_18 18
#include <TasmotaSerial.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_19_ps16dz_dimmer.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_20_hue.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_20_hue.ino"
#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT)
# 31 "C:/shared/sonoff/Git/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 =
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://{x1:80/</URLBase>"
"<device>"
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
"<friendlyName>Amazon-Echo-HA-Bridge ({x1)</friendlyName>"
"<manufacturer>Royal Philips Electronics</manufacturer>"
"<manufacturerURL>http://www.philips.com</manufacturerURL>"
"<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
"<modelName>Philips hue bridge 2012</modelName>"
"<modelNumber>929000226503</modelNumber>"
"<serialNumber>{x3</serialNumber>"
"<UDN>uuid:{x2</UDN>"
"</device>"
"</root>\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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_21_wemo.ino"
# 20 "C:/shared/sonoff/Git/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 =
"<scpd xmlns=\"urn:Belkin:service-1-0\">"
"<actionList>"
"<action>"
"<name>SetBinaryState</name>"
"<argumentList>"
"<argument>"
"<retval/>"
"<name>BinaryState</name>"
"<relatedStateVariable>BinaryState</relatedStateVariable>"
"<direction>in</direction>"
"</argument>"
"</argumentList>"
"</action>"
"<action>"
"<name>GetBinaryState</name>"
"<argumentList>"
"<argument>"
"<retval/>"
"<name>BinaryState</name>"
"<relatedStateVariable>BinaryState</relatedStateVariable>"
"<direction>out</direction>"
"</argument>"
"</argumentList>"
"</action>"
"</actionList>"
"<serviceStateTable>"
"<stateVariable sendEvents=\"yes\">"
"<name>BinaryState</name>"
"<dataType>bool</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"<stateVariable sendEvents=\"yes\">"
"<name>level</name>"
"<dataType>string</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"</serviceStateTable>"
"</scpd>\r\n\r\n";
const char WEMO_METASERVICE_XML[] PROGMEM =
"<scpd xmlns=\"urn:Belkin:service-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<actionList>"
"<action>"
"<name>GetMetaInfo</name>"
"<argumentList>"
"<retval />"
"<name>GetMetaInfo</name>"
"<relatedStateVariable>MetaInfo</relatedStateVariable>"
"<direction>in</direction>"
"</argumentList>"
"</action>"
"</actionList>"
"<serviceStateTable>"
"<stateVariable sendEvents=\"yes\">"
"<name>MetaInfo</name>"
"<dataType>string</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"</serviceStateTable>"
"</scpd>\r\n\r\n";
const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM =
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
"<s:Body>"
"<u:%cetBinaryStateResponse xmlns:u=\"urn:Belkin:service:basicevent:1\">"
"<BinaryState>%d</BinaryState>"
"</u:%cetBinaryStateResponse>"
"</s:Body>"
"</s:Envelope>\r\n";
const char WEMO_SETUP_XML[] PROGMEM =
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:Belkin:device-1-0\">"
"<device>"
"<deviceType>urn:Belkin:device:controllee:1</deviceType>"
"<friendlyName>{x1</friendlyName>"
"<manufacturer>Belkin International Inc.</manufacturer>"
"<modelName>Socket</modelName>"
"<modelNumber>3.1415</modelNumber>"
"<UDN>uuid:{x2</UDN>"
"<serialNumber>{x3</serialNumber>"
"<binaryState>0</binaryState>"
"<serviceList>"
"<service>"
"<serviceType>urn:Belkin:service:basicevent:1</serviceType>"
"<serviceId>urn:Belkin:serviceId:basicevent1</serviceId>"
"<controlURL>/upnp/control/basicevent1</controlURL>"
"<eventSubURL>/upnp/event/basicevent1</eventSubURL>"
"<SCPDURL>/eventservice.xml</SCPDURL>"
"</service>"
"<service>"
"<serviceType>urn:Belkin:service:metainfo:1</serviceType>"
"<serviceId>urn:Belkin:serviceId:metainfo1</serviceId>"
"<controlURL>/upnp/control/metainfo1</controlURL>"
"<eventSubURL>/upnp/event/metainfo1</eventSubURL>"
"<SCPDURL>/metainfoservice.xml</SCPDURL>"
"</service>"
"</serviceList>"
"</device>"
"</root>\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>1</Binary")) != nullptr) {
power = POWER_ON;
}
else if (strstr_P(event, PSTR("State>0</Binary")) != nullptr) {
power = POWER_OFF;
}
if (power != POWER_TOGGLE) {
uint8_t device = (light_type) ? devices_present : 1;
ExecuteCommandPower(device, power, SRC_WEMO);
}
}
snprintf_P(event, sizeof(event), WEMO_RESPONSE_STATE_SOAP, state, bitRead(power, devices_present -1), state);
WSSend(200, CT_XML, event);
}
void HandleUpnpService(void)
{
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_EVENT_SERVICE));
WSSend(200, CT_PLAIN, FPSTR(WEMO_EVENTSERVICE_XML));
}
void HandleUpnpMetaService(void)
{
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_META_SERVICE));
WSSend(200, CT_PLAIN, FPSTR(WEMO_METASERVICE_XML));
}
void HandleUpnpSetupWemo(void)
{
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_SETUP));
String setup_xml = FPSTR(WEMO_SETUP_XML);
setup_xml.replace("{x1", SettingsText(SET_FRIENDLYNAME1));
setup_xml.replace("{x2", WemoUuid());
setup_xml.replace("{x3", WemoSerialnumber());
WSSend(200, CT_XML, setup_xml);
}
bool Xdrv21(uint8_t function)
{
bool result = false;
if (devices_present && (EMUL_WEMO == Settings.flag2.emulation)) {
switch (function) {
case FUNC_WEB_ADD_HANDLER:
WebServer->on("/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_22_sonoff_ifan.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino"
};
# 275 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_1_headers.ino"
# 20 "C:/shared/sonoff/Git/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 disableDefResp = true, uint8_t transacId = 1);
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
#ifdef USE_ZIGBEE
#include <vector>
#include <map>
#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<uint32_t> endpoints;
std::vector<uint32_t> clusters_in;
std::vector<uint32_t> clusters_out;
uint32_t timer;
uint16_t cluster;
uint16_t endpoint;
uint32_t value;
Z_DeviceTimer func;
DynamicJsonBuffer *json_buffer;
JsonObject *json;
} 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);
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<Z_Device> _devices = {};
uint32_t _saveTimer = 0;
template < typename T>
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
template < typename T>
static int32_t findEndpointInVector(const std::vector<T> & vecOfElements, uint8_t element);
static int32_t findClusterEndpoint(const std::vector<uint32_t> & 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<T> & 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<T> & vecOfElements, uint8_t element) {
int32_t found = 0;
for (auto &elem : vecOfElements) {
if ( ((elem >> 16) & 0xFF) == element) { return found; }
found++;
}
return -1;
}
# 204 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
int32_t Z_Devices::findClusterEndpoint(const std::vector<uint32_t> & 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<uint32_t>(),
std::vector<uint32_t>(),
std::vector<uint32_t>(),
0,0,0,0,
nullptr,
nullptr, nullptr };
device.json_buffer = new DynamicJsonBuffer();
_devices.push_back(device);
dirty();
return _devices.back();
}
# 244 "C:/shared/sonoff/Git/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;
}
# 263 "C:/shared/sonoff/Git/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;
}
# 282 "C:/shared/sonoff/Git/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);
}
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<Z_Device>::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<char*>()) {
String sval = val.as<String>();
to.set(key, sval);
} else if (val.is<JsonArray>()) {
JsonArray &nested_arr = to.createNestedArray(key);
CopyJsonArray(nested_arr, val.as<JsonArray>());
} else if (val.is<JsonObject>()) {
JsonObject &nested_obj = to.createNestedObject(key);
CopyJsonObject(nested_obj, val.as<JsonObject>());
} else {
to.set(key, val);
}
}
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
for (auto v : arr) {
if (v.is<char*>()) {
String sval = v.as<String>();
to.add(sval);
} else if (v.is<JsonArray>()) {
} else if (v.is<JsonObject>()) {
} 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);
# 693 "C:/shared/sonoff/Git/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();
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
}
}
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<Z_Device>::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<uint32_t>::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<uint32_t>::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<uint32_t>::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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_4_persistence.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_4_persistence.ino"
#ifdef USE_ZIGBEE
# 50 "C:/shared/sonoff/Git/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<uint32_t>::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<uint32_t>::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<uint32_t>::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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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) {
uint32_t i = offset;
uint32_t len = _payload.len();
char attrid_str[12];
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X!%02X"), _cluster_id, _cmd_id);
char hex_char[_payload.len()*2+2];
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
json[attrid_str] = hex_char;
}
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 },
# 558 "C:/shared/sonoff/Git/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<const char*>());
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<const char*>());
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;
}
# 915 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_6_commands.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_6_commands.ino"
#ifdef USE_ZIGBEE
typedef struct Z_CommandConverter {
const char * tasmota_cmd;
const char * zcl_cmd;
} Z_CommandConverter;
const Z_CommandConverter Z_Commands[] = {
{ "Power", "0006!xx" },
{ "Dimmer", "0008!04/xx0A00" },
{ "Dimmer+", "0008!06/001902" },
{ "Dimmer-", "0008!06/011902" },
{ "DimmerStop", "0008!03" },
{ "ResetAlarm", "0009!00/xxyyyy" },
{ "ResetAllAlarms","0009!01" },
{ "Hue", "0300!00/xx000A00" },
{ "Sat", "0300!03/xx0A00" },
{ "HueSat", "0300!06/xxyy0A00" },
{ "Color", "0300!07/xxxxyyyy0A00" },
{ "CT", "0300!0A/xxxx0A00" },
{ "Shutter", "0102!xx" },
{ "ShutterOpen", "0102!00" },
{ "ShutterClose", "0102!01" },
{ "ShutterStop", "0102!02" },
{ "ShutterLift", "0102!05xx" },
{ "ShutterTilt", "0102!08xx" },
};
#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, false );
}
}
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);
}
}
const __FlashStringHelper* zigbeeFindCommand(const char *command) {
char parm_uc[16];
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
const Z_CommandConverter *conv = &Z_Commands[i];
if (0 == strcasecmp_P(command, conv->tasmota_cmd)) {
return (const __FlashStringHelper*) conv->zcl_cmd;
}
}
return nullptr;
}
inline bool isXYZ(char c) {
return (c >= 'x') && (c <= 'z');
}
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino"
# 20 "C:/shared/sonoff/Git/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_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 )
# 271 "C:/shared/sonoff/Git/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)
# 289 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_8_parsers.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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_SendActiveEpReq(uint16_t shortaddr) {
uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ,
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
}
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_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_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)
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 },
};
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 ;
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 ;
void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZbZNPSend, &CmndZbPermitJoin,
&CmndZbStatus, &CmndZbReset, &CmndZbSend,
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
&CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind
};
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;
# 142 "C:/shared/sonoff/Git/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<unsigned int>()) {
return val.as<unsigned int>();
} else {
if (val.is<const char*>()) {
String sval = val.as<String>();
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 disableDefResp, 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((disableDefResp ? 0x10 : 0x00) | (clusterSpecific ? 0x01 : 0x00));
buf.add8(transacId);
buf.add8(cmdId);
if (len > 0) {
buf.addBuffer(msg, len);
}
ZigbeeZNPSend(buf.getBuffer(), buf.len());
}
inline int8_t hexValue(char c) {
if ((c >= '0') && (c <= '9')) {
return c - '0';
}
if ((c >= 'A') && (c <= 'F')) {
return 10 + c - 'A';
}
if ((c >= 'a') && (c <= 'f')) {
return 10 + c - 'a';
}
return -1;
}
uint32_t parseHex(const char **data, size_t max_len = 8) {
uint32_t ret = 0;
for (uint32_t i = 0; i < max_len; i++) {
int8_t v = hexValue(**data);
if (v < 0) { break; }
ret = (ret << 4) | v;
*data += 1;
}
return ret;
}
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
uint16_t cluster = 0x0000;
uint8_t cmd = ZCL_READ_ATTRIBUTES;
bool clusterSpecific = false;
cluster = parseHex(&data, 4);
if (('_' == *data) || ('!' == *data)) {
if ('!' == *data) { clusterSpecific = true; }
data++;
} else {
ResponseCmndChar("Wrong delimiter for payload");
return;
}
cmd = parseHex(&data, 2);
if ('/' == *data) { data++; }
size_t size = strlen(data);
SBuffer buf((size+2)/2);
while (*data) {
uint8_t code = parseHex(&data, 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, data);
if (0 == endpoint) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
return;
}
ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len());
if (clusterSpecific) {
zigbeeSetCommandTimer(dstAddr, cluster, endpoint);
}
ResponseCmndDone();
}
void CmndZbSend(void) {
# 461 "C:/shared/sonoff/Git/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;
String cmd_str = "";
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
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>()) {
JsonObject &cmd_obj = val_cmd.as<JsonObject&>();
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;
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str());
if (tasmota_cmd) {
cmd_str = tasmota_cmd;
} else {
Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str());
return;
}
if (value.is<bool>()) {
x = value.as<bool>() ? 1 : 0;
} else if (value.is<unsigned int>()) {
x = value.as<unsigned int>();
} else {
const char *s_const = value.as<const char*>();
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);
}
}
}
}
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str());
cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str());
} else {
}
} else if (val_cmd.is<char*>()) {
cmd_str = val_cmd.as<String>();
} else {
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"),
device, endpoint, cmd_str.c_str());
zigbeeZCLSendStr(device, endpoint, cmd_str.c_str());
} 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<char*>());
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) {
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_SendActiveEpReq(shortaddr);
ResponseCmndDone();
}
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<char*>());
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>()) {
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) && (attrs_len > 0)) {
ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false );
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_24_buzzer.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_25_A4988_Stepper.ino"
# 21 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_25_A4988_Stepper.ino"
#ifdef USE_A4988_STEPPER
#define XDRV_25 25
#include <A4988_Stepper.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_26_ariluxrf.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_27_shutter.ino"
# 20 "C:/shared/sonoff/Git/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}";
const char JSON_SHUTTER_BUTTON[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Button%d\":%d}";
#include <Ticker.h>
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;
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);
}
void ShutterRtc50mS(void)
{
for (uint32_t i = 0; i < shutters_present; i++) {
Shutter.time[i]++;
if (Shutter.accelerator[i]) {
Shutter.pwm_frequency += Shutter.accelerator[i];
Shutter.pwm_frequency = tmax(0,tmin(Shutter.direction[i]==1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i],Shutter.pwm_frequency));
analogWriteFreq(Shutter.pwm_frequency);
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 (0 == percent) return 0;
if (100 == percent) return Shutter.open_max[index];
if (Settings.shutter_set50percent[index] != 50) {
return (percent <= 5) ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index];
} else {
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 (0 >= realpos) return 0;
if (Shutter.open_max[index] <= realpos) return 100;
if (Settings.shutter_set50percent[index] != 50) {
return (Settings.shuttercoeff[2][index] * 5 > realpos) ? SHT_DIV_ROUND(realpos, Settings.shuttercoeff[2][index]) : SHT_DIV_ROUND(realpos-Settings.shuttercoeff[0][index], Settings.shuttercoeff[1][index]);
} else {
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 = 0;
analogWriteFreq(Shutter.pwm_frequency);
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)
{
uint32_t shutter_moving = 0;
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) {
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]);
}
ResponseJsonEnd();
if (always || (1 == shutter_moving)) {
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER));
}
if (rules_flag.shutter_moving > shutter_moving) {
rules_flag.shutter_moved = 1;
} else {
rules_flag.shutter_moved = 0;
}
rules_flag.shutter_moving = shutter_moving;
}
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*1000 / max_freq_change_per_sec;
int32_t velocity = Shutter.direction[i] == 1 ? 100 : Shutter.close_velocity[i];
int32_t minstopway = min_runtime_ms * velocity / 100 * Shutter.pwm_frequency / max_frequency * Shutter.direction[i] ;
int32_t next_possible_stop = Shutter.real_position[i] + minstopway ;
stop_position_delta =200 * Shutter.pwm_frequency/max_frequency + Shutter.direction[i] * (next_possible_stop - Shutter.target_position[i]);
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: time: %d, velocity %d, minstopway %d,cur_freq %d, max_frequency %d, act_freq_change %d, min_runtime_ms %d, act.pos %d, next_stop %d, target: %d"),Shutter.time[i],velocity,minstopway,
Shutter.pwm_frequency,max_frequency, Shutter.accelerator[i],min_runtime_ms,Shutter.real_position[i], next_possible_stop,Shutter.target_position[i]);
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 == 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);
Shutter.accelerator[i] = 0;
Shutter.pwm_frequency = Shutter.pwm_frequency > 250 ? 250 : Shutter.pwm_frequency;
analogWriteFreq(Shutter.pwm_frequency);
analogWrite(pin[GPIO_PWM1+i], 50);
Shutter.pwm_frequency = 0;
analogWriteFreq(Shutter.pwm_frequency);
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);
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 = 0;
analogWriteFreq(Shutter.pwm_frequency);
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 > 0) {
Shutter.accelerator[i] = 0;
Shutter.pwm_frequency = tmax(Shutter.pwm_frequency-((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) , 0);
analogWriteFreq(Shutter.pwm_frequency);
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;
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 (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++) {
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]) {
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)<<pos_press_index)) {
char scommand[CMDSZ];
char stopic[TOPSZ];
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
if ((i==shutter_index) || (Settings.shutter_button[button_index] & (0x01<<30))) {
snprintf_P(scommand, sizeof(scommand),PSTR("ShutterPosition%d"), i+1);
GetGroupTopic_P(stopic, scommand);
Response_P("%d", position);
MqttPublish(stopic, false);
}
}
}
}
}
}
}
}
Response_P(PSTR("{"));
ResponseAppend_P(JSON_SHUTTER_BUTTON, shutter_index+1, (buttonState <= SHT_PRESSED_IMMEDIATE) ? (button_index+1) : 0, press_index);
ResponseJsonEnd();
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER));
XdrvRulesProcess();
}
}
void ShutterSetPosition(uint32_t device, uint32_t position)
{
char svalue[32];
snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION "%d %d"), device, position);
ExecuteCommand(svalue, SRC_IGNORE);
}
void CmndShutterOpen(void)
{
if ((1 == XdrvMailbox.index) && (XdrvMailbox.payload != -99)) {
XdrvMailbox.index = XdrvMailbox.payload;
}
XdrvMailbox.payload = 100;
last_source = SRC_WEBGUI;
CmndShutterPosition();
}
void CmndShutterClose(void)
{
if ((1 == XdrvMailbox.index) && (XdrvMailbox.payload != -99)) {
XdrvMailbox.index = XdrvMailbox.payload;
}
XdrvMailbox.payload = 0;
XdrvMailbox.data_len = 0;
last_source = SRC_WEBGUI;
CmndShutterPosition();
}
void CmndShutterStop(void)
{
if ((XdrvMailbox.index > 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) ? 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);
Shutter.accelerator[index] = Shutter.max_pwm_frequency / ((Shutter.motordelay[index] > 0) ? Shutter.motordelay[index] : 1);
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;
# 915 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_28_pcf8574.ino"
# 20 "C:/shared/sonoff/Git/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 =
"<p><form action='" WEB_HANDLE_PCF8574 "' method='get'><button>" D_CONFIGURE_PCF8574 "</button></form></p>";
const char HTTP_FORM_I2C_PCF8574_1[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_PCF8574_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='" WEB_HANDLE_PCF8574 "'>"
"<p><input id='b1' name='b1' type='checkbox'%s><b>" D_INVERT_PORTS "</b></p><hr/>";
const char HTTP_FORM_I2C_PCF8574_2[] PROGMEM =
"<tr><td><b>" D_DEVICE " %d " D_PORT " %d</b></td><td style='width:100px'><select id='i2cs%d' name='i2cs%d'>"
"<option%s value='0'>" D_DEVICE_INPUT "</option>"
"<option%s value='1'>" D_DEVICE_OUTPUT "</option>"
"</select></td></tr>";
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("</table>"));
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_29_deepsleep.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_29_deepsleep.ino"
#ifdef USE_DEEPSLEEP
# 31 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_31_tasmota_slave.ino"
# 20 "C:/shared/sonoff/Git/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 <TasmotaSerial.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_32_hotplug.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_33_nrf24l01.ino"
# 30 "C:/shared/sonoff/Git/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 <SPI.h>
#include <RF24.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino"
# 22 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino"
if (1 == type) {
char svalue[10];
snprintf_P(svalue, sizeof(svalue), PSTR("%s"), 7);
}
# 159 "C:/shared/sonoff/Git/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 <cont.h>
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 <cont.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_interface.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_01_lcd.ino"
# 20 "C:/shared/sonoff/Git/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 <Wire.h>
#include <LiquidCrystal_I2C.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_02_ssd1306.ino"
# 20 "C:/shared/sonoff/Git/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 <Wire.h>
#include <renderer.h>
#include <Adafruit_SSD1306.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_03_matrix.ino"
# 20 "C:/shared/sonoff/Git/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 <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_04_ili9341.ino"
# 20 "C:/shared/sonoff/Git/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 <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_05_epaper_29.ino"
# 20 "C:/shared/sonoff/Git/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 <epd2in9.h>
#include <epdpaint.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_06_epaper_42.ino"
# 21 "C:/shared/sonoff/Git/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 <epd4in2.h>
#include <epdpaint.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_07_sh1106.ino"
# 20 "C:/shared/sonoff/Git/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 <Wire.h>
#include <renderer.h>
#include <Adafruit_SH1106.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_08_ILI9488.ino"
# 20 "C:/shared/sonoff/Git/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 <ILI9488.h>
#include <FT6236.h>
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; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&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; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_09_SSD1351.ino"
# 20 "C:/shared/sonoff/Git/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 <SSD1351.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino"
# 20 "C:/shared/sonoff/Git/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 <RA8876.h>
#include <FT6236.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino"
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&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; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_interface.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_01_ws2812.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_01_ws2812.ino"
#ifdef USE_LIGHT
#ifdef USE_WS2812
# 38 "C:/shared/sonoff/Git/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 <NeoPixelBus.h>
#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<selectedNeoFeatureType, selectedNeoSpeedType> *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, &currentColor, 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<selectedNeoFeatureType, selectedNeoSpeedType>(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 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_02_my92x1.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_04_sm2135.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_05_sonoff_l1.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_interface.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_01_hlw8012.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_02_cse7766.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_PZEM004T
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino"
#define XNRG_03 3
const uint32_t PZEM_STABILIZE = 30;
#include <TasmotaSerial.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_04_mcp39f501.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_05_pzem_ac.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_05_pzem_ac.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_PZEM_AC
# 33 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_06_pzem_dc.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_06_pzem_dc.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_PZEM_DC
# 32 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_07_ade7953.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_07_ade7953.ino"
#ifdef USE_I2C
#ifdef USE_ENERGY_SENSOR
#ifdef USE_ADE7953
# 31 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_08_sdm120.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_09_dds2382.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_10_sdm630.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_11_ddsu666.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_12_solaxX1.ino"
# 20 "C:/shared/sonoff/Git/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 <TasmotaSerial.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_LE01MR
# 71 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_interface.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_01_counter.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_02_analog.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_04_snfsc.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_04_snfsc.ino"
#ifdef USE_SONOFF_SC
# 57 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_05_ds18x20.ino"
# 20 "C:/shared/sonoff/Git/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]);
}
uint8_t OneWireReadBit(void)
{
if (!ds18x20_dual_mode) {
pinMode(ds18x20_pin, OUTPUT);
digitalWrite(ds18x20_pin, LOW);
delayMicroseconds(3);
pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT);
} else {
digitalWrite(ds18x20_pin_out, LOW);
delayMicroseconds(3);
digitalWrite(ds18x20_pin_out, HIGH);
}
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)
{
#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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_old.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_old.ino"
#ifdef USE_DHT_OLD
# 29 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino"
#ifdef USE_DHT_V2
# 29 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v3.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v3.ino"
#ifdef USE_DHT_V3
# 30 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v4.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v4.ino"
#ifdef USE_DHT_V4
# 30 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v5.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v5.ino"
#ifdef USE_DHT
# 30 "C:/shared/sonoff/Git/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;
}
bool error = false;
noInterrupts();
if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) {
for (uint32_t i = 0; i < 5; i++) {
int data = 0;
for (uint32_t j = 0; j < 8; j++) {
if (!DhtWaitState(sensor, 1)) {
error = true;
break;
}
delayMicroseconds(35);
if (digitalRead(Dht[sensor].pin)) {
data |= (1 << (7 - j));
}
if (!DhtWaitState(sensor, 0)) {
error = true;
break;
}
}
if (error) { break; }
dht_data[i] = data;
}
} else {
error = true;
}
interrupts();
if (error) { 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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_07_sht1x.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_07_sht1x.ino"
#ifdef USE_I2C
#ifdef USE_SHT
# 31 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_08_htu21.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_08_htu21.ino"
#ifdef USE_I2C
#ifdef USE_HTU
# 30 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_09_bmp.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_09_bmp.ino"
#ifdef USE_I2C
#ifdef USE_BMP
# 30 "C:/shared/sonoff/Git/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 <bme680.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_10_bh1750.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_11_veml6070.ino"
# 89 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_12_ads1115.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_12_ads1115.ino"
#ifdef USE_I2C
#ifdef USE_ADS1115
# 43 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino"
#ifdef USE_I2C
#ifdef USE_INA219
# 30 "C:/shared/sonoff/Git/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<sizeof(ina219_type); i++) {
if (!ina219_type[i]) { continue; }
uint16_t addr = ina219_addresses[i];
ina219_voltage[i] = Ina219GetBusVoltage_V(addr) + (Ina219GetShuntVoltage_mV(addr) / 1000);
ina219_current[i] = Ina219GetCurrent_mA(addr) / 1000;
ina219_valid[i] = SENSOR_MAX_MISS;
}
return true;
}
# 184 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino"
bool Ina219CommandSensor(void)
{
if ((XdrvMailbox.payload >= 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; i<sizeof(ina219_type); i++)
if (ina219_type[i] && ina219_valid[i])
num_found++;
int sensor_num = 0;
for (int i=0; i<sizeof(ina219_type); i++) {
if (!ina219_type[i] || !ina219_valid[i])
continue;
sensor_num++;
char voltage[16];
dtostrfd(ina219_voltage[i], Settings.flag2.voltage_resolution, voltage);
char current[16];
dtostrfd(ina219_current[i], Settings.flag2.current_resolution, current);
char power[16];
dtostrfd(ina219_voltage[i] * ina219_current[i], Settings.flag2.wattage_resolution, power);
char name[16];
if (num_found>1)
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_14_sht3x.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
#ifdef USE_MHZ19
# 33 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
#include <TasmotaSerial.h>
#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 "C:/shared/sonoff/Git/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<uint16_t>(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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino"
#ifdef USE_I2C
#ifdef USE_TSL2561
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino"
#define XSNS_16 16
#define XI2C_16 16
#include <Tsl2561Util.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_17_senseair.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_17_senseair.ino"
#ifdef USE_SENSEAIR
# 29 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino"
#ifdef USE_PMS5003
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino"
#define XSNS_18 18
#include <TasmotaSerial.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_19_mgs.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino"
#ifdef USE_NOVA_SDS
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino"
#define XSNS_20 20
#include <TasmotaSerial.h>
#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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_21_sgp30.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_21_sgp30.ino"
#ifdef USE_I2C
#ifdef USE_SGP30
# 30 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_22_sr04.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_22_sr04.ino"
#ifdef USE_SR04
#include <NewPing.h>
#include <TasmotaSerial.h>
# 32 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_24_si1145.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_24_si1145.ino"
#ifdef USE_I2C
#ifdef USE_SI1145
# 30 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_26_lm75ad.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_26_lm75ad.ino"
#ifdef USE_I2C
#ifdef USE_LM75AD
# 31 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
# 28 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
#ifdef USE_I2C
#ifdef USE_APDS9960
# 39 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
void readAllColorAndProximityData(void)
{
if (I2cReadBuffer(APDS9960_I2C_ADDR, APDS9960_CDATAL, (uint8_t *) &color_data, (uint16_t)9))
{
}
}
# 1616 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino"
}
}
return result;
}
#endif
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_29_mcp230xx.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_29_mcp230xx.ino"
#ifdef USE_I2C
#ifdef USE_MCP230xx
# 31 "C:/shared/sonoff/Git/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<mcp230xx_pincount;idx++) {
int_millis[idx]=millis();
}
mcp230xx_int_en = int_en;
MCP230xx_CheckForIntCounter();
MCP230xx_CheckForIntRetainer();
}
void MCP230xx_Detect(void)
{
if (I2cActive(USE_MCP230xx_ADDR)) { return; }
uint8_t buffer;
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IOCON, 0x80);
if (I2cValidRead8(&buffer, USE_MCP230xx_ADDR, MCP230xx_IOCON)) {
if (0x00 == buffer) {
mcp230xx_type = 1;
I2cSetActiveFound(USE_MCP230xx_ADDR, "MCP23008");
mcp230xx_pincount = 8;
MCP230xx_ApplySettings();
} else {
if (0x80 == buffer) {
mcp230xx_type = 2;
I2cSetActiveFound(USE_MCP230xx_ADDR, "MCP23017");
mcp230xx_pincount = 16;
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IOCON, 0x00);
MCP230xx_GPINTEN = 0x04;
MCP230xx_GPPU = 0x0C;
MCP230xx_INTF = 0x0E;
MCP230xx_INTCAP = 0x10;
MCP230xx_GPIO = 0x12;
MCP230xx_ApplySettings();
}
}
}
}
void MCP230xx_CheckForInterrupt(void) {
uint8_t intf;
uint8_t mcp230xx_intcap = 0;
uint8_t report_int;
for (uint32_t mcp230xx_port = 0; mcp230xx_port < mcp230xx_type; mcp230xx_port++) {
if (I2cValidRead8(&intf,USE_MCP230xx_ADDR,MCP230xx_INTF+mcp230xx_port)) {
if (intf > 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<XdrvMailbox.data_len;ca++) {
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
if (',' == XdrvMailbox.data[ca]) { paramcount++; }
}
UpperCase(XdrvMailbox.data,XdrvMailbox.data);
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET")) { MCP230xx_Reset(1); return serviced; }
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET1")) { MCP230xx_Reset(1); return serviced; }
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET2")) { MCP230xx_Reset(2); return serviced; }
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET3")) { MCP230xx_Reset(3); return serviced; }
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET4")) { MCP230xx_Reset(4); return serviced; }
#ifdef USE_MCP230xx_OUTPUT
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET5")) { MCP230xx_Reset(5); return serviced; }
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET6")) { MCP230xx_Reset(6); return serviced; }
#endif
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTPRI")) {
if (paramcount > 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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino"
# 46 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_31_ccs811.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_31_ccs811.ino"
#ifdef USE_I2C
#ifdef USE_CCS811
# 30 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino"
#ifdef USE_I2C
#ifdef USE_MPU6050
# 30 "C:/shared/sonoff/Git/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 <helper_3dmath.h>
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 <MPU6050.h>
#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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_33_ds3231.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_33_ds3231.ino"
#ifdef USE_I2C
#ifdef USE_DS3231
# 35 "C:/shared/sonoff/Git/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"),
GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).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"),
GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino"
#ifdef USE_HX711
# 35 "C:/shared/sonoff/Git/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<unsigned long>(filler) << 24
| static_cast<unsigned long>(data[2]) << 16
| static_cast<unsigned long>(data[1]) << 8
| static_cast<unsigned long>(data[0]) );
return static_cast<long>(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 "C:/shared/sonoff/Git/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 =
"<p><form action='" WEB_HANDLE_HX711 "' method='get'><button name='reset'>" D_RESET_HX711 "</button></form></p>";
const char HTTP_BTN_MENU_HX711[] PROGMEM =
"<p><form action='" WEB_HANDLE_HX711 "' method='get'><button>" D_CONFIGURE_HX711 "</button></form></p>";
const char HTTP_FORM_HX711[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_CALIBRATION "&nbsp;</b></legend>"
"<form method='post' action='" WEB_HANDLE_HX711 "'>"
"<p><b>" D_REFERENCE_WEIGHT "</b> (" D_UNIT_KILOGRAM ")<br><input type='number' step='0.001' id='p1' placeholder='0' value='%s'></p>"
"<br><button name='calibrate' type='submit'>" D_CALIBRATE "</button>"
"</form>"
"</fieldset><br><br>"
"<fieldset><legend><b>&nbsp;" D_HX711_PARAMETERS "&nbsp;</b></legend>"
"<form method='post' action='" WEB_HANDLE_HX711 "'>"
"<p><b>" D_ITEM_WEIGHT "</b> (" D_UNIT_KILOGRAM ")<br><input type='number' max='6.5535' step='0.0001' id='p2' placeholder='0.0' value='%s'></p>";
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_35_tx20.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_35_tx20.ino"
#ifdef USE_TX20_WIND_SENSOR
# 29 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_35_tx20.ino"
#define XSNS_35 35
#define TX20_BIT_TIME 1220
#define TX20_RESET_VALUES 60
extern "C" {
#include "gpio.h"
}
#ifdef USE_WEBSERVER
const char HTTP_SNS_TX20[] PROGMEM =
"{s}TX20 " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
"{s}TX20 " D_TX20_WIND_SPEED_AVG "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
"{s}TX20 " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
"{s}TX20 " D_TX20_WIND_DIRECTION "{m}%s{e}";
#endif
const char kTx20Directions[] 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 tx20_sa = 0;
uint8_t tx20_sb = 0;
uint8_t tx20_sd = 0;
uint8_t tx20_se = 0;
uint16_t tx20_sc = 0;
uint16_t tx20_sf = 0;
float tx20_wind_speed_kmh = 0;
float tx20_wind_speed_max = 0;
float tx20_wind_speed_avg = 0;
float tx20_wind_sum = 0;
int tx20_count = 0;
uint8_t tx20_wind_direction = 0;
bool tx20_available = false;
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
void Tx20StartRead(void) ICACHE_RAM_ATTR;
#endif
void Tx20StartRead(void)
{
# 101 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_35_tx20.ino"
tx20_available = false;
tx20_sa = 0;
tx20_sb = 0;
tx20_sd = 0;
tx20_se = 0;
tx20_sc = 0;
tx20_sf = 0;
delayMicroseconds(TX20_BIT_TIME / 2);
for (int32_t bitcount = 41; bitcount > 0; bitcount--) {
uint8_t dpin = (digitalRead(pin[GPIO_TX20_TXD_BLACK]));
if (bitcount > 41 - 5) {
tx20_sa = (tx20_sa << 1) | (dpin ^ 1);
} else if (bitcount > 41 - 5 - 4) {
tx20_sb = tx20_sb >> 1 | ((dpin ^ 1) << 3);
} else if (bitcount > 41 - 5 - 4 - 12) {
tx20_sc = tx20_sc >> 1 | ((dpin ^ 1) << 11);
} else if (bitcount > 41 - 5 - 4 - 12 - 4) {
tx20_sd = tx20_sd >> 1 | ((dpin ^ 1) << 3);
} else if (bitcount > 41 - 5 - 4 - 12 - 4 - 4) {
tx20_se = tx20_se >> 1 | (dpin << 3);
} else {
tx20_sf = tx20_sf >> 1 | (dpin << 11);
}
delayMicroseconds(TX20_BIT_TIME);
}
uint8_t chk = (tx20_sb + (tx20_sc & 0xf) + ((tx20_sc >> 4) & 0xf) + ((tx20_sc >> 8) & 0xf));
chk &= 0xf;
if ((chk == tx20_sd) && (tx20_sc < 400)) {
tx20_available = true;
}
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << pin[GPIO_TX20_TXD_BLACK]);
}
void Tx20Read(void)
{
if (!(uptime % TX20_RESET_VALUES)) {
tx20_count = 0;
tx20_wind_sum = 0;
tx20_wind_speed_max = 0;
}
else if (tx20_available) {
tx20_wind_speed_kmh = float(tx20_sc) * 0.36;
if (tx20_wind_speed_kmh > tx20_wind_speed_max) {
tx20_wind_speed_max = tx20_wind_speed_kmh;
}
tx20_count++;
tx20_wind_sum += tx20_wind_speed_kmh;
tx20_wind_speed_avg = tx20_wind_sum / tx20_count;
tx20_wind_direction = tx20_sb;
}
}
void Tx20Init(void) {
pinMode(pin[GPIO_TX20_TXD_BLACK], INPUT);
attachInterrupt(pin[GPIO_TX20_TXD_BLACK], Tx20StartRead, RISING);
}
void Tx20Show(bool json)
{
char wind_speed_string[33];
dtostrfd(tx20_wind_speed_kmh, 2, wind_speed_string);
char wind_speed_max_string[33];
dtostrfd(tx20_wind_speed_max, 2, wind_speed_max_string);
char wind_speed_avg_string[33];
dtostrfd(tx20_wind_speed_avg, 2, wind_speed_avg_string);
char wind_direction_string[4];
GetTextIndexed(wind_direction_string, sizeof(wind_direction_string), tx20_wind_direction, kTx20Directions);
if (json) {
ResponseAppend_P(PSTR(",\"TX20\":{\"Speed\":%s,\"SpeedAvg\":%s,\"SpeedMax\":%s,\"Direction\":\"%s\"}"),
wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string);
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_TX20, wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string);
#endif
}
}
bool Xsns35(uint8_t function)
{
bool result = false;
if (pin[GPIO_TX20_TXD_BLACK] < 99) {
switch (function) {
case FUNC_INIT:
Tx20Init();
break;
case FUNC_EVERY_SECOND:
Tx20Read();
break;
case FUNC_JSON_APPEND:
Tx20Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Tx20Show(0);
break;
#endif
}
}
return result;
}
#endif
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_36_mgc3130.ino"
# 22 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_36_mgc3130.ino"
#ifdef USE_I2C
#ifdef USE_MGC3130
# 35 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_37_rfsensor.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_37_rfsensor.ino"
#ifdef USE_RF_SENSOR
# 33 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_38_az7798.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_38_az7798.ino"
#ifdef USE_AZ7798
#define XSNS_38 38
# 112 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_38_az7798.ino"
#include <TasmotaSerial.h>
#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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_39_max31855.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_40_pn532.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_40_pn532.ino"
#ifdef USE_PN532_HSU
#define XSNS_40 40
#include <TasmotaSerial.h>
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<length[0]; i++) {
sum += buf[i];
}
if (PN532_receive(tmp, 2, timeout) <= 0) {
return PN532_TIMEOUT;
}
if (0 != (uint8_t)(sum + tmp[0]) || 0 != tmp[1]) {
return PN532_INVALID_FRAME;
}
return length[0];
}
uint32_t PN532_getFirmwareVersion(void)
{
uint32_t response;
pn532_packetbuffer[0] = PN532_COMMAND_GETFIRMWAREVERSION;
if (PN532_writeCommand(pn532_packetbuffer, 1)) {
return 0;
}
int16_t status = PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer));
if (0 > 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 "C:/shared/sonoff/Git/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<XdrvMailbox.data_len;ca++) {
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
if (',' == XdrvMailbox.data[ca]) { paramcount++; }
}
UpperCase(XdrvMailbox.data,XdrvMailbox.data);
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"E")) {
pn532_function = 1;
AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Next scanned tag data block 1 will be erased"));
ResponseTime_P(PSTR(",\"PN532\":{\"COMMAND\":\"E\"}}"));
return serviced;
}
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"S")) {
if (paramcount > 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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_41_max44009.ino"
# 20 "C:/shared/sonoff/Git/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 *)&regdata, 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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_42_scd30.ino"
# 20 "C:/shared/sonoff/Git/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 <FrogmoreScd30.h>
#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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_43_hre.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_43_hre.ino"
#ifdef USE_HRE
# 49 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_44_sps30.ino"
# 20 "C:/shared/sonoff/Git/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 <Wire.h>
#include <twi.h>
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<dlen) {
tmp[0] = twi_buff[bind++];
tmp[1] = twi_buff[bind++];
tmp[2] = twi_buff[bind++];
if (sps30_calc_CRC(tmp)!=tmp[2]) {
index+=2;
} else {
data[index++]=tmp[0];
data[index++]=tmp[1];
}
}
}
void sps30_cmd(uint16_t cmd) {
unsigned char cmdb[6];
Wire.beginTransmission(SPS30_ADDR);
cmdb[0]=cmd>>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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_45_vl53l0x.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_45_vl53l0x.ino"
#ifdef USE_I2C
#ifdef USE_VL53L0X
#define XSNS_45 45
#define XI2C_31 31
#include <Wire.h>
#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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_46_MLX90614.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_47_max31865.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_48_chirp.ino"
# 35 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_48_chirp.ino"
#ifdef USE_I2C
#ifdef USE_CHIRP
# 47 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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<CHIRP_MAX_SENSOR_COUNT) {
chirp_sensor[chirp_found_sensors].address = address;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: fw %x"), chirp_sensor[chirp_found_sensors].version);
}
chirp_found_sensors++;
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Found %u CHIRP sensor(s)."), chirp_found_sensors);
return (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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_50_paj7620.ino"
# 31 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_51_rdm6300.ino"
# 21 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_51_rdm6300.ino"
#ifdef USE_RDM6300
#define XSNS_51 51
#define RDM6300_BAUDRATE 9600
#include <TasmotaSerial.h>
#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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_52_ibeacon.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_52_ibeacon.ino"
#ifdef USE_IBEACON
#define XSNS_52 52
#include <TasmotaSerial.h>
#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;cnt<MAX_IBEACONS;cnt++) {
if (ibeacons[cnt].FLAGS) {
ibeacons[cnt].TIME++;
if (ibeacons[cnt].TIME>IB_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;cnt<MAX_IBEACONS;cnt++) {
if (ibeacons[cnt].FLAGS) {
if (!strncmp(ibeacons[cnt].MAC,ib->MAC,12)) {
memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
ibeacons[cnt].TIME=0;
return 1;
}
}
}
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
if (!ibeacons[cnt].FLAGS) {
memcpy(ibeacons[cnt].MAC,ib->MAC,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_sindex<HM17_BSIZ) {
hm17_sbuffer[hm17_sindex]=IBEACON_Serial->read();
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<MAX_IBEACONS;cnt++) {
if (ibeacons[cnt].FLAGS) {
memcpy(mac,ibeacons[cnt].MAC,12);
mac[12]=0;
memcpy(rssi,ibeacons[cnt].RSSI,4);
rssi[4]=0;
WSContentSend_PD(HTTP_IBEACON,mac,rssi);
}
}
}
#endif
# 485 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_52_ibeacon.ino"
bool xsns52_cmd(void) {
bool serviced = true;
const char S_JSON_IBEACON[] = "{\"" D_CMND_SENSOR "%d\":%s:%d}";
const char S_JSON_IBEACON1[] = "{\"" D_CMND_SENSOR "%d\":%s:%s}";
uint16_t len=XdrvMailbox.data_len;
if (len > 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<MAX_IBEACONS;cnt++) ibeacons[cnt].FLAGS=0;
Response_P(S_JSON_IBEACON1, XSNS_52,"clr list","");
}
#ifdef IBEACON_DEBUG
else if (*cp=='d') {
cp++;
if (*cp) hm17_debug=atoi(cp);
Response_P(S_JSON_IBEACON, XSNS_52,"debug",hm17_debug);
}
#endif
} else {
serviced=false;
}
return serviced;
}
#define D_CMND_IBEACON "IBEACON"
bool ibeacon_cmd(void) {
ib_mac[0]=0;
int16_t rssi=0;
const char S_JSON_IBEACON[] = "{\"" D_CMND_IBEACON "_%s_RSSI\":%d}";
uint8_t cmd_len = strlen(D_CMND_IBEACON);
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_IBEACON), cmd_len)) {
rssi = XdrvMailbox.payload;
if (rssi==99) {
memcpy(ib_mac,XdrvMailbox.topic+cmd_len+1,12);
ib_mac[12]=0;
if (hm17_scanning) {
hm17_flag|=2;
} else {
ib_sendbeep();
}
}
Response_P(S_JSON_IBEACON,ib_mac,rssi);
return true;
}
return false;
}
void ib_sendbeep(void) {
hm17_flag=0;
hm17_sendcmd(HM17_CON);
}
void ibeacon_mqtt(const char *mac,const char *rssi) {
char s_mac[14];
char s_rssi[6];
memcpy(s_mac,mac,12);
s_mac[12]=0;
memcpy(s_rssi,rssi,4);
s_rssi[4]=0;
int16_t n_rssi=atoi(s_rssi);
ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "_%s\":{\"RSSI\":%d}}"),s_mac,n_rssi);
MqttPublishTeleSensor();
}
bool Xsns52(byte function)
{
bool result = false;
switch (function) {
case FUNC_INIT:
IBEACON_Init();
break;
case FUNC_LOOP:
IBEACON_loop();
break;
case FUNC_EVERY_SECOND:
hm17_every_second();
break;
case FUNC_COMMAND_SENSOR:
if (XSNS_52 == XdrvMailbox.index) {
result = xsns52_cmd();
}
break;
case FUNC_COMMAND:
result=ibeacon_cmd();
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
if (hm17_found) IBEACON_Show();
break;
#endif
}
return result;
}
#endif
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
# 24 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
#ifdef USE_SML_M
#define XSNS_53 53
#define SML_BAUDRATE 9600
# 45 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
#include <TasmotaSerial.h>
#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 "C:/shared/sonoff/Git/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; hcnt<len/2+1; hcnt++) {
for (uint8_t mcnt=0; mcnt<len; mcnt++) {
flg=0;
for (uint8_t icnt=0; icnt<index; icnt++) {
if (ind[icnt]==mcnt) {
flg=1;
}
}
if (!flg) {
if (array[mcnt]<min) {
min=array[mcnt];
mind=mcnt;
}
}
}
ind[index]=mind;
index++;
min=FLT_MAX;
}
return array[ind[len/2]];
}
double sml_median(struct SML_MEDIAN_FILTER* mf, double in) {
mf->buffer[mf->index]=in;
mf->index++;
if (mf->index>=MEDIAN_SIZE) mf->index=0;
return sml_median_array(mf->buffer,MEDIAN_SIZE);
# 603 "C:/shared/sonoff/Git/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_logindex<sizeof(log_data)-2) {
sml_logindex++;
}
}
} else {
index=0;
log_data[index]=':';
index++;
log_data[index]=' ';
index++;
d_lastms=millis();
while ((millis()-d_lastms)<40) {
if (SML_SAVAILABLE) {
unsigned char c;
if (meter_desc_p[(dump2log&7)-1].type=='e') {
c=SML_SREAD;
sprintf(&log_data[index],"%02x ",c);
index+=3;
if (c==EBUS_SYNC) break;
} else {
if (sml_start==0x77) {
sml_start=0;
} else {
c=SML_SPEAK;
if (c==0x77) {
sml_start=c;
break;
}
}
c=SML_SREAD;
sprintf(&log_data[index],"%02x ",c);
index+=3;
}
}
}
if (index>2) {
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<len-1; type++) {
sprintf(str,"%02x",*cp++);
str+=2;
}
}
value=0;
} else {
value=999999;
scaler=0;
}
}
dval=value;
if (scaler==-1) {
dval/=10;
} else if (scaler==-2) {
dval/=100;
} else if (scaler==-3) {
dval/=1000;
} else if (scaler==-4) {
dval/=10000;
} else if (scaler==1) {
dval*=10;
} else if (scaler==2) {
dval*=100;
} else if (scaler==3) {
dval*=1000;
}
return dval;
}
uint8_t hexnibble(char chr) {
uint8_t rVal = 0;
if (isdigit(chr)) {
rVal = chr - '0';
} else {
chr=toupper(chr);
if (chr >= '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; count<len; count++) {
if (ebus_buffer[count]==EBUS_ESC) {
ebus_buffer[count]+=ebus_buffer[count+1];
count++;
for (count1=count; count1<len; count1++) {
ebus_buffer[count1]=ebus_buffer[count1+1];
}
}
}
}
uint8_t ebus_crc8(uint8_t data, uint8_t crc_init) {
uint8_t crc;
uint8_t polynom;
int i;
crc = crc_init;
for (i = 0; i < 8; i++) {
if (crc & 0x80) {
polynom = (uint8_t) 0x9B;
}
else {
polynom = (uint8_t) 0;
}
crc = (uint8_t)((crc & ~0x80) << 1);
if (data & 0x80) {
crc = (uint8_t)(crc | 1) ;
}
crc = (uint8_t)(crc ^ polynom);
data = (uint8_t)(data << 1);
}
return (crc);
}
uint8_t ebus_CalculateCRC( uint8_t *Data, uint16_t DataLen ) {
uint16_t i;
uint8_t Crc = 0;
for(i = 0 ; i < DataLen ; ++i, ++Data ) {
Crc = ebus_crc8( *Data, Crc );
}
return Crc;
}
void sml_empty_receiver(uint32_t meters) {
while (meter_ss[meters]->available()) {
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; count<SML_BSIZ-1; count++) {
smltbuf[meters][count]=smltbuf[meters][count+1];
}
}
uint8_t iob=(uint8_t)meter_ss[meters]->read();
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; meters<meters_used; meters++) {
if (meter_desc_p[meters].type!='c') {
while (meter_ss[meters]->available()) {
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<MAX_DVARS) {
mp++;
while (*mp==' ') mp++;
uint8_t ind=atoi(mp);
while (*mp>='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<METER_ID_SIZE;p++) {
if (*cp==*mp) {
meter_id[mindex][p]=0;
break;
}
meter_id[mindex][p]=*cp++;
}
} else {
sml_getvalue(cp,mindex);
}
} else {
double dval;
if (meter_desc_p[mindex].type!='e' && meter_desc_p[mindex].type!='r' && meter_desc_p[mindex].type!='m' && meter_desc_p[mindex].type!='M' && meter_desc_p[mindex].type!='p') {
if (meter_desc_p[mindex].type=='o' || meter_desc_p[mindex].type=='c') {
dval=CharToDouble((char*)cp);
} else {
dval=sml_getvalue(cp,mindex);
}
} else {
if (*mp=='b') {
mp++;
uint8_t shift=*mp&7;
ebus_dval>>=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<SML_MAX_VARS-1) {
vindex++;
}
mp = strchr(mp, '|');
if (mp) mp++;
}
}
void SML_Immediate_MQTT(const char *mp,uint8_t index,uint8_t mindex) {
char tpowstr[32];
char jname[24];
char *cp=strchr(mp,',');
if (cp) {
cp++;
cp=strchr(cp,',');
if (cp) {
cp++;
cp=strchr(cp,',');
if (cp) {
cp++;
for (uint8_t count=0;count<sizeof(jname);count++) {
if (*cp==',') {
jname[count]=0;
break;
}
jname[count]=*cp++;
}
cp++;
uint8_t dp=atoi(cp);
if (dp&0x10) {
dtostrfd(meter_vars[index],dp&0xf,tpowstr);
ResponseTime_P(PSTR(",\"%s\":{\"%s\":%s}}"),meter_desc_p[mindex].prefix,jname,tpowstr);
MqttPublishTeleSensor();
}
}
}
}
}
void SML_Show(boolean json) {
int8_t count,mindex,cindex=0;
char tpowstr[32];
char name[24];
char unit[8];
char jname[24];
int8_t index=0,mid=0;
char *mp=(char*)meter_p;
char *cp;
int8_t lastmind=((*mp)&7)-1;
if (lastmind<0 || lastmind>=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;i<sizeof(tpowstr)-2;i++) {
if (*mp=='|' || *mp==0) break;
tpowstr[i]=*mp++;
}
tpowstr[i]=0;
WSContentSend_PD(PSTR("{s}%s{e}"),tpowstr);
mp--;
mp = strchr(mp, '|');
if (mp) mp++;
continue;
}
cp=strchr(mp,'@');
if (cp) {
cp++;
if (*cp=='#') {
sprintf(tpowstr,"\"%s\"",&meter_id[mindex][0]);
mid=1;
} else {
mid=0;
}
cp=strchr(cp,',');
if (cp) {
cp++;
for (count=0;count<sizeof(name);count++) {
if (*cp==',') {
name[count]=0;
break;
}
name[count]=*cp++;
}
cp++;
for (count=0;count<sizeof(unit);count++) {
if (*cp==',') {
unit[count]=0;
break;
}
unit[count]=*cp++;
}
cp++;
for (count=0;count<sizeof(jname);count++) {
if (*cp==',') {
jname[count]=0;
break;
}
jname[count]=*cp++;
}
cp++;
if (!mid) {
uint8_t dp=atoi(cp)&0xf;
dtostrfd(meter_vars[index],dp,tpowstr);
}
if (json) {
if (index==0) {
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%s"),meter_desc_p[mindex].prefix,jname,tpowstr);
}
else {
if (lastmind!=mindex) {
ResponseAppend_P(PSTR("}"));
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%s"),meter_desc_p[mindex].prefix,jname,tpowstr);
lastmind=mindex;
} else {
ResponseAppend_P(PSTR(",\"%s\":%s"),jname,tpowstr);
}
}
} else {
WSContentSend_PD(PSTR("{s}%s %s: {m}%s %s{e}"),meter_desc_p[mindex].prefix,name,tpowstr,unit);
}
}
}
if (index<SML_MAX_VARS-1) {
index++;
}
mp = strchr(cp, '|');
if (mp) mp++;
}
if (json) {
ResponseAppend_P(PSTR("}"));
} else {
}
# 1802 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
}
struct SML_COUNTER {
uint8_t sml_cnt_debounce;
uint8_t sml_cnt_old_state;
uint32_t sml_cnt_last_ts;
uint32_t sml_counter_ltime;
uint16_t sml_debounce;
#ifdef ANALOG_OPTO_SENSOR
int16_t ana_curr;
int16_t ana_max;
int16_t ana_min;
int16_t ana_cmpl;
int16_t ana_cmph;
#endif
} sml_counters[MAX_COUNTERS];
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
void SML_CounterUpd(uint8_t index) ICACHE_RAM_ATTR;
void SML_CounterUpd1(void) ICACHE_RAM_ATTR;
void SML_CounterUpd2(void) ICACHE_RAM_ATTR;
void SML_CounterUpd3(void) ICACHE_RAM_ATTR;
void SML_CounterUpd4(void) ICACHE_RAM_ATTR;
#endif
void SML_CounterUpd(uint8_t index) {
uint8_t level=digitalRead(meter_desc_p[sml_counters[index].sml_cnt_old_state].srcpin);
if (!level) {
uint32_t ltime=millis()-sml_counters[index].sml_counter_ltime;
sml_counters[index].sml_counter_ltime=millis();
if (ltime>sml_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;i<GPIO_SENSOR_END;i++) {
if (pin[i]==gpiopin) {
return true;
}
}
return false;
}
void SML_Init(void) {
meters_used=METERS_USED;
meter_desc_p=meter_desc;
meter_p=meter;
sml_desc_cnt=0;
for (uint32_t cnt=0;cnt<SML_MAX_VARS;cnt++) {
meter_vars[cnt]=0;
}
for (uint32_t cnt=0;cnt<MAX_METERS;cnt++) {
meter_spos[cnt]=0;
}
#ifdef USE_SCRIPT
for (uint32_t cnt=0;cnt<MAX_METERS;cnt++) {
if (script_meter_desc[cnt].txmem) {
free(script_meter_desc[cnt].txmem);
script_meter_desc[cnt].txmem=0;
}
}
uint8_t meter_script=Run_Scripter(">M",-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<METER_DEF_SIZE-1;cnt++) {
if (lp[cnt]=='\n' && lp[cnt+1]=='#') {
mlen=cnt+3;
break;
}
}
if (mlen==0) return;
script_meter=(uint8_t*)calloc(mlen,1);
if (!script_meter) {
goto dddef_exit;
}
tp=script_meter;
goto next_line;
}
}
else {
if (!*lp || *lp=='#' || *lp=='>') {
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; cnt<sizeof(txbuff); cnt++) {
if (*lp==SCRIPT_EOL) {
txbuff[cnt]=0;
txlen=cnt;
break;
}
if (*lp==',') tx_entries++;
txbuff[cnt]=*lp++;
}
if (txlen) {
script_meter_desc[index].txmem=(char*)calloc(txlen+2,1);
if (script_meter_desc[index].txmem) {
strcpy(script_meter_desc[index].txmem,txbuff);
}
script_meter_desc[index].index=0;
script_meter_desc[index].max_index=tx_entries;
sml_send_blocks++;
}
}
}
if (*lp==SCRIPT_EOL) lp--;
goto next_line;
}
if (*lp=='-' || isdigit(*lp)) {
if (*lp=='-') lp++;
uint8_t mnum=strtol(lp,0,10);
if (mnum<1 || mnum>meters_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; meters<meters_used; meters++) {
if (meter_desc_p[meters].type=='c') {
if (meter_desc_p[meters].flag&2) {
#ifdef ANALOG_OPTO_SENSOR
ADS1115_init();
sml_counters[cindex].ana_max=-32768;
sml_counters[cindex].ana_min=+32767;
#endif
} else {
if (meter_desc_p[meters].flag&1) {
pinMode(meter_desc_p[meters].srcpin,INPUT_PULLUP);
} else {
pinMode(meter_desc_p[meters].srcpin,INPUT);
}
if (meter_desc_p[meters].params<=0) {
attachInterrupt(meter_desc_p[meters].srcpin, counter_callbacks[cindex], CHANGE);
sml_counters[cindex].sml_cnt_old_state=meters;
sml_counters[cindex].sml_debounce=-meter_desc_p[meters].params;
}
InjektCounterValue(meters,RtcSettings.pulse_counter[cindex]);
cindex++;
}
} else {
#ifdef SPECIAL_SS
if (meter_desc_p[meters].type=='m' || meter_desc_p[meters].type=='M' || meter_desc_p[meters].type=='p') {
meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1);
} else {
meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1,1);
}
#else
meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1);
#endif
if (meter_ss[meters]->begin(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; meters<meters_used; meters++) {
if (meter_desc_p[meters].type=='c') {
if (meter_desc_p[meters].params>0) {
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 (val<sml_counters[cindex].ana_min) sml_counters[cindex].ana_min=val;
sml_counters[cindex].ana_curr=val;
int16_t range=sml_counters[cindex].ana_max-sml_counters[cindex].ana_min;
}
#endif
} else {
uint8_t state;
sml_counters[cindex].sml_cnt_debounce<<=1;
sml_counters[cindex].sml_cnt_debounce|=(digitalRead(meter_desc_p[meters].srcpin)&1)|0x80;
if (sml_counters[cindex].sml_cnt_debounce==0xc0) {
state=1;
} else {
state=0;
}
if (sml_counters[cindex].sml_cnt_old_state!=state) {
sml_counters[cindex].sml_cnt_old_state=state;
if (state==0) {
RtcSettings.pulse_counter[cindex]++;
InjektCounterValue(meters,RtcSettings.pulse_counter[cindex]);
}
}
}
}
#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
} else {
if (ctime-sml_counters[cindex].sml_cnt_last_ts>10) {
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<meters_used; cnt++) {
if (script_meter_desc[cnt].trxpin>=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;cnt<slen;cnt++) {
sbuff[cnt]|=(CalcEvenParity(sbuff[cnt])<<7);
}
}
if (script_meter_desc[meter].type=='p') {
*ucp++=0xc0;
*ucp++=0xa8;
*ucp++=1;
*ucp++=1;
*ucp++=0;
*ucp++=SML_PzemCrc(sbuff,6);
slen+=6;
}
meter_ss[meter]->write(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 "C:/shared/sonoff/Git/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; meters<meters_used; meters++) {
if (meter_desc_p[meters].type=='c') {
InjektCounterValue(meters,RtcSettings.pulse_counter[cindex]);
cindex++;
}
}
}
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"counter%d: %d\"}}"),index,RtcSettings.pulse_counter[index-1]);
} else if (*cp=='r') {
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"restart\"}}"));
SML_CounterSaveState();
SML_Init();
} else {
serviced=false;
}
}
return serviced;
}
void InjektCounterValue(uint8_t meter,uint32_t counter) {
sprintf((char*)&smltbuf[meter][0],"1-0:1.8.0*255(%d)",counter);
SML_Decode(meter);
}
void SML_CounterSaveState(void) {
for (byte i = 0; i < MAX_COUNTERS; i++) {
Settings.pulse_counter[i] = RtcSettings.pulse_counter[i];
}
}
# 2425 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
bool Xsns53(byte function) {
bool result = false;
switch (function) {
case FUNC_INIT:
SML_Init();
break;
case FUNC_LOOP:
SML_Counter_Poll();
break;
case FUNC_EVERY_50_MSECOND:
if (dump2log) Dump2log();
else SML_Poll();
break;
#ifdef USE_SCRIPT
case FUNC_EVERY_100_MSECOND:
SML_Check_Send();
break;
#endif
case FUNC_JSON_APPEND:
SML_Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
SML_Show(0);
break;
#endif
case FUNC_COMMAND_SENSOR:
if (XSNS_53 == XdrvMailbox.index) {
result = XSNS_53_cmd();
}
break;
case FUNC_SAVE_BEFORE_RESTART:
case FUNC_SAVE_AT_MIDNIGHT:
SML_CounterSaveState();
break;
}
return result;
}
#endif
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
#ifdef USE_I2C
#ifdef USE_INA226
# 70 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
#define XSNS_54 54
#define XI2C_35 35
#define INA226_MAX_ADDRESSES 4
#define INA226_ADDRESS1 (0x40)
#define INA226_ADDRESS2 (0x41)
#define INA226_ADDRESS3 (0x44)
#define INA226_ADDRESS4 (0x45)
#define INA226_REG_CONFIG (0x00)
#define INA226_RES_CONFIG (0x4127)
#define INA226_DEF_CONFIG (0x42FF)
#define INA226_CONFIG_RESET (0x8000)
#define INA226_REG_SHUNTVOLTAGE (0x01)
#define INA226_REG_BUSVOLTAGE (0x02)
#define INA226_REG_POWER (0x03)
#define INA226_REG_CURRENT (0x04)
#define INA226_REG_CALIBRATION (0x05)
typedef struct Ina226SlaveInfo_tag {
uint8_t address;
uint16_t calibrationValue;
uint16_t config;
uint8_t present : 1;
float i_lsb;
} Ina226SlaveInfo_t;
static const uint8_t PROGMEM probeAddresses[INA226_MAX_ADDRESSES] = {INA226_ADDRESS1, INA226_ADDRESS2, INA226_ADDRESS3, INA226_ADDRESS4};
static char Ina226Str[] = "INA226";
static uint8_t slavesFound = 0;
static uint8_t schedule_reinit = 0;
static Ina226SlaveInfo_t slaveInfo[4] = {0};
static float voltages[4];
static float currents[4];
static float powers[4];
static void _debug_fval(const char *str, float fval, uint8_t prec = 4 )
{
char fstr[32];
dtostrfd(fval, prec, fstr);
AddLog_P2( LOG_LEVEL_DEBUG, PSTR("%s: %s"), str, fstr );
}
# 138 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
static uint32_t _expand_r_shunt(uint16_t compact_r_shunt)
{
uint32_t r_shunt_uohms = (compact_r_shunt & 0x8000) ?
(((uint32_t)(compact_r_shunt & 0x7FFF)) * 1000ul) :
(compact_r_shunt & 0x7FFF);
return r_shunt_uohms;
}
void Ina226SetCalibration(uint8_t slaveIndex)
{
Ina226SlaveInfo_t *si = slaveInfo + slaveIndex;
I2cWrite16( si->address, 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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_55_hih_series.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_55_hih_series.ino"
#ifdef USE_I2C
#ifdef USE_HIH6
# 33 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_56_hpma.ino"
# 21 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_56_hpma.ino"
#ifdef USE_HPMA
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_56_hpma.ino"
#define XSNS_56 56
#include <hpma115S0.h>
#include <TasmotaSerial.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_57_tsl2591.ino"
# 20 "C:/shared/sonoff/Git/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.h>
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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_58_dht12.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_59_ds1624.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_60_GPS.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_60_GPS.ino"
#ifdef USE_GPS
# 113 "C:/shared/sonoff/Git/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(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\r\n"
"<GPX version=\"1.1\" creator=\"TASMOTA\" xmlns=\"http://www.topografix.com/GPX/1/1\" \r\n"
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
"xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\r\n"
"<trk>\r\n<trkseg>\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("<trkpt\n\t lat=\"%s\" lon=\"%s\">\n\t<time>%s</time>\n</trkpt>\n"),lat ,lon, stime);
WebServer->sendContent_P(record);
}
void UBXsendFooter(void)
{
WebServer->sendContent(F("</trkseg>\n</trk>\n</gpx>"));
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; i<sizeof(UBX.Message.cfgRate); i++) {
UBXSerial->write(((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.lat<abs(UBX_LAT_LON_THRESHOLD))||(UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon<abs(UBX_LAT_LON_THRESHOLD))) {
DEBUG_SENSOR_LOG(PSTR("UBX: Diff lat: %u lon: %u "),UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat, UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon);
return false;
}
}
UBX.rec_buffer.values.lat = UBX.Message.navPosllh.lat;
UBX.rec_buffer.values.lon = UBX.Message.navPosllh.lon;
DEBUG_SENSOR_LOG(PSTR("UBX: lat/lon: %i / %i"), UBX.rec_buffer.values.lat, UBX.rec_buffer.values.lon);
DEBUG_SENSOR_LOG(PSTR("UBX: hAcc: %d"), UBX.Message.navPosllh.hAcc);
UBX.state.last_iTOW = UBX.Message.navPosllh.iTOW;
UBX.state.last_alt = UBX.Message.navPosllh.alt;
UBX.state.last_vAcc = UBX.Message.navPosllh.vAcc;
UBX.state.last_hAcc = UBX.Message.navPosllh.hAcc;
if (UBX.mode.send_when_new) {
UBXTriggerTele();
}
return true;
} else {
DEBUG_SENSOR_LOG(PSTR("UBX: no valid position data"));
}
return false;
}
void UBXHandleSTATUS()
{
DEBUG_SENSOR_LOG(PSTR("UBX: gpsFix: %u, valid: %u"), UBX.Message.navStatus.gpsFix, (UBX.Message.navStatus.flags)&1);
if ((UBX.Message.navStatus.flags)&1) {
UBX.state.gpsFix = UBX.Message.navStatus.gpsFix;
} else {
UBX.state.gpsFix = 0;
}
}
void UBXHandleTIME()
{
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time: %u-%u-%u %u:%u:%u"), UBX.Message.navTime.year, UBX.Message.navTime.month ,UBX.Message.navTime.day,UBX.Message.navTime.hour,UBX.Message.navTime.min,UBX.Message.navTime.sec);
if (UBX.Message.navTime.valid.UTC == 1) {
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time is valid"));
if (Rtc.user_time_entry == false || UBX.mode.forceUTCupdate) {
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: UTC-Time is valid, set system time"));
TIME_T gpsTime;
gpsTime.year = UBX.Message.navTime.year - 1970;
gpsTime.month = UBX.Message.navTime.month;
gpsTime.day_of_month = UBX.Message.navTime.day;
gpsTime.hour = UBX.Message.navTime.hour;
gpsTime.minute = UBX.Message.navTime.min;
gpsTime.second = UBX.Message.navTime.sec;
Rtc.utc_time = MakeTime(gpsTime);
Rtc.user_time_entry = true;
}
}
}
void UBXHandleOther(void)
{
if (UBX.state.non_empty_loops>6) {
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}<hr>{m}<hr>{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}<hr>{m}<hr>{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 = "<button><a href='/UBX'>Download GPX-File</a></button>";
#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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_61_MI_NRF24.ino"
# 35 "C:/shared/sonoff/Git/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
# 53 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_61_MI_NRF24.ino"
#define XSNS_61 61
#include <vector>
#define FLORA 1
#define MJ_HT_V1 2
#define LYWSD02 3
#define LYWSD03 4
uint8_t kMINRFSlaveID[4][3] = { 0xC4,0x7C,0x8D,
0x58,0x2D,0x34,
0xE7,0x2E,0x00,
0xA4,0xC1,0x38,
};
const char kMINRFSlaveType1[] PROGMEM = "Flora";
const char kMINRFSlaveType2[] PROGMEM = "MJ_HT_V1";
const char kMINRFSlaveType3[] PROGMEM = "LYWSD02";
const char kMINRFSlaveType4[] PROGMEM = "LYWSD03";
const char * kMINRFSlaveType[] PROGMEM = {kMINRFSlaveType1,kMINRFSlaveType2,kMINRFSlaveType3,kMINRFSlaveType4};
const 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 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<mi_sensor_t> 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;
}
}
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;
}
}
# 420 "C:/shared/sonoff/Git/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;
}
MINRF.packetMode = _mode;
}
# 485 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_61_MI_NRF24.ino"
uint32_t MINRFgetSensorSlot(uint8_t (&_serial)[6], uint8_t _type){
if(_type==0xff){
DEBUG_SENSOR_LOG(PSTR("MINRF: will test MAC-type"));
for (uint32_t i=0;i<4;i++){
if(memcmp(_serial,kMINRFSlaveID+i,3)==0){
DEBUG_SENSOR_LOG(PSTR("MINRF: MAC is type %u"), i);
_type = i+1;
}
else {
DEBUG_SENSOR_LOG(PSTR("MINRF: MAC-type is unknown"));
}
}
}
if(_type==0xff) return _type;
DEBUG_SENSOR_LOG(PSTR("MINRF: vector size %u"), MIBLEsensors.size());
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
if(memcmp(_serial,MIBLEsensors.at(i).serial,sizeof(_serial))==0){
DEBUG_SENSOR_LOG(PSTR("MINRF: known sensor at slot: %u"), i);
if(MIBLEsensors.at(i).showedUp < 3){
MIBLEsensors.at(i).showedUp++;
}
return i;
}
DEBUG_SENSOR_LOG(PSTR("MINRF i: %x %x %x %x %x %x"), MIBLEsensors.at(i).serial[5], MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[2],MIBLEsensors.at(i).serial[1],MIBLEsensors.at(i).serial[0]);
DEBUG_SENSOR_LOG(PSTR("MINRF n: %x %x %x %x %x %x"), _serial[5], _serial[4], _serial[3],_serial[2],_serial[1],_serial[0]);
}
DEBUG_SENSOR_LOG(PSTR("MINRF: found new sensor"));
mi_sensor_t _newSensor;
memcpy(_newSensor.serial,_serial, sizeof(_serial));
_newSensor.type = _type;
_newSensor.showedUp = 1;
_newSensor.temp =-1000.0f;
switch (_type)
{
case 1:
_newSensor.moisture =-1000.0f;
_newSensor.fertility =-1000.0f;
_newSensor.lux = 0x00ffffff;
break;
case 2: case 3: case 4:
_newSensor.hum=-1.0f;
_newSensor.bat=0xff;
break;
default:
break;
}
MIBLEsensors.push_back(_newSensor);
DEBUG_SENSOR_LOG(PSTR("MINRF: new sensor at slot: %u"), MIBLEsensors.size()-1);
return MIBLEsensors.size()-1;
};
void MINRFpurgeFakeSensors(void){
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
if(MIBLEsensors.at(i).showedUp<3){
DEBUG_SENSOR_LOG(PSTR("MINRF: remove FAKE sensor at slot: %u"), i);
MIBLEsensors.erase(MIBLEsensors.begin()+i);
}
}
}
void MINRFhandleFloraPacket(void){
if(MINRF.buffer.floraPacket.T.idWord!=0x9800 && MINRF.buffer.floraPacket.T.valueTen!=0x10){
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected Flora packet"));
MINRF_LOG_BUFFER(MINRF.buffer.raw);
return;
}
MINRFreverseMAC(MINRF.buffer.floraPacket.T.serial);
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.floraPacket.T.serial, 0xff);
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
if(_slot==0xff) return;
static float _tempFloat;
switch(MINRF.buffer.floraPacket.L.mode) {
case 4:
_tempFloat=(float)(MINRF.buffer.floraPacket.T.data)/10.0f;
if(_tempFloat<60){
MIBLEsensors.at(_slot).temp=_tempFloat;
}
DEBUG_SENSOR_LOG(PSTR("Flora: Mode 4: U16: %x Temp"), MINRF.buffer.floraPacket.T.data );
break;
case 7:
if(true){
MIBLEsensors.at(_slot).lux=MINRF.buffer.floraPacket.L.data;
}
DEBUG_SENSOR_LOG(PSTR("Flora: Mode 7: U24: %x Lux"), MINRF.buffer.floraPacket.L.data);
break;
case 8:
_tempFloat =(float)MINRF.buffer.floraPacket.M.data;
if(_tempFloat<100){
MIBLEsensors.at(_slot).moisture=_tempFloat;
}
DEBUG_SENSOR_LOG(PSTR("Flora: Mode 8: U8: %x Moisture"), MINRF.buffer.floraPacket.M.data);
break;
case 9:
_tempFloat=(float)(MINRF.buffer.floraPacket.F.data);
if(_tempFloat<65535){
MIBLEsensors.at(_slot).fertility=_tempFloat;
}
DEBUG_SENSOR_LOG(PSTR("Mode 9: U16: %x Fertility"), MINRF.buffer.floraPacket.F.data);
break;
}
}
void MINRFhandleMJ_HT_V1Packet(void){
if(MINRF.buffer.MJ_HT_V1Packet.TH.idWord != 0xaa01 && MINRF.buffer.MJ_HT_V1Packet.TH.valueTen!=0x10){
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected MJ_HT_V1-packet"));
MINRF_LOG_BUFFER(MINRF.buffer.raw);
return;
}
MINRFreverseMAC(MINRF.buffer.MJ_HT_V1Packet.TH.serial);
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.MJ_HT_V1Packet.TH.serial, 0xff);
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
if(_slot==0xff) return;
static float _tempFloat;
switch(MINRF.buffer.MJ_HT_V1Packet.TH.mode) {
case 0x0d:
_tempFloat=(float)(MINRF.buffer.MJ_HT_V1Packet.TH.temp)/10.0f;
if(_tempFloat<60){
MIBLEsensors.at(_slot).temp = _tempFloat;
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1: temp updated"));
}
_tempFloat=(float)(MINRF.buffer.MJ_HT_V1Packet.TH.hum)/10.0f;
if(_tempFloat<100){
MIBLEsensors.at(_slot).hum = _tempFloat;
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1: hum updated"));
}
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1 mode:0x0d: U16: %x Temp U16: %x Hum"), MINRF.buffer.MJ_HT_V1Packet.TH.temp, MINRF.buffer.MJ_HT_V1Packet.TH.hum);
break;
case 0x0a:
if(MINRF.buffer.MJ_HT_V1Packet.B.battery<101){
MIBLEsensors.at(_slot).bat = MINRF.buffer.MJ_HT_V1Packet.B.battery;
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1: bat updated"));
}
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1 mode:0x0a: U8: %x %%"), MINRF.buffer.MJ_HT_V1Packet.B.battery);
break;
}
}
void MINRFhandleLYWSD02Packet(void){
if(MINRF.buffer.LYWSD02Packet.TH.valueTen!=0x10){
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected LYWSD02-packet"));
MINRF_LOG_BUFFER(MINRF.buffer.raw);
return;
}
MINRFreverseMAC(MINRF.buffer.LYWSD02Packet.TH.serial);
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.LYWSD02Packet.TH.serial, 0xff);
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
if(_slot==0xff) return;
static float _tempFloat;
switch(MINRF.buffer.LYWSD02Packet.TH.mode) {
case 4:
_tempFloat=(float)(MINRF.buffer.LYWSD02Packet.TH.data)/10.0f;
if(_tempFloat<60){
MIBLEsensors.at(_slot).temp=_tempFloat;
}
DEBUG_SENSOR_LOG(PSTR("LYWSD02: Mode 4: U16: %x Temp"), MINRF.buffer.LYWSD02Packet.TH.data );
break;
case 6:
_tempFloat=(float)(MINRF.buffer.LYWSD02Packet.TH.data)/10.0f;
if(_tempFloat<101){
MIBLEsensors.at(_slot).hum=_tempFloat;
}
DEBUG_SENSOR_LOG(PSTR("LYWSD02: Mode 6: U16: %x Hum"), MINRF.buffer.LYWSD02Packet.TH.data );
break;
}
}
void MINRFhandleLYWSD03Packet(void){
MINRFreverseMAC(MINRF.buffer.LYWSD02Packet.TH.serial);
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.LYWSD02Packet.TH.serial, 0xff);
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
if(_slot==0xff) return;
MINRF_LOG_BUFFER(MINRF.streamBuffer);
MINRF_LOG_BUFFER(MINRF.lsfrBuffer);
MINRF_LOG_BUFFER(MINRF.buffer.raw);
}
void MINRF_EVERY_50_MSECOND() {
if(MINRF.timer>6000){
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();
}
if (MINRF.packetMode == LYWSD03){
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}<hr>{m}<hr>{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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_62_MI_HM10.ino"
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_62_MI_HM10.ino"
#ifdef USE_HM10
#define XSNS_62 62
#include <TasmotaSerial.h>
#include <vector>
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<mi_sensor_t> 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 "C:/shared/sonoff/Git/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; i<MIBLEsensors.size(); i++){
if(memcmp(_serial,MIBLEsensors.at(i).serial,sizeof(_serial))==0){
DEBUG_SENSOR_LOG(PSTR("MIBLE: known sensor at slot: %u"), i);
if(MIBLEsensors.at(i).showedUp < 3){
MIBLEsensors.at(i).showedUp++;
}
return i;
}
DEBUG_SENSOR_LOG(PSTR("MIBLE i: %x %x %x %x %x %x"), 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]);
DEBUG_SENSOR_LOG(PSTR("MIBLE n: %x %x %x %x %x %x"), _serial[0], _serial[1], _serial[2],_serial[3],_serial[4],_serial[5]);
}
DEBUG_SENSOR_LOG(PSTR("MIBLE: found new sensor"));
mi_sensor_t _newSensor;
memcpy(_newSensor.serial,_serial, sizeof(_serial));
_newSensor.type = _type;
_newSensor.showedUp = 1;
_newSensor.temp =-1000.0f;
switch (_type)
{
case 1:
_newSensor.moisture =-1000.0f;
_newSensor.fertility =-1000.0f;
_newSensor.lux = 0xffff;
break;
case 2: case 3: case 4:
_newSensor.hum=-1.0f;
_newSensor.bat=0xff;
break;
default:
break;
}
MIBLEsensors.push_back(_newSensor);
DEBUG_SENSOR_LOG(PSTR("MIBLE: new sensor at slot: %u"), MIBLEsensors.size()-1);
return MIBLEsensors.size()-1;
};
void HM10SerialInit(void) {
HM10.mode.init = false;
HM10.serialSpeed = HM10_BAUDRATE;
HM10Serial = new TasmotaSerial(pin[GPIO_HM10_RX], pin[GPIO_HM10_TX], 1, 0, HM10_MAX_RX_BUF);
if (HM10Serial->begin(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 "C:/shared/sonoff/Git/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(i<HM10_MAX_RX_BUF){
ret[i] = HM10Serial->read();
}
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}<hr>{m}<hr>{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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_64_aht10.ino"
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_64_aht10.ino"
#ifdef USE_I2C
#ifdef USE_AHT10
#define XSNS_64 64
#define XI2C_43 43
#define AHT10_ADDR 0x38
unsigned char eSensorCalibrateCmd[3] = {0xE1, 0x08, 0x00};
unsigned char eSensorNormalCmd[3] = {0xA8, 0x00, 0x00};
unsigned char eSensorMeasureCmd[3] = {0xAC, 0x33, 0x00};
unsigned char eSensorResetCmd = 0xBA;
struct AHT10 {
float humidity = NAN;
float temperature = NAN;
uint8_t valid = 0;
uint8_t count = 0;
char name[6] = "AHT10";
} AHT10;
bool begin()
{
Wire.begin(AHT10_ADDR);
Wire.beginTransmission(AHT10_ADDR);
Wire.write(eSensorCalibrateCmd, 3);
Wire.endTransmission();
delay(500);
if((readStatus() & 0x68) == 0x08)
return true;
else
{
return false;
}
}
bool AHT10Read(void)
{
unsigned long result, temp[6];
if (AHT10.valid) { AHT10.valid--; }
Wire.beginTransmission(AHT10_ADDR);
Wire.write(eSensorMeasureCmd, 3);
Wire.endTransmission();
delay(100);
Wire.requestFrom(AHT10_ADDR, 6);
for(unsigned char i = 0; Wire.available() > 0; i++)
{
temp[i] = Wire.read();
}
AHT10.humidity = (((temp[1] << 16) | (temp[2] << 8) | temp[3]) >> 4)* 100 / 1048576;
AHT10.temperature = ((200 * (((temp[3] & 0x0F) << 16) | (temp[4] << 8) | temp[5])) / 1048576) - 50;
if (isnan(AHT10.temperature) || isnan(AHT10.humidity)) { return false; }
AHT10.valid = SENSOR_MAX_MISS;
return true;
}
unsigned char readStatus(void)
{
unsigned char result = 0;
Wire.requestFrom(AHT10_ADDR, 1);
result = Wire.read();
return result;
}
void AHT10Detect(void)
{
if (I2cActive(AHT10_ADDR))
{
return;
}
if (begin())
{
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 Xsns64(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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_91_prometheus.ino"
# 22 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_interface.ino"
# 20 "C:/shared/sonoff/Git/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 "C:/shared/sonoff/Git/Tasmota/tasmota/xx2c_interface.ino"
# 20 "C:/shared/sonoff/Git/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